refactor: pass coin viewpoints for every function requiring inputs.

This commit is contained in:
Christopher Jeffrey 2016-12-09 03:52:36 -08:00
parent ead3f64b7f
commit 12b3274d33
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
32 changed files with 1009 additions and 1023 deletions

View File

@ -656,7 +656,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
}
// Verify sequence locks.
valid = yield this.verifyLocks(prev, tx, state.lockFlags);
valid = yield this.verifyLocks(prev, tx, view, state.lockFlags);
if (!valid) {
throw new VerifyError(block,
@ -666,7 +666,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
}
// Count sigops (legacy + scripthash? + witness?)
sigops += tx.getSigopsWeight(state.flags);
sigops += tx.getSigopsWeight(view, state.flags);
if (sigops > constants.block.MAX_SIGOPS_WEIGHT) {
throw new VerifyError(block,
@ -677,7 +677,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
// Contextual sanity checks.
if (i > 0) {
if (!tx.checkInputs(height, ret)) {
if (!tx.checkInputs(view, height, ret)) {
throw new VerifyError(block,
'invalid',
ret.reason,
@ -685,7 +685,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
}
// Push onto verification queue.
jobs.push(tx.verifyAsync(state.flags));
jobs.push(tx.verifyAsync(view, state.flags));
}
// Add new coins.
@ -706,7 +706,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
}
// Make sure the miner isn't trying to conjure more coins.
if (block.getClaimed() > block.getReward(this.network)) {
if (block.getClaimed() > block.getReward(view, this.network)) {
throw new VerifyError(block,
'invalid',
'bad-cb-amount',
@ -916,7 +916,7 @@ Chain.prototype.reconnect = co(function* reconnect(entry) {
this.emit('tip', entry);
this.emit('reconnect', entry, block);
this.emit('connect', entry, block);
this.emit('connect', entry, block, result.view);
});
/**
@ -983,6 +983,8 @@ Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
this.setDeploymentState(result.state);
this.emit('tip', entry);
return result.view;
});
/**
@ -1210,7 +1212,7 @@ Chain.prototype.add = co(function* add(block) {
Chain.prototype._add = co(function* add(block) {
var ret = new VerifyResult();
var initial = true;
var hash, height, entry, prev;
var hash, height, entry, prev, view;
while (block) {
hash = block.hash('hex');
@ -1324,12 +1326,12 @@ Chain.prototype._add = co(function* add(block) {
this.emit('competitor resolved', block, entry);
} else {
// Attempt to add block to the chain index.
yield this.setBestChain(entry, block, prev);
view = yield this.setBestChain(entry, block, prev);
// Emit our block (and potentially resolved
// orphan) only if it is on the main chain.
this.emit('block', block, entry);
this.emit('connect', entry, block);
this.emit('connect', entry, block, view);
if (!initial)
this.emit('resolved', block, entry);
@ -2254,7 +2256,7 @@ Chain.prototype.verifyFinal = co(function* verifyFinal(prev, tx, flags) {
* [Error, Number(minTime), Number(minHeight)].
*/
Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) {
Chain.prototype.getLocks = co(function* getLocks(prev, tx, view, flags) {
var mask = constants.sequence.MASK;
var granularity = constants.sequence.GRANULARITY;
var disableFlag = constants.sequence.DISABLE_FLAG;
@ -2274,9 +2276,10 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) {
if (input.sequence & disableFlag)
continue;
coinHeight = input.coin.height !== -1
? input.coin.height
: this.height + 1;
coinHeight = view.getHeight(input);
if (coinHeight === -1)
coinHeight = this.height + 1;
if ((input.sequence & typeFlag) === 0) {
coinHeight += (input.sequence & mask) - 1;
@ -2303,8 +2306,8 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) {
* @returns {Promise} - Returns Boolean.
*/
Chain.prototype.verifyLocks = co(function* verifyLocks(prev, tx, flags) {
var locks = yield this.getLocks(prev, tx, flags);
Chain.prototype.verifyLocks = co(function* verifyLocks(prev, tx, view, flags) {
var locks = yield this.getLocks(prev, tx, view, flags);
var medianTime;
if (locks.height >= prev.height + 1)

View File

@ -24,7 +24,6 @@ var LDB = require('../db/ldb');
var layout = require('./layout');
var LRU = require('../utils/lru');
var Block = require('../primitives/block');
var Coin = require('../primitives/coin');
var Outpoint = require('../primitives/outpoint');
var TX = require('../primitives/tx');
var Address = require('../primitives/address');
@ -943,116 +942,40 @@ ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) {
* @returns {Promise} - Returns {@link Block}.
*/
ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) {
var block = yield this.getBlock(hash);
var i, j, view, undo, tx, input, hash, coins;
if (!block)
return;
view = new CoinView();
undo = yield this.getUndoCoins(block.hash());
ChainDB.prototype.getBlockView = co(function* getBlockView(block) {
var view = new CoinView();
var undo = yield this.getUndoCoins(block.hash());
var i, j, tx, input, prev, coins;
if (undo.isEmpty())
return block;
return view;
for (i = block.txs.length - 1; i > 0; i--) {
tx = block.txs[i];
for (j = tx.inputs.length - 1; j >= 0; j--) {
input = tx.inputs[j];
hash = input.prevout.hash;
prev = input.prevout.hash;
if (!view.has(hash)) {
if (!view.has(prev)) {
assert(!undo.isEmpty());
if (undo.top().height === -1) {
coins = new Coins();
coins.hash = hash;
coins.hash = prev;
coins.coinbase = false;
view.add(coins);
}
}
input.coin = undo.apply(view, input.prevout);
undo.apply(view, input.prevout);
}
}
// Undo coins should be empty.
assert(undo.isEmpty(), 'Undo coins data inconsistency.');
return block;
});
/**
* Fill a transaction with coins (only unspents).
* @param {TX} tx
* @returns {Promise}
*/
ChainDB.prototype.fillCoins = co(function* fillCoins(tx) {
var found = true;
var i, input, prevout, coin;
if (tx.isCoinbase())
return found;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
if (input.coin)
continue;
coin = yield this.getCoin(prevout.hash, prevout.index);
if (!coin) {
input.coin = null;
found = false;
continue;
}
input.coin = coin;
}
return found;
});
/**
* Fill a transaction with coins (all historical coins).
* @param {TX} tx
* @returns {Promise}
*/
ChainDB.prototype.fillHistory = co(function* fillHistory(tx) {
var found = true;
var i, input, prevout, prev;
if (!this.options.indexTX)
return found;
if (tx.isCoinbase())
return found;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
if (input.coin)
continue;
prev = yield this.getTX(prevout.hash);
if (!prev || prevout.index >= prev.outputs.length) {
input.coin = null;
found = false;
continue;
}
input.coin = Coin.fromTX(prev, prevout.index);
}
return found;
return view;
});
/**
@ -1730,8 +1653,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(entry, block, view) {
if (i > 0) {
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
assert(input.coin);
this.pending.spend(input.coin);
this.pending.spend(view.getOutput(input));
}
}
@ -1745,7 +1667,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(entry, block, view) {
}
// Index the transaction if enabled.
this.indexTX(tx);
this.indexTX(tx, view);
}
// Commit new coin state.
@ -1785,8 +1707,8 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(entry, block) {
for (j = tx.inputs.length - 1; j >= 0; j--) {
input = tx.inputs[j];
input.coin = undo.apply(view, input.prevout);
this.pending.add(input.coin);
undo.apply(view, input.prevout);
this.pending.add(view.getOutput(input));
}
}
@ -1803,7 +1725,7 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(entry, block) {
}
// Remove from transaction index.
this.unindexTX(tx);
this.unindexTX(tx, view);
}
// Undo coins should be empty.
@ -1862,7 +1784,7 @@ ChainDB.prototype.saveOptions = function saveOptions() {
* @param {TX} tx
*/
ChainDB.prototype.indexTX = function indexTX(tx) {
ChainDB.prototype.indexTX = function indexTX(tx, view) {
var hash = tx.hash();
var i, input, output, prevout;
var hashes, addr;
@ -1870,7 +1792,7 @@ ChainDB.prototype.indexTX = function indexTX(tx) {
if (this.options.indexTX) {
this.put(layout.t(hash), tx.toExtended());
if (this.options.indexAddress) {
hashes = tx.getHashes();
hashes = tx.getHashes(view);
for (i = 0; i < hashes.length; i++) {
addr = hashes[i];
this.put(layout.T(addr, hash), DUMMY);
@ -1885,9 +1807,7 @@ ChainDB.prototype.indexTX = function indexTX(tx) {
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
addr = input.getHash();
assert(input.coin);
addr = view.getOutput(input).getHash();
if (!addr)
continue;
@ -1913,7 +1833,7 @@ ChainDB.prototype.indexTX = function indexTX(tx) {
* @param {TX} tx
*/
ChainDB.prototype.unindexTX = function unindexTX(tx) {
ChainDB.prototype.unindexTX = function unindexTX(tx, view) {
var hash = tx.hash();
var i, input, output, prevout;
var hashes, addr;
@ -1921,7 +1841,7 @@ ChainDB.prototype.unindexTX = function unindexTX(tx) {
if (this.options.indexTX) {
this.del(layout.t(hash));
if (this.options.indexAddress) {
hashes = tx.getHashes();
hashes = tx.getHashes(view);
for (i = 0; i < hashes.length; i++) {
addr = hashes[i];
this.del(layout.T(addr, hash));
@ -1936,9 +1856,7 @@ ChainDB.prototype.unindexTX = function unindexTX(tx) {
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
addr = input.getHash();
assert(input.coin);
addr = view.getOutput(input).getHash();
if (!addr)
continue;

View File

@ -138,6 +138,26 @@ Coins.prototype.has = function has(index) {
return this.outputs[index] != null;
};
/**
* Test whether the collection has a coin.
* @param {Number} index
* @returns {Boolean}
*/
Coins.prototype.isUnspent = function isUnspent(index) {
var output;
if (index >= this.outputs.length)
return false;
output = this.outputs[index];
if (!output || output.spent)
return false;
return true;
};
/**
* Get a coin entry.
* @param {Number} index
@ -190,15 +210,29 @@ Coins.prototype.getCoin = function getCoin(index) {
Coins.prototype.spend = function spend(index) {
var entry = this.get(index);
if (!entry)
if (!entry || entry.spent)
return;
this.outputs[index] = null;
this.cleanup();
// this.outputs[index] = null;
// this.cleanup();
entry.spent = true;
return entry;
};
/**
* Cleanup spent outputs.
*/
Coins.prototype.length = function length() {
var len = this.outputs.length;
while (len > 0 && !this.isUnspent(len - 1))
len--;
return len;
};
/**
* Cleanup spent outputs.
*/
@ -218,7 +252,7 @@ Coins.prototype.cleanup = function cleanup() {
*/
Coins.prototype.isEmpty = function isEmpty() {
return this.outputs.length === 0;
return this.length() === 0;
};
/*
@ -258,10 +292,10 @@ Coins.prototype.isEmpty = function isEmpty() {
Coins.prototype.toRaw = function toRaw() {
var bw = new BufferWriter();
var len = this.outputs.length;
var len = this.length();
var size = Math.floor((len + 5) / 8);
var first = this.has(0);
var second = this.has(1);
var first = this.isUnspent(0);
var second = this.isUnspent(1);
var offset = 0;
var i, j, code, ch, output;
@ -296,7 +330,7 @@ Coins.prototype.toRaw = function toRaw() {
for (i = 0; i < size; i++) {
ch = 0;
for (j = 0; j < 8 && 2 + i * 8 + j < len; j++) {
if (this.outputs[2 + i * 8 + j])
if (this.isUnspent(2 + i * 8 + j))
ch |= 1 << j;
}
bw.writeU8(ch);
@ -306,7 +340,7 @@ Coins.prototype.toRaw = function toRaw() {
for (i = 0; i < len; i++) {
output = this.outputs[i];
if (!output)
if (!output || output.spent)
continue;
output.toWriter(bw);
@ -524,6 +558,7 @@ function CoinEntry() {
this.size = 0;
this.raw = null;
this.output = null;
this.spent = false;
}
/**
@ -577,9 +612,11 @@ CoinEntry.prototype.toCoin = function toCoin(coins, index) {
*/
CoinEntry.prototype.toOutput = function toOutput() {
if (this.output)
return this.output;
return decompress.output(new Output(), this.reader());
if (!this.output) {
this.output = new Output();
decompress.output(this.output, this.reader());
}
return this.output;
};
/**
@ -652,4 +689,8 @@ CoinEntry.fromCoin = function fromCoin(coin) {
* Expose
*/
module.exports = Coins;
exports = Coins;
exports.Coins = Coins;
exports.CoinEntry = CoinEntry;
module.exports = exports;

View File

@ -9,6 +9,8 @@
var co = require('../utils/co');
var Coins = require('./coins');
var UndoCoins = require('./undocoins');
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
/**
* A collections of {@link Coins} objects.
@ -79,6 +81,48 @@ CoinView.prototype.removeTX = function removeTX(tx, height) {
return this.add(coins);
};
/**
* Add a tx to the collection.
* @param {TX} tx
* @param {Number} height
*/
CoinView.prototype.addCoin = function addCoin(coin) {
var coins = this.get(coin.hash);
if (!coins) {
coins = new Coins();
coins.hash = coin.hash;
coins.height = coin.height;
coins.coinbase = coin.coinbase;
this.add(coins);
}
if (!coins.has(coin.index))
coins.addCoin(coin);
};
/**
* Add a tx to the collection.
* @param {TX} tx
* @param {Number} height
*/
CoinView.prototype.addOutput = function addOutput(hash, index, output) {
var coins = this.get(hash);
if (!coins) {
coins = new Coins();
coins.hash = hash;
coins.height = -1;
coins.coinbase = false;
this.add(coins);
}
if (!coins.has(index))
coins.addOutput(index, output);
};
/**
* Remove a coin and return it.
* @param {Coins} coins
@ -91,7 +135,7 @@ CoinView.prototype.spendFrom = function spendFrom(coins, index) {
var undo;
if (!entry)
return null;
return false;
this.undo.push(entry);
@ -102,7 +146,7 @@ CoinView.prototype.spendFrom = function spendFrom(coins, index) {
undo.version = coins.version;
}
return entry.toCoin(coins, index);
return true;
};
/**
@ -112,13 +156,45 @@ CoinView.prototype.spendFrom = function spendFrom(coins, index) {
* @returns {Coin}
*/
CoinView.prototype.getCoin = function getCoin(hash, index) {
var coins = this.get(hash);
CoinView.prototype.getCoin = function getCoin(input) {
var coins = this.get(input.prevout.hash);
if (!coins)
return;
return coins.getCoin(index);
return coins.getCoin(input.prevout.index);
};
/**
* Get a single coin.
* @param {Hash} hash
* @param {Number} index
* @returns {Coin}
*/
CoinView.prototype.getHeight = function getHeight(input) {
var coins = this.get(input.prevout.hash);
if (!coins)
return -1;
return coins.height;
};
/**
* Get a single coin.
* @param {Hash} hash
* @param {Number} index
* @returns {Coin}
*/
CoinView.prototype.getOutput = function getOutput(input) {
var coins = this.get(input.prevout.hash);
if (!coins)
return;
return coins.getOutput(input.prevout.index);
};
/**
@ -174,9 +250,7 @@ CoinView.prototype.spendInputs = co(function* spendInputs(db, tx) {
if (!coins)
return false;
input.coin = this.spendFrom(coins, prevout.index);
if (!input.coin)
if (!this.spendFrom(coins, prevout.index))
return false;
}
@ -201,6 +275,92 @@ CoinView.prototype.toArray = function toArray() {
return out;
};
/**
* Convert collection to an array.
* @returns {Coins[]}
*/
CoinView.prototype.toPrevWriter = function toPrevWriter(bw, tx) {
var i, input, coins, entry;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
coins = this.get(input.prevout.hash);
if (!coins) {
bw.writeU8(0);
continue;
}
entry = coins.get(input.prevout.index);
if (!entry) {
bw.writeU8(0);
continue;
}
bw.writeU8(1);
entry.toWriter(bw);
}
return bw;
};
/**
* Convert collection to an array.
* @returns {Coins[]}
*/
CoinView.prototype.toPrev = function toPrev(tx) {
return this.toPrevWriter(new BufferWriter()).render();
};
/**
* Convert collection to an array.
* @returns {Coins[]}
*/
CoinView.prototype.fromPrevReader = function fromPrevReader(br, tx) {
var i, input, coins, entry;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
coins = this.get(input.prevout.hash);
if (!coins) {
coins = new Coins();
coins.hash = input.prevout.hash;
coins.coinbase = false;
this.add(coins);
}
if (br.readU8() === 1) {
entry = Coins.CoinEntry.fromReader(br);
coins.add(input.prevout.index, entry);
}
}
return this;
};
/**
* Convert collection to an array.
* @returns {Coins[]}
*/
CoinView.fromPrevReader = function fromPrevReader(br, tx) {
return new CoinView().fromPrevReader(br, tx);
};
/**
* Convert collection to an array.
* @returns {Coins[]}
*/
CoinView.fromPrev = function fromPrev(data, tx) {
return new CoinView().fromPrevReader(new BufferReader(data), tx);
};
/*
* Expose
*/

View File

@ -287,4 +287,8 @@ UndoCoin.fromRaw = function fromRaw(data) {
* Expose
*/
module.exports = UndoCoins;
exports = UndoCoins;
exports.UndoCoins = UndoCoins;
exports.UndoCoin = UndoCoin;
module.exports = exports;

View File

@ -1539,16 +1539,17 @@ RPC.prototype.__template = co(function* _template(version, coinbase, rules) {
var vbrules = [];
var attempt = yield this._getAttempt(false);
var block = attempt.block;
var i, j, tx, deps, input, dep, output, raw, rwhash;
var i, j, entry, tx, deps, input, dep, output, raw, rwhash;
var template, name, deployment, state;
for (i = 1; i < block.txs.length; i++) {
tx = block.txs[i];
txIndex[tx.hash('hex')] = i;
for (i = 0; i < block.items.length; i++) {
entry = block.items[i];
txIndex[entry.hash] = i;
}
for (i = 1; i < block.txs.length; i++) {
tx = block.txs[i];
for (i = 0; i < block.items.length; i++) {
entry = block.items[i];
tx = entry.tx;
deps = [];
for (j = 0; j < tx.inputs.length; j++) {
@ -1565,8 +1566,8 @@ RPC.prototype.__template = co(function* _template(version, coinbase, rules) {
txid: tx.txid(),
hash: tx.rwhash(),
depends: deps,
fee: tx.getFee(),
sigops: tx.getSigops(),
fee: entry.fee,
sigops: entry.sigops,
weight: tx.getWeight()
});
}
@ -1651,7 +1652,7 @@ RPC.prototype.__template = co(function* _template(version, coinbase, rules) {
hash: rwhash,
depends: [],
fee: 0,
sigops: tx.getSigops(),
sigops: tx.getSigopsWeight(),
weight: tx.getWeight()
};
} else {

View File

@ -660,24 +660,17 @@ HTTPServer.prototype._init = function _init() {
if (!tx)
return send(404);
yield this.node.fillHistory(tx);
send(200, tx.toJSON(this.network));
}));
// TX by address
this.get('/tx/address/:address', con(function* (req, res, send, next) {
var i, txs, tx;
var txs;
enforce(req.options.address, 'Address is required.');
txs = yield this.node.getTXByAddress(req.options.address);
for (i = 0; i < txs.length; i++) {
tx = txs[i];
yield this.node.fillHistory(tx);
}
send(200, txs.map(function(tx) {
return tx.toJSON(this.network);
}, this));
@ -685,17 +678,12 @@ HTTPServer.prototype._init = function _init() {
// Bulk read TXs
this.post('/tx/address', con(function* (req, res, send, next) {
var i, txs, tx;
var txs;
enforce(req.options.address, 'Address is required.');
txs = yield this.node.getTXByAddress(req.options.address);
for (i = 0; i < txs.length; i++) {
tx = txs[i];
yield this.node.fillHistory(tx);
}
send(200, txs.map(function(tx) {
return tx.toJSON(this.network);
}, this));
@ -704,32 +692,32 @@ HTTPServer.prototype._init = function _init() {
// Block by hash/height
this.get('/block/:block', con(function* (req, res, send, next) {
var hash = req.options.hash || req.options.height;
var block;
var block, view;
enforce(hash != null, 'Hash or height required.');
block = yield this.node.getFullBlock(hash);
block = yield this.chain.db.getBlock(hash);
if (!block)
return send(404);
send(200, block.toJSON(this.network));
view = yield this.chain.db.getBlockView(block);
if (!view)
return send(404);
send(200, block.toJSON(this.network, view));
}));
// Mempool snapshot
this.get('/mempool', con(function* (req, res, send, next) {
var i, txs, tx;
var txs;
if (!this.mempool)
return send(400, { error: 'No mempool available.' });
txs = this.mempool.getHistory();
for (i = 0; i < txs.length; i++) {
tx = txs[i];
yield this.node.fillHistory(tx);
}
send(200, txs.map(function(tx) {
return tx.toJSON(this.network);
}, this));
@ -935,14 +923,6 @@ HTTPServer.prototype._init = function _init() {
send(200, tx.toJSON(this.network));
}));
// Fill TX
this.post('/wallet/:id/fill', con(function* (req, res, send, next) {
var tx = req.options.tx;
enforce(tx, 'TX is required.');
yield req.wallet.fillHistory(tx);
send(200, tx.toJSON(this.network));
}));
// Zap Wallet TXs
this.post('/wallet/:id/zap', con(function* (req, res, send, next) {
var options = req.options;

View File

@ -204,7 +204,7 @@ Mempool.prototype._addBlock = function addBlock(block, txs) {
Mempool.prototype.removeBlock = co(function* removeBlock(block, txs) {
var unlock = yield this.locker.lock();
try {
return this._removeBlock(block, txs);
return yield this._removeBlock(block, txs);
} finally {
unlock();
}
@ -219,8 +219,8 @@ Mempool.prototype.removeBlock = co(function* removeBlock(block, txs) {
* @returns {Promise}
*/
Mempool.prototype._removeBlock = function removeBlock(block, txs) {
var i, entry, tx, hash;
Mempool.prototype._removeBlock = co(function* removeBlock(block, txs) {
var i, tx, hash;
for (i = 1; i < txs.length; i++) {
tx = txs[i];
@ -232,15 +232,18 @@ Mempool.prototype._removeBlock = function removeBlock(block, txs) {
tx = tx.clone();
tx.unsetBlock();
entry = MempoolEntry.fromTX(tx, block.height);
this.trackEntry(entry);
try {
yield this._addTX(tx);
} catch (e) {
this.emit('error', e);
continue;
}
this.emit('unconfirmed', tx, block);
}
this.rejects.reset();
};
});
/**
* Reset the mempool.
@ -698,23 +701,20 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
view = yield this.getCoinView(tx);
// Find missing outpoints.
missing = this.injectCoins(tx, view);
missing = this.findMissing(tx, view);
// Maybe store as an orphan.
if (missing.length > 0) {
if (this.storeOrphan(tx, missing))
return missing;
return;
}
if (missing)
return this.storeOrphan(tx, missing);
// Create a mempool entry and
// begin contextual verification.
entry = MempoolEntry.fromTX(tx, this.chain.height);
entry = MempoolEntry.fromTX(tx, view, this.chain.height);
yield this.verify(entry);
yield this.verify(entry, view);
// Add and index the entry.
yield this.addEntry(entry);
yield this.addEntry(entry, view);
// Trim size if we're too big.
if (this.limitSize(hash)) {
@ -735,12 +735,12 @@ Mempool.prototype._addTX = co(function* _addTX(tx) {
* @returns {Promise}
*/
Mempool.prototype.addEntry = co(function* addEntry(entry) {
Mempool.prototype.addEntry = co(function* addEntry(entry, view) {
var i, resolved, tx;
this.trackEntry(entry);
this.trackEntry(entry, view);
this.emit('tx', entry.tx);
this.emit('tx', entry.tx, view);
this.emit('add entry', entry);
if (this.fees)
@ -812,7 +812,7 @@ Mempool.prototype.removeEntry = function removeEntry(entry, limit) {
* @returns {Promise}
*/
Mempool.prototype.verify = co(function* verify(entry) {
Mempool.prototype.verify = co(function* verify(entry, view) {
var tx = entry.tx;
var height = this.chain.height + 1;
var lockFlags = flags.STANDARD_LOCKTIME_FLAGS;
@ -824,7 +824,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
var now, minFee, result;
// Verify sequence locks.
if (!(yield this.verifyLocks(tx, lockFlags))) {
if (!(yield this.verifyLocks(tx, view, lockFlags))) {
throw new VerifyError(tx,
'nonstandard',
'non-BIP68-final',
@ -833,14 +833,14 @@ Mempool.prototype.verify = co(function* verify(entry) {
// Check input an witness standardness.
if (this.requireStandard) {
if (!tx.hasStandardInputs()) {
if (!tx.hasStandardInputs(view)) {
throw new VerifyError(tx,
'nonstandard',
'bad-txns-nonstandard-inputs',
0);
}
if (this.chain.state.hasWitness()) {
if (!tx.hasStandardWitness(ret)) {
if (!tx.hasStandardWitness(view, ret)) {
ret = new VerifyError(tx,
'nonstandard',
ret.reason,
@ -852,7 +852,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
}
// Annoying process known as sigops counting.
if (tx.getSigopsWeight(flags) > constants.tx.MAX_SIGOPS_WEIGHT) {
if (tx.getSigopsWeight(view, flags) > constants.tx.MAX_SIGOPS_WEIGHT) {
throw new VerifyError(tx,
'nonstandard',
'bad-txns-too-many-sigops',
@ -905,18 +905,18 @@ Mempool.prototype.verify = co(function* verify(entry) {
}
// Contextual sanity checks.
if (!tx.checkInputs(height, ret))
if (!tx.checkInputs(view, height, ret))
throw new VerifyError(tx, 'invalid', ret.reason, ret.score);
// Script verification.
try {
yield this.verifyInputs(tx, flags1);
yield this.verifyInputs(tx, view, flags1);
} catch (err) {
if (tx.hasWitness())
throw err;
// Try without segwit and cleanstack.
result = yield this.verifyResult(tx, flags2);
result = yield this.verifyResult(tx, view, flags2);
// If it failed, the first verification
// was the only result we needed.
@ -925,7 +925,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
// If it succeeded, segwit may be causing the
// failure. Try with segwit but without cleanstack.
result = yield this.verifyResult(tx, flags3);
result = yield this.verifyResult(tx, view, flags3);
// Cleanstack was causing the failure.
if (result)
@ -938,7 +938,7 @@ Mempool.prototype.verify = co(function* verify(entry) {
// Paranoid checks.
if (this.paranoid) {
result = yield this.verifyResult(tx, mandatory);
result = yield this.verifyResult(tx, view, mandatory);
assert(result, 'BUG: Verify failed for mandatory but not standard.');
}
});
@ -951,9 +951,9 @@ Mempool.prototype.verify = co(function* verify(entry) {
* @returns {Promise}
*/
Mempool.prototype.verifyResult = co(function* verifyResult(tx, flags) {
Mempool.prototype.verifyResult = co(function* verifyResult(tx, view, flags) {
try {
yield this.verifyInputs(tx, flags);
yield this.verifyInputs(tx, view, flags);
} catch (err) {
if (err.type === 'VerifyError')
return false;
@ -970,8 +970,8 @@ Mempool.prototype.verifyResult = co(function* verifyResult(tx, flags) {
* @returns {Promise}
*/
Mempool.prototype.verifyInputs = co(function* verifyInputs(tx, flags) {
if (yield tx.verifyAsync(flags))
Mempool.prototype.verifyInputs = co(function* verifyInputs(tx, view, flags) {
if (yield tx.verifyAsync(view, flags))
return;
if (!(flags & constants.flags.UNSTANDARD_VERIFY_FLAGS)) {
@ -983,7 +983,7 @@ Mempool.prototype.verifyInputs = co(function* verifyInputs(tx, flags) {
flags &= ~constants.flags.UNSTANDARD_VERIFY_FLAGS;
if (yield tx.verifyAsync(flags)) {
if (yield tx.verifyAsync(view, flags)) {
throw new VerifyError(tx,
'nonstandard',
'non-mandatory-script-verify-flag',
@ -1280,7 +1280,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, missing) {
this.logger.debug('Ignoring large orphan: %s', tx.txid());
if (!tx.hasWitness())
this.rejects.add(tx.hash());
return false;
return;
}
for (i = 0; i < missing.length; i++) {
@ -1288,7 +1288,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, missing) {
if (this.hasReject(prev)) {
this.logger.debug('Not storing orphan %s (rejected parents).', tx.txid());
this.rejects.add(tx.hash());
return false;
return;
}
}
@ -1310,7 +1310,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, missing) {
this.limitOrphans();
return true;
return missing;
};
/**
@ -1479,23 +1479,22 @@ Mempool.prototype.getCoinView = co(function* getCoinView(tx) {
* @returns {Boolean}
*/
Mempool.prototype.injectCoins = function injectCoins(tx, view) {
Mempool.prototype.findMissing = function findMissing(tx, view) {
var missing = [];
var i, input, prevout, coin;
var i, input, coin;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
coin = view.getCoin(prevout.hash, prevout.index);
coin = view.getOutput(input);
if (!coin) {
missing.push(prevout.hash);
missing.push(input.prevout.hash);
continue;
}
input.coin = coin;
}
if (missing.length === 0)
return;
return missing;
};
@ -1516,8 +1515,8 @@ Mempool.prototype.getSnapshot = function getSnapshot() {
* @returns {Promise} - Returns Boolean.
*/
Mempool.prototype.verifyLocks = function verifyLocks(tx, flags) {
return this.chain.verifyLocks(this.chain.tip, tx, flags);
Mempool.prototype.verifyLocks = function verifyLocks(tx, view, flags) {
return this.chain.verifyLocks(this.chain.tip, tx, view, flags);
};
/**
@ -1537,7 +1536,7 @@ Mempool.prototype.verifyFinal = function verifyFinal(tx, flags) {
* @param {MempoolEntry} entry
*/
Mempool.prototype.trackEntry = function trackEntry(entry) {
Mempool.prototype.trackEntry = function trackEntry(entry, view) {
var tx = entry.tx;
var hash = tx.hash('hex');
var i, input, output, key, coin;
@ -1545,7 +1544,7 @@ Mempool.prototype.trackEntry = function trackEntry(entry) {
this.tx[hash] = entry;
if (this.options.indexAddress)
this.txIndex.addTX(tx);
this.txIndex.addTX(tx, view);
assert(!tx.isCoinbase());
@ -1553,8 +1552,6 @@ Mempool.prototype.trackEntry = function trackEntry(entry) {
input = tx.inputs[i];
key = input.prevout.hash + input.prevout.index;
assert(input.coin);
if (this.options.indexAddress)
this.coinIndex.removeCoin(input.prevout);
@ -1659,7 +1656,7 @@ Mempool.prototype.removeSpenders = function removeSpenders(entry) {
Mempool.prototype.memUsage = function memUsage(tx) {
var mem = 0;
var i, j, input, output, coin, op;
var i, j, input, output, op;
mem += 272; // tx
mem += 80; // _hash
@ -1675,23 +1672,6 @@ Mempool.prototype.memUsage = function memUsage(tx) {
input = tx.inputs[i];
mem += 144; // input
if (input.coin) {
coin = input.coin;
mem += 144; // coin
mem += 88; // coin hash
mem += 40; // script
mem += 80; // script raw buffer
mem += 32; // script code array
mem += coin.script.code.length * 40; // opcodes
for (j = 0; j < coin.script.code.length; j++) {
op = coin.script.code[j];
if (op.data)
mem += 80; // op buffers
}
}
mem += 104; // prevout
mem += 88; // prevout hash
@ -1744,106 +1724,6 @@ Mempool.prototype.getSize = function getSize() {
return this.size;
};
/**
* Fill transaction with all unspent _and spent_
* coins. Similar to {@link Mempool#fillHistory}
* except that it will also fill with coins
* from the blockchain as well.
* @param {TX} tx
* @returns {Promise} - Returns {@link TX}.
*/
Mempool.prototype.fillHistory = co(function* fillHistory(tx) {
var found = true;
var i, input, prevout, prev;
if (tx.isCoinbase())
return found;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
if (input.coin)
continue;
prev = this.getTX(prevout.hash);
if (prev) {
if (prevout.index >= prev.outputs.length) {
input.coin = null;
found = false;
continue;
}
input.coin = Coin.fromTX(prev, prevout.index);
continue;
}
prev = yield this.chain.db.getTX(prevout.hash);
if (!prev || prevout.index >= prev.outputs.length) {
input.coin = null;
found = false;
continue;
}
input.coin = Coin.fromTX(prev, prevout.index);
}
return found;
});
/**
* Fill transaction with all unspent
* coins. Similar to {@link Mempool#fillCoins}
* except that it will also fill with coins
* from the blockchain as well.
* @param {TX} tx
* @returns {Promise} - Returns {@link TX}.
*/
Mempool.prototype.fillCoins = co(function* fillCoins(tx) {
var found = true;
var i, input, hash, index, coin;
if (tx.isCoinbase())
return found;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
hash = input.prevout.hash;
index = input.prevout.index;
if (input.coin)
continue;
coin = this.getCoin(hash, index);
if (coin) {
input.coin = coin;
continue;
}
if (this.isSpent(hash, index)) {
input.coin = null;
found = false;
continue;
}
coin = yield this.chain.db.getCoin(hash, index);
if (!coin) {
input.coin = null;
found = false;
continue;
}
input.coin = coin;
}
return found;
});
/**
* Address Index
*/
@ -1877,9 +1757,9 @@ AddressIndex.prototype.getTX = function getTX(address) {
return out;
};
AddressIndex.prototype.addTX = function addTX(tx) {
AddressIndex.prototype.addTX = function addTX(tx, view) {
var key = tx.hash('hex');
var hashes = tx.getHashes('hex');
var hashes = tx.getHashes(view, 'hex');
var i, hash, items;
for (i = 0; i < hashes.length; i++) {

View File

@ -34,6 +34,7 @@ function MempoolEntry(options) {
this.tx = null;
this.height = -1;
this.size = 0;
this.sigops = 0;
this.priority = 0;
this.fee = 0;
this.ts = 0;
@ -54,6 +55,7 @@ MempoolEntry.prototype.fromOptions = function fromOptions(options) {
this.tx = options.tx;
this.height = options.height;
this.size = options.size;
this.sigops = options.sigops;
this.priority = options.priority;
this.fee = options.fee;
this.ts = options.ts;
@ -79,17 +81,18 @@ MempoolEntry.fromOptions = function fromOptions(options) {
* @param {Number} height
*/
MempoolEntry.prototype.fromTX = function fromTX(tx, height) {
var priority = tx.getPriority(height);
var value = tx.getChainValue(height);
MempoolEntry.prototype.fromTX = function fromTX(tx, view, height) {
var priority = tx.getPriority(view, height);
var value = tx.getChainValue(view, height);
var sigops = tx.getSigopsWeight(view);
var dependencies = false;
var size = tx.getVirtualSize();
var fee = tx.getFee();
var fee = tx.getFee(view);
var i, input;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (input.coin.height === -1) {
if (view.getHeight(input) === -1) {
dependencies = true;
break;
}
@ -98,6 +101,7 @@ MempoolEntry.prototype.fromTX = function fromTX(tx, height) {
this.tx = tx;
this.height = height;
this.size = size;
this.sigops = sigops;
this.priority = priority;
this.fee = fee;
this.ts = util.now();
@ -114,8 +118,8 @@ MempoolEntry.prototype.fromTX = function fromTX(tx, height) {
* @returns {MempoolEntry}
*/
MempoolEntry.fromTX = function fromTX(tx, height) {
return new MempoolEntry().fromTX(tx, height);
MempoolEntry.fromTX = function fromTX(tx, view, height) {
return new MempoolEntry().fromTX(tx, view, height);
};
/**

View File

@ -12,8 +12,9 @@ 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');
var MinerBlock = require('./minerblock');
var BlockEntry = MinerBlock.BlockEntry;
/**
* A bitcoin miner (supports mining witness blocks).
@ -413,15 +414,12 @@ Miner.prototype.build = function build(attempt) {
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
entry = this.mempool.getEntry(hash);
item = new QueueItem(entry, attempt);
item = BlockEntry.fromEntry(entry, attempt);
tx = item.tx;
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;
@ -461,7 +459,10 @@ Miner.prototype.build = function build(attempt) {
if (weight > this.maxWeight)
continue;
sigops += tx.getSigopsWeight(attempt.flags);
sigops += item.sigops;
// If we had a view we could do:
// sigops += tx.getSigopsWeight(view, attempt.flags);
if (sigops > this.maxSigops)
continue;
@ -483,6 +484,7 @@ Miner.prototype.build = function build(attempt) {
attempt.weight = weight;
attempt.sigops = sigops;
attempt.fees += item.fee;
attempt.items.push(item);
block.txs.push(tx.clone());
@ -504,21 +506,6 @@ Miner.prototype.build = function build(attempt) {
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

View File

@ -68,6 +68,7 @@ function MinerBlock(options) {
this.sigops = 0;
this.weight = 0;
this.fees = 0;
this.items = [];
this.coinbase = new TX();
this.coinbase.mutable = true;
@ -190,7 +191,7 @@ MinerBlock.prototype._init = function _init() {
this.weight += 8 * scale;
// Initialize sigops weight.
this.sigops = cb.getSigopsWeight(this.flags);
this.sigops = cb.getSigopsWeight(null, this.flags);
};
/**
@ -270,16 +271,17 @@ MinerBlock.prototype.updateMerkle = function updateMerkle() {
* @returns {Boolean} Whether the transaction was successfully added.
*/
MinerBlock.prototype.addTX = function addTX(tx) {
var weight, sigops;
MinerBlock.prototype.addTX = function addTX(tx, view) {
var item, weight, sigops;
assert(!tx.mutable, 'Cannot add mutable TX to block.');
if (this.block.hasTX(tx))
return false;
weight = tx.getWeight();
sigops = tx.getSigopsWeight(this.flags);
item = BlockEntry.fromTX(tx, view, this);
weight = item.tx.getWeight();
sigops = item.sigops;
if (!tx.isFinal(this.height, this.locktime))
return false;
@ -295,10 +297,11 @@ MinerBlock.prototype.addTX = function addTX(tx) {
this.weight += weight;
this.sigops += sigops;
this.fees += tx.getFee();
this.fees += item.fee;
// Add the tx to our block
this.block.txs.push(tx.clone());
this.items.push(item);
// Update coinbase value
this.updateCoinbase();
@ -468,25 +471,11 @@ MinerBlock.prototype.iterate = function iterate() {
*/
MinerBlock.prototype.commit = function commit(nonce) {
var i, j, tx, input;
assert(!this.committed, 'Block is already committed.');
this.committed = true;
this.block.nonce = nonce;
this.block.mutable = false;
this.coinbase.mutable = false;
for (i = 0; i < this.block.txs.length; i++) {
tx = this.block.txs[i];
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
input.coin = null;
}
tx.setBlock(this.block, i);
}
};
/**
@ -512,8 +501,48 @@ MinerBlock.prototype.destroy = function destroy() {
this.destroyed = true;
};
/**
* BlockEntry
* @constructor
*/
function BlockEntry(tx) {
this.tx = tx;
this.hash = tx.hash('hex');
this.fee = 0;
this.rate = 0;
this.priority = 0;
this.free = false;
this.sigops = 0;
this.depCount = 0;
}
BlockEntry.fromTX = function fromTX(tx, view, attempt) {
var entry = new BlockEntry(tx);
entry.fee = tx.getFee(view);
entry.rate = tx.getRate(view);
entry.priority = tx.getPriority(view, attempt.height);
entry.free = false;
entry.sigops = tx.getSigopsWeight(view, attempt.flags);
return entry;
};
BlockEntry.fromEntry = function fromEntry(entry, attempt) {
var item = new BlockEntry(entry.tx);
item.fee = entry.getFee();
item.rate = entry.getRate();
item.priority = entry.getPriority(attempt.height);
item.free = entry.isFree(attempt.height);
item.sigops = entry.sigops;
return item;
};
/*
* Expose
*/
module.exports = MinerBlock;
exports = MinerBlock;
exports.MinerBlock = MinerBlock;
exports.BlockEntry = BlockEntry;
module.exports = exports;

View File

@ -375,16 +375,6 @@ FullNode.prototype.getBlock = function getBlock(hash) {
return this.chain.db.getBlock(hash);
};
/**
* Retrieve a block from the chain database, filled with coins.
* @param {Hash} hash
* @returns {Promise} - Returns {@link Block}.
*/
FullNode.prototype.getFullBlock = function getFullBlock(hash) {
return this.chain.db.getFullBlock(hash);
};
/**
* Retrieve a coin from the mempool or chain database.
* Takes into account spent coins in the mempool.
@ -486,28 +476,6 @@ FullNode.prototype.isSpent = function isSpent(hash, index) {
return this.chain.db.isSpent(hash, index);
};
/**
* Fill a transaction with coins from the mempool
* and chain database (unspent only).
* @param {TX} tx
* @returns {Promise} - Returns {@link TX}.
*/
FullNode.prototype.fillCoins = function fillCoins(tx) {
return this.mempool.fillCoins(tx);
};
/**
* Fill a transaction with all historical coins
* from the mempool and chain database.
* @param {TX} tx
* @returns {Promise} - Returns {@link TX}.
*/
FullNode.prototype.fillHistory = function fillHistory(tx) {
return this.mempool.fillHistory(tx);
};
/*
* Expose
*/

View File

@ -539,14 +539,14 @@ Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
* @returns {Amount} reward
*/
Block.prototype.getReward = function getReward(network) {
Block.prototype.getReward = function getReward(view, network) {
var i, reward, fee;
network = Network.get(network);
reward = btcutils.getReward(this.height, network.halvingInterval);
for (i = 1; i < this.txs.length; i++) {
fee = this.txs[i].getFee();
fee = this.txs[i].getFee(view);
if (fee < 0 || fee > constants.MAX_MONEY)
return -1;
@ -606,7 +606,7 @@ Block.prototype.getPrevout = function getPrevout() {
* @returns {Object}
*/
Block.prototype.inspect = function inspect() {
Block.prototype.inspect = function inspect(view) {
var commitmentHash = this.getCommitmentHash('hex');
return {
hash: this.rhash(),
@ -623,7 +623,9 @@ Block.prototype.inspect = function inspect() {
ts: this.ts,
bits: this.bits,
nonce: this.nonce,
txs: this.txs
txs: this.txs.map(function(tx) {
return tx.inspect(view);
})
};
};
@ -635,7 +637,7 @@ Block.prototype.inspect = function inspect() {
* @returns {Object}
*/
Block.prototype.toJSON = function toJSON(network) {
Block.prototype.toJSON = function toJSON(network, view) {
network = Network.get(network);
return {
hash: this.rhash(),
@ -648,7 +650,7 @@ Block.prototype.toJSON = function toJSON(network) {
nonce: this.nonce,
totalTX: this.totalTX,
txs: this.txs.map(function(tx) {
return tx.toJSON(network);
return tx.toJSON(network, view);
})
};
};

View File

@ -137,7 +137,7 @@ Coin.prototype.inspect = function inspect() {
* @returns {Object}
*/
Coin.prototype.toJSON = function toJSON(network) {
Coin.prototype.toJSON = function toJSON(network, minimal) {
var address = this.getAddress();
network = Network.get(network);
@ -152,8 +152,10 @@ Coin.prototype.toJSON = function toJSON(network) {
script: this.script.toJSON(),
address: address,
coinbase: this.coinbase,
hash: this.hash ? util.revHex(this.hash) : null,
index: this.index
hash: !minimal
? (this.hash ? util.revHex(this.hash) : null)
: undefined,
index: !minimal ? this.index : undefined
};
};

View File

@ -14,7 +14,6 @@ var Network = require('../protocol/network');
var Script = require('../script/script');
var Witness = require('../script/witness');
var Outpoint = require('./outpoint');
var Coin = require('./coin');
var BufferWriter = require('../utils/writer');
var BufferReader = require('../utils/reader');
@ -27,7 +26,6 @@ var BufferReader = require('../utils/reader');
* @property {Script} script - Input script / scriptSig.
* @property {Number} sequence - nSequence.
* @property {Witness} witness - Witness (empty if not present).
* @property {Coin?} coin - Previous output.
*/
function Input(options) {
@ -38,9 +36,6 @@ function Input(options) {
this.script = new Script();
this.sequence = 0xffffffff;
this.witness = new Witness();
this.coin = null;
this.mutable = false;
this._address = null;
if (options)
this.fromOptions(options);
@ -69,9 +64,6 @@ Input.prototype.fromOptions = function fromOptions(options) {
if (options.witness)
this.witness.fromOptions(options.witness);
if (options.coin)
this.coin = Coin(options.coin);
return this;
};
@ -92,14 +84,14 @@ Input.fromOptions = function fromOptions(options) {
* @returns {ScriptType} type
*/
Input.prototype.getType = function getType() {
Input.prototype.getType = function getType(coin) {
var type;
if (this.isCoinbase())
return 'coinbase';
if (this.coin)
return this.coin.getType();
if (coin)
return coin.getType();
if (this.witness.items.length > 0)
type = this.witness.getInputType();
@ -115,13 +107,13 @@ Input.prototype.getType = function getType() {
* @returns {Script?} Redeem script.
*/
Input.prototype.getRedeem = function getRedeem() {
Input.prototype.getRedeem = function getRedeem(coin) {
var redeem, prev;
if (this.isCoinbase())
return;
if (!this.coin) {
if (!coin) {
if (this.witness.isScripthashInput())
return this.witness.getRedeem();
@ -131,7 +123,7 @@ Input.prototype.getRedeem = function getRedeem() {
return;
}
prev = this.coin.script;
prev = coin.script;
if (prev.isScripthash()) {
prev = this.script.getRedeem();
@ -151,13 +143,13 @@ Input.prototype.getRedeem = function getRedeem() {
* @returns {String} subtype
*/
Input.prototype.getSubtype = function getSubtype() {
Input.prototype.getSubtype = function getSubtype(coin) {
var redeem, type;
if (this.isCoinbase())
return;
redeem = this.getRedeem();
redeem = this.getRedeem(coin);
if (!redeem)
return;
@ -174,28 +166,17 @@ Input.prototype.getSubtype = function getSubtype() {
* @returns {Address?} address
*/
Input.prototype.getAddress = function getAddress() {
var address;
Input.prototype.getAddress = function getAddress(coin) {
if (this.isCoinbase())
return;
if (this.coin)
return this.coin.getAddress();
if (coin)
return coin.getAddress();
address = this._address;
if (this.witness.items.length > 0)
return this.witness.getInputAddress();
if (!address) {
if (this.witness.items.length > 0)
address = this.witness.getInputAddress();
else
address = this.script.getInputAddress();
if (!this.mutable)
this._address = address;
}
return address;
return this.script.getInputAddress();
};
/**
@ -243,17 +224,17 @@ Input.prototype.isCoinbase = function isCoinbase() {
* @returns {Object}
*/
Input.prototype.inspect = function inspect() {
Input.prototype.inspect = function inspect(coin) {
return {
type: this.getType(),
subtype: this.getSubtype(),
address: this.getAddress(),
type: this.getType(coin),
subtype: this.getSubtype(coin),
address: this.getAddress(coin),
script: this.script,
witness: this.witness,
redeem: this.getRedeem(),
redeem: this.getRedeem(coin),
sequence: this.sequence,
prevout: this.prevout,
coin: this.coin
coin: coin
};
};
@ -265,21 +246,24 @@ Input.prototype.inspect = function inspect() {
* @returns {Object}
*/
Input.prototype.toJSON = function toJSON(network) {
var address = this.getAddress();
Input.prototype.toJSON = function toJSON(network, coin) {
var address;
network = Network.get(network);
if (address)
address = address.toBase58(network);
if (!coin) {
address = this.getAddress();
if (address)
address = address.toBase58(network);
}
return {
prevout: this.prevout.toJSON(),
coin: this.coin ? this.coin.toJSON(network) : null,
script: this.script.toJSON(),
witness: this.witness.toJSON(),
sequence: this.sequence,
address: address
address: address,
coin: coin ? coin.toJSON(network, true) : null
};
};
@ -293,7 +277,6 @@ Input.prototype.fromJSON = function fromJSON(json) {
assert(json, 'Input data is required.');
assert(util.isNumber(json.sequence));
this.prevout.fromJSON(json.prevout);
this.coin = json.coin ? Coin.fromJSON(json.coin) : null;
this.script.fromJSON(json.script);
this.witness.fromJSON(json.witness);
this.sequence = json.sequence;
@ -388,7 +371,6 @@ Input.prototype.fromCoin = function fromCoin(coin) {
assert(typeof coin.index === 'number');
this.prevout.hash = coin.hash;
this.prevout.index = coin.index;
this.coin = coin;
return this;
};
@ -410,8 +392,10 @@ Input.fromCoin = function fromCoin(coin) {
*/
Input.prototype.fromTX = function fromTX(tx, index) {
var coin = Coin.fromTX(tx, index);
return this.fromCoin(coin);
assert(index < tx.outputs.length);
this.prevout.hash = tx.hash('hex');
this.prevout.index = index;
return this;
};
/**

View File

@ -18,7 +18,6 @@ var BufferWriter = require('../utils/writer');
var base58 = require('../utils/base58');
var Script = require('../script/script');
var Address = require('./address');
var Input = require('./input');
var Output = require('./output');
var ec = require('../crypto/ec');
@ -626,26 +625,6 @@ KeyRing.prototype.ownHash = function ownHash(hash) {
return false;
};
/**
* Check whether transaction input belongs to this address.
* @param {TX|Output} tx - Transaction or Output.
* @param {Number?} index - Output index.
* @returns {Boolean}
*/
KeyRing.prototype.ownInput = function ownInput(tx, index) {
var input;
if (tx instanceof Input) {
input = tx;
} else {
input = tx.inputs[index];
assert(input, 'Input does not exist.');
}
return this.ownHash(input.getHash());
};
/**
* Check whether transaction output belongs to this address.
* @param {TX|Output} tx - Transaction or Output.

View File

@ -19,6 +19,8 @@ var TX = require('./tx');
var Input = require('./input');
var Output = require('./output');
var Coin = require('./coin');
var Outpoint = require('./outpoint');
var CoinMap = require('../blockchain/coinview');
var KeyRing = require('./keyring');
var Address = require('./address');
var workerPool = require('../workers/workerpool').pool;
@ -70,6 +72,7 @@ function MTX(options) {
this.mutable = true;
this.changeIndex = -1;
this.view = new CoinMap();
if (options)
this.fromOptions(options);
@ -156,17 +159,26 @@ MTX.prototype.clone = function clone() {
* @param {Number?} index - Input of output if `options` is a TX.
*/
MTX.prototype.addInput = function addInput(options, index) {
MTX.prototype.addInput = function addInput(coin, index) {
var input = new Input();
input.mutable = true;
if (coin instanceof TX) {
input.fromTX(coin, index);
coin = Coin.fromTX(coin, index);
}
if (options instanceof TX)
input.fromTX(options, index);
else if (options instanceof Coin)
input.fromCoin(options);
else
input.fromOptions(options);
if (coin instanceof Coin) {
input.fromCoin(coin);
this.view.addCoin(coin);
this.inputs.push(input);
return this;
}
if (coin instanceof Outpoint) {
input.prevout.fromOptions(coin);
} else {
input.fromOptions(coin);
}
this.inputs.push(input);
@ -214,6 +226,112 @@ MTX.prototype.addOutput = function addOutput(options, value) {
return this;
};
/**
* Verify all transaction inputs.
* @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS]
* @returns {Boolean} Whether the inputs are valid.
*/
MTX.prototype.verify = function verify(flags) {
return TX.prototype.verify.call(this, this.view, flags);
};
/**
* Verify the transaction inputs on the worker pool
* (if workers are enabled).
* @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS]
* @returns {Promise}
* @returns {Boolean} Whether the inputs are valid.
*/
MTX.prototype.verifyAsync = function verifyAsync(flags) {
return TX.prototype.verify.call(this, this.view, flags);
};
/**
* Calculate the fee for the transaction.
* @returns {Amount} fee (zero if not all coins are available).
*/
MTX.prototype.getFee = function getFee() {
return TX.prototype.getFee.call(this, this.view);
};
/**
* Calculate the total input value.
* @returns {Amount} value
*/
MTX.prototype.getInputValue = function getInputValue() {
return TX.prototype.getInputValue.call(this, this.view);
};
/**
* Get all input addresses.
* @private
* @returns {Address[]} addresses
*/
MTX.prototype.getInputAddresses = function getInputAddresses() {
return TX.prototype.getInputValue.call(this, this.view);
};
/**
* Get all addresses.
* @returns {Address[]} addresses
*/
MTX.prototype.getAddresses = function getAddresses() {
return TX.prototype.getAddresses.call(this, this.view);
};
/**
* Get all input address hashes.
* @returns {Hash[]} hashes
*/
MTX.prototype.getInputHashes = function getInputHashes(enc) {
return TX.prototype.getInputHashes.call(this, this.view, enc);
};
/**
* Test whether the transaction has
* all coins available/filled.
* @returns {Boolean}
*/
MTX.prototype.hasCoins = function hasCoins() {
return TX.prototype.hasCoins.call(this, this.view);
};
/**
* Calculate virtual sigop count.
* @param {VerifyFlags?} flags
* @returns {Number} sigop count
*/
MTX.prototype.getSigops = function getSigops(flags) {
return TX.prototype.getSigops.call(this, this.view, flags);
};
/**
* Perform contextual checks to verify input, output,
* and fee values, as well as coinbase spend maturity
* (coinbases can only be spent 100 blocks or more
* after they're created). Note that this function is
* consensus critical.
* @param {Number} spendHeight - Height at which the
* transaction is being spent. In the mempool this is
* the chain height plus one at the time it entered the pool.
* @param {Object?} ret - Return object, may be
* set with properties `reason` and `score`.
* @returns {Boolean}
*/
MTX.prototype.checkInputs = function checkInputs(spendHeight, ret) {
return TX.prototype.checkInputs.call(this, this.view, spendHeight, ret);
};
/**
* Build input script (or witness) templates (with
* OP_0 in place of signatures).
@ -655,7 +773,7 @@ MTX.prototype.isSigned = function isSigned() {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = input.coin;
coin = this.view.getOutput(input);
if (!coin)
return false;
@ -783,7 +901,7 @@ MTX.prototype.template = function template(ring) {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = input.coin;
coin = this.view.getOutput(input);
if (!coin)
continue;
@ -823,7 +941,7 @@ MTX.prototype.sign = function sign(ring, type) {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = input.coin;
coin = this.view.getOutput(input);
if (!coin)
continue;
@ -867,7 +985,7 @@ MTX.prototype.signAsync = function signAsync(ring, type) {
MTX.prototype.estimateSize = co(function* estimateSize(estimate) {
var scale = constants.WITNESS_SCALE_FACTOR;
var total = 0;
var i, input, output, size, prev;
var i, input, output, size, prev, coin;
// Calculate the size, minus the input scripts.
total += 4;
@ -886,17 +1004,18 @@ MTX.prototype.estimateSize = co(function* estimateSize(estimate) {
// Add size for signatures and public keys
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = this.view.getOutput(input);
size = 0;
// We're out of luck here.
// Just assume it's a p2pkh.
if (!input.coin) {
if (!coin) {
total += 110;
continue;
}
// Previous output script.
prev = input.coin.script;
prev = coin.script;
// P2PK
if (prev.isPubkey()) {
@ -1199,13 +1318,30 @@ MTX.prototype.setSequence = function setSequence(index, locktime, seconds) {
MTX.prototype._mutable = function _mutable() {
var i;
for (i = 0; i < this.inputs.length; i++)
this.inputs[i].mutable = true;
for (i = 0; i < this.outputs.length; i++)
this.outputs[i].mutable = true;
return this;
};
/**
* Inspect the transaction.
* @returns {Object}
*/
MTX.prototype.inspect = function inspect() {
return TX.prototype.inspect.call(this, this.view);
};
/**
* Convert transaction to JSON.
* @param {Network} network
* @returns {Object}
*/
MTX.prototype.toJSON = function toJSON(network) {
return TX.prototype.toJSON.call(this, network, this.view);
};
/**
* @see TX.fromJSON
*/

View File

@ -21,7 +21,6 @@ var VerifyResult = require('../btc/errors').VerifyResult;
var Input = require('./input');
var Output = require('./output');
var Outpoint = require('./outpoint');
var Coin = require('./coin');
var InvItem = require('./invitem');
var workerPool = require('../workers/workerpool').pool;
var BufferWriter = require('../utils/writer');
@ -694,7 +693,7 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type
* @returns {Boolean} Whether the inputs are valid.
*/
TX.prototype.verify = function verify(flags) {
TX.prototype.verify = function verify(view, flags) {
var i, input, coin;
if (this.inputs.length === 0)
@ -705,7 +704,7 @@ TX.prototype.verify = function verify(flags) {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = input.coin;
coin = view.getOutput(input);
if (!coin)
return false;
@ -759,14 +758,14 @@ TX.prototype.verifyInput = function verifyInput(index, coin, flags) {
* @returns {Boolean} Whether the inputs are valid.
*/
TX.prototype.verifyAsync = co(function* verifyAsync(flags) {
TX.prototype.verifyAsync = co(function* verifyAsync(view, flags) {
if (this.inputs.length === 0)
return false;
if (this.isCoinbase())
return true;
return yield workerPool.verify(this, flags);
return yield workerPool.verify(this, view, flags);
});
/**
@ -821,11 +820,11 @@ TX.prototype.isRBF = function isRBF() {
* @returns {Amount} fee (zero if not all coins are available).
*/
TX.prototype.getFee = function getFee() {
if (!this.hasCoins())
TX.prototype.getFee = function getFee(view) {
if (!this.hasCoins(view))
return 0;
return this.getInputValue() - this.getOutputValue();
return this.getInputValue(view) - this.getOutputValue();
};
/**
@ -833,18 +832,22 @@ TX.prototype.getFee = function getFee() {
* @returns {Amount} value
*/
TX.prototype.getInputValue = function getInputValue() {
TX.prototype.getInputValue = function getInputValue(view) {
var total = 0;
var i;
var i, input, coin;
if (this._inputValue !== -1)
return this._inputValue;
if (!this.hasCoins())
return total;
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = view.getOutput(input);
for (i = 0; i < this.inputs.length; i++)
total += this.inputs[i].coin.value;
if (!coin)
return 0;
total += coin.value;
}
if (!this.mutable)
this._inputValue = total;
@ -859,13 +862,15 @@ TX.prototype.getInputValue = function getInputValue() {
TX.prototype.getOutputValue = function getOutputValue() {
var total = 0;
var i;
var i, output;
if (this._outputValue !== -1)
return this._outputValue;
for (i = 0; i < this.outputs.length; i++)
total += this.outputs[i].value;
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
total += output.value;
}
if (!this.mutable)
this._outputValue = total;
@ -879,16 +884,18 @@ TX.prototype.getOutputValue = function getOutputValue() {
* @returns {Array}
*/
TX.prototype._getInputAddresses = function getInputAddresses() {
TX.prototype._getInputAddresses = function getInputAddresses(view) {
var table = {};
var addrs = [];
var i, address, hash;
var i, address, hash, input, coin;
if (this.isCoinbase())
return [addrs, table];
for (i = 0; i < this.inputs.length; i++) {
address = this.inputs[i].getAddress();
input = this.inputs[i];
coin = view ? view.getOutput(input) : null;
address = input.getAddress(coin);
if (!address)
continue;
@ -938,8 +945,8 @@ TX.prototype._getOutputAddresses = function getOutputAddresses() {
* @returns {Array}
*/
TX.prototype._getAddresses = function getAddresses() {
var inputs = this._getInputAddresses();
TX.prototype._getAddresses = function getAddresses(view) {
var inputs = this._getInputAddresses(view);
var output = this.getOutputAddresses();
var addrs = inputs[0];
var table = inputs[1];
@ -963,8 +970,8 @@ TX.prototype._getAddresses = function getAddresses() {
* @returns {Address[]} addresses
*/
TX.prototype.getInputAddresses = function getInputAddresses() {
return this._getInputAddresses()[0];
TX.prototype.getInputAddresses = function getInputAddresses(view) {
return this._getInputAddresses(view)[0];
};
/**
@ -981,8 +988,8 @@ TX.prototype.getOutputAddresses = function getOutputAddresses() {
* @returns {Address[]} addresses
*/
TX.prototype.getAddresses = function getAddresses() {
return this._getAddresses()[0];
TX.prototype.getAddresses = function getAddresses(view) {
return this._getAddresses(view)[0];
};
/**
@ -990,15 +997,15 @@ TX.prototype.getAddresses = function getAddresses() {
* @returns {Hash[]} hashes
*/
TX.prototype.getInputHashes = function getInputHashes(enc) {
TX.prototype.getInputHashes = function getInputHashes(view, enc) {
var i, input, table;
if (enc === 'hex') {
table = this._getInputAddresses()[1];
table = this._getInputAddresses(view)[1];
return Object.keys(table);
}
input = this.getInputAddresses();
input = this.getInputAddresses(view);
for (i = 0; i < input.length; i++)
input[i] = input[i].getHash();
@ -1032,11 +1039,11 @@ TX.prototype.getOutputHashes = function getOutputHashes(enc) {
* @returns {Hash[]} hashes
*/
TX.prototype.getHashes = function getHashes(enc) {
TX.prototype.getHashes = function getHashes(view, enc) {
var i, hashes, table;
if (enc === 'hex') {
table = this._getAddresses()[1];
table = this._getAddresses(view)[1];
return Object.keys(table);
}
@ -1054,94 +1061,21 @@ TX.prototype.getHashes = function getHashes(enc) {
* @returns {Boolean}
*/
TX.prototype.hasCoins = function hasCoins() {
var i;
TX.prototype.hasCoins = function hasCoins(view) {
var i, input;
if (this.inputs.length === 0)
return false;
for (i = 0; i < this.inputs.length; i++) {
if (!this.inputs[i].coin)
input = this.inputs[i];
if (!view.getOutput(input))
return false;
}
return true;
};
/**
* Attempt to connect coins to prevouts.
* @param {Coin|TX|Coin[]|TX[]} coins
* @returns {Boolean} Whether the transaction is now completely filled.
*/
TX.prototype.fillCoins = function fillCoins(coins) {
var result = true;
var i, input, hash, index, map, tx, coin;
if ((coins instanceof Coin)
|| (coins instanceof TX)) {
coins = [coins];
}
if (Array.isArray(coins)) {
map = {};
for (i = 0; i < coins.length; i++) {
coin = coins[i];
if (coin instanceof TX)
map[coin.hash('hex')] = coin;
else if (coin instanceof Coin)
map[coin.hash + coin.index] = coin;
else
assert(false, 'Non-coin object passed to fillCoins.');
}
coins = map;
}
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
hash = input.prevout.hash;
index = input.prevout.index;
if (input.coin)
continue;
tx = coins[hash];
if (tx) {
if (index < tx.outputs.length) {
input.coin = Coin.fromTX(tx, index);
continue;
}
result = false;
continue;
}
coin = coins[hash + index];
if (coin) {
input.coin = coin;
continue;
}
result = false;
}
return result;
};
/**
* Empty coins.
*/
TX.prototype.emptyCoins = function emptyCoins() {
var i, input;
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
input.coin = null;
}
};
/**
* Check finality of transaction by examining nLockTime and nSequences.
* @example
@ -1159,7 +1093,7 @@ TX.prototype.emptyCoins = function emptyCoins() {
TX.prototype.isFinal = function isFinal(height, ts) {
var threshold = constants.LOCKTIME_THRESHOLD;
var i;
var i, input;
if (this.locktime === 0)
return true;
@ -1168,7 +1102,8 @@ TX.prototype.isFinal = function isFinal(height, ts) {
return true;
for (i = 0; i < this.inputs.length; i++) {
if (this.inputs[i].sequence !== 0xffffffff)
input = this.inputs[i];
if (input.sequence !== 0xffffffff)
return false;
}
@ -1202,7 +1137,7 @@ TX.prototype.getLegacySigops = function getLegacySigops() {
* @returns {Number} sigop count
*/
TX.prototype.getScripthashSigops = function getScripthashSigops() {
TX.prototype.getScripthashSigops = function getScripthashSigops(view) {
var total = 0;
var i, input, coin;
@ -1211,7 +1146,7 @@ TX.prototype.getScripthashSigops = function getScripthashSigops() {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = input.coin;
coin = view.getOutput(input);
if (!coin)
continue;
@ -1229,9 +1164,9 @@ TX.prototype.getScripthashSigops = function getScripthashSigops() {
* @returns {Number} sigop weight
*/
TX.prototype.getSigopsWeight = function getSigopsWeight(flags) {
TX.prototype.getSigopsWeight = function getSigopsWeight(view, flags) {
var weight = this.getLegacySigops() * constants.WITNESS_SCALE_FACTOR;
var input, i;
var i, input, coin;
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
@ -1240,17 +1175,18 @@ TX.prototype.getSigopsWeight = function getSigopsWeight(flags) {
return weight;
if (flags & constants.flags.VERIFY_P2SH)
weight += this.getScripthashSigops() * constants.WITNESS_SCALE_FACTOR;
weight += this.getScripthashSigops(view) * constants.WITNESS_SCALE_FACTOR;
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = view.getOutput(input);
if (!input.coin)
if (!coin)
continue;
weight += Script.getWitnessSigops(
input.script,
input.coin.script,
coin.script,
input.witness,
flags);
}
@ -1264,13 +1200,13 @@ TX.prototype.getSigopsWeight = function getSigopsWeight(flags) {
* @returns {Number} sigop count
*/
TX.prototype.getSigops = function getSigops(flags) {
TX.prototype.getSigops = function getSigops(view, flags) {
var scale = constants.WITNESS_SCALE_FACTOR;
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
return (this.getSigopsWeight(flags) + scale - 1) / scale | 0;
return (this.getSigopsWeight(view, flags) + scale - 1) / scale | 0;
};
/**
@ -1455,7 +1391,7 @@ TX.prototype.isStandard = function isStandard(ret) {
* @returns {Boolean}
*/
TX.prototype.hasStandardInputs = function hasStandardInputs() {
TX.prototype.hasStandardInputs = function hasStandardInputs(view) {
var maxSigops = constants.script.MAX_SCRIPTHASH_SIGOPS;
var i, input, coin, redeem;
@ -1464,13 +1400,13 @@ TX.prototype.hasStandardInputs = function hasStandardInputs() {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = input.coin;
coin = view.getOutput(input);
if (!coin)
return false;
if (coin.script.isUnknown())
return false;
if (coin.script.isPubkeyhash())
continue;
if (coin.script.isScripthash()) {
redeem = input.script.getRedeem();
@ -1480,7 +1416,12 @@ TX.prototype.hasStandardInputs = function hasStandardInputs() {
if (redeem.getSigops(true) > maxSigops)
return false;
continue;
}
if (coin.script.isUnknown())
return false;
}
return true;
@ -1492,13 +1433,13 @@ TX.prototype.hasStandardInputs = function hasStandardInputs() {
* @returns {Boolean}
*/
TX.prototype.hasStandardWitness = function hasStandardWitness(ret) {
TX.prototype.hasStandardWitness = function hasStandardWitness(view, ret) {
var result;
if (!ret)
ret = new VerifyResult();
result = this.getWitnessStandard();
result = this.getWitnessStandard(view);
switch (result) {
case BAD_WITNESS:
@ -1525,9 +1466,9 @@ TX.prototype.hasStandardWitness = function hasStandardWitness(ret) {
* @returns {Boolean}
*/
TX.prototype.getWitnessStandard = function getWitnessStandard() {
TX.prototype.getWitnessStandard = function getWitnessStandard(view) {
var ret = BAD_OKAY;
var i, j, input, prev, hash, redeem, m, n;
var i, j, input, prev, hash, redeem, m, n, coin;
if (!this.hasWitness())
return ret;
@ -1537,14 +1478,15 @@ TX.prototype.getWitnessStandard = function getWitnessStandard() {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = view.getOutput(input);
if (!input.coin)
if (!coin)
continue;
if (input.witness.length === 0)
continue;
prev = input.coin.script;
prev = coin.script;
if (prev.isScripthash()) {
prev = input.script.getRedeem();
@ -1660,32 +1602,34 @@ TX.prototype.getWitnessStandard = function getWitnessStandard() {
* @returns {Boolean}
*/
TX.prototype.checkInputs = function checkInputs(spendHeight, ret) {
TX.prototype.checkInputs = function checkInputs(view, spendHeight, ret) {
var total = 0;
var i, input, coin, fee, value;
var i, input, coins, coin, fee, value;
if (!ret)
ret = new VerifyResult();
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = input.coin;
coins = view.get(input.prevout.hash);
if (!coin) {
if (!coins) {
// Note: don't trigger dos score here.
ret.reason = 'bad-txns-inputs-missingorspent';
ret.score = 0;
return false;
}
if (coin.coinbase && spendHeight != null) {
if (spendHeight - coin.height < constants.tx.COINBASE_MATURITY) {
if (coins.coinbase && spendHeight != null) {
if (spendHeight - coins.height < constants.tx.COINBASE_MATURITY) {
ret.reason = 'bad-txns-premature-spend-of-coinbase';
ret.score = 0;
return false;
}
}
coin = view.getOutput(input);
if (coin.value < 0 || coin.value > constants.MAX_MONEY) {
ret.reason = 'bad-txns-inputvalues-outofrange';
ret.score = 100;
@ -1760,9 +1704,9 @@ TX.prototype.getModifiedSize = function getModifiedSize(size) {
* @returns {Number}
*/
TX.prototype.getPriority = function getPriority(height, size) {
TX.prototype.getPriority = function getPriority(view, height, size) {
var sum = 0;
var i, input, age;
var i, input, age, coin, coinHeight;
assert(typeof height === 'number', 'Must pass in height.');
@ -1774,16 +1718,19 @@ TX.prototype.getPriority = function getPriority(height, size) {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = view.getOutput(input);
if (!input.coin)
if (!coin)
continue;
if (input.coin.height === -1)
coinHeight = view.getHeight(input);
if (coinHeight === -1)
continue;
if (input.coin.height <= height) {
age = height - input.coin.height;
sum += input.coin.value * age;
if (coinHeight <= height) {
age = height - coinHeight;
sum += coin.value * age;
}
}
@ -1796,9 +1743,9 @@ TX.prototype.getPriority = function getPriority(height, size) {
* @returns {Number}
*/
TX.prototype.getChainValue = function getChainValue(height) {
TX.prototype.getChainValue = function getChainValue(view, height) {
var value = 0;
var i, input;
var i, input, coin, coinHeight;
if (this.isCoinbase())
return value;
@ -1808,15 +1755,18 @@ TX.prototype.getChainValue = function getChainValue(height) {
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
coin = view.getOutput(input);
if (!input.coin)
if (!coin)
continue;
if (input.coin.height === -1)
coinHeight = view.getHeight(input);
if (coinHeight === -1)
continue;
if (input.coin.height <= height)
value += input.coin.value;
if (coinHeight <= height)
value += coin.value;
}
return value;
@ -1834,8 +1784,8 @@ TX.prototype.getChainValue = function getChainValue(height) {
* @returns {Boolean}
*/
TX.prototype.isFree = function isFree(height, size) {
var priority = this.getPriority(height, size);
TX.prototype.isFree = function isFree(view, height, size) {
var priority = this.getPriority(view, height, size);
return priority > constants.tx.FREE_THRESHOLD;
};
@ -1879,11 +1829,11 @@ TX.prototype.getRoundFee = function getRoundFee(size, rate) {
* @returns {Rate}
*/
TX.prototype.getRate = function getRate(size) {
TX.prototype.getRate = function getRate(view, size) {
if (size == null)
size = this.getVirtualSize();
return btcutils.getRate(size, this.getFee());
return btcutils.getRate(size, this.getFee(view));
};
/**
@ -2051,12 +2001,18 @@ TX.prototype.toInv = function toInv() {
* @returns {Object}
*/
TX.prototype.inspect = function inspect() {
var rate = this.getRate();
TX.prototype.inspect = function inspect(view) {
var rate = 0;
var fee = 0;
// Rate can exceed 53 bits in testing.
if (!util.isSafeInteger(rate))
rate = 0;
if (view) {
fee = this.getFee(view);
rate = this.getRate(view);
// Rate can exceed 53 bits in testing.
if (!util.isSafeInteger(rate))
rate = 0;
}
return {
hash: this.rhash(),
@ -2065,9 +2021,9 @@ TX.prototype.inspect = function inspect() {
virtualSize: this.getVirtualSize(),
height: this.height,
value: Amount.btc(this.getOutputValue()),
fee: Amount.btc(this.getFee()),
minFee: Amount.btc(this.getMinFee()),
fee: Amount.btc(fee),
rate: Amount.btc(rate),
minFee: Amount.btc(this.getMinFee()),
date: util.date(this.ts || this.ps),
block: this.block ? util.revHex(this.block) : null,
ts: this.ts,
@ -2075,7 +2031,10 @@ TX.prototype.inspect = function inspect() {
index: this.index,
version: this.version,
flag: this.flag,
inputs: this.inputs,
inputs: this.inputs.map(function(input) {
var coin = view ? view.getOutput(input) : null;
return input.inspect(coin);
}),
outputs: this.outputs,
locktime: this.locktime
};
@ -2089,12 +2048,18 @@ TX.prototype.inspect = function inspect() {
* @returns {Object}
*/
TX.prototype.toJSON = function toJSON(network) {
var rate = this.getRate();
TX.prototype.toJSON = function toJSON(network, view) {
var rate = 0;
var fee = 0;
// Rate can exceed 53 bits in testing.
if (!util.isSafeInteger(rate))
rate = 0;
if (view) {
fee = this.getFee(view);
rate = this.getRate(view);
// Rate can exceed 53 bits in testing.
if (!util.isSafeInteger(rate))
rate = 0;
}
network = Network.get(network);
@ -2107,12 +2072,13 @@ TX.prototype.toJSON = function toJSON(network) {
ps: this.ps,
date: util.date(this.ts || this.ps),
index: this.index,
fee: Amount.btc(this.getFee()),
fee: Amount.btc(fee),
rate: Amount.btc(rate),
version: this.version,
flag: this.flag,
inputs: this.inputs.map(function(input) {
return input.toJSON(network);
var coin = view ? view.getCoin(input) : null;
return input.toJSON(network, coin);
}),
outputs: this.outputs.map(function(output) {
return output.toJSON(network);
@ -2359,7 +2325,7 @@ TX.prototype.frameWitness = function frameWitness() {
TX.prototype.frameNormalWriter = function frameNormalWriter(bw) {
var offset = bw.written;
var i;
var i, input, output;
if (this.inputs.length === 0 && this.outputs.length !== 0)
throw new Error('Cannot serialize zero-input tx.');
@ -2368,13 +2334,17 @@ TX.prototype.frameNormalWriter = function frameNormalWriter(bw) {
bw.writeVarint(this.inputs.length);
for (i = 0; i < this.inputs.length; i++)
this.inputs[i].toWriter(bw);
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
input.toWriter(bw);
}
bw.writeVarint(this.outputs.length);
for (i = 0; i < this.outputs.length; i++)
this.outputs[i].toWriter(bw);
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
output.toWriter(bw);
}
bw.writeU32(this.locktime);
@ -2392,7 +2362,7 @@ TX.prototype.frameNormalWriter = function frameNormalWriter(bw) {
TX.prototype.frameWitnessWriter = function frameWitnessWriter(bw) {
var offset = bw.written;
var witnessSize = 0;
var i, start;
var i, start, input, output;
if (this.inputs.length === 0 && this.outputs.length !== 0)
throw new Error('Cannot serialize zero-input tx.');
@ -2403,13 +2373,17 @@ TX.prototype.frameWitnessWriter = function frameWitnessWriter(bw) {
bw.writeVarint(this.inputs.length);
for (i = 0; i < this.inputs.length; i++)
this.inputs[i].toWriter(bw);
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
input.toWriter(bw);
}
bw.writeVarint(this.outputs.length);
for (i = 0; i < this.outputs.length; i++)
this.outputs[i].toWriter(bw);
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
output.toWriter(bw);
}
start = bw.written;

View File

@ -2359,7 +2359,7 @@ TXDB.prototype.getAccountCoins = co(function* getAccountCoins(account) {
* @returns {Promise} - Returns {@link TX}.
*/
TXDB.prototype.fillHistory = co(function* fillHistory(tx) {
TXDB.prototype.getSpentCoins = co(function* getSpentCoins(tx) {
var coins = [];
var i, input, credits, credit;
@ -2377,45 +2377,12 @@ TXDB.prototype.fillHistory = co(function* fillHistory(tx) {
continue;
}
input.coin = credit.coin;
coins.push(credit.coin);
}
return coins;
});
/**
* Fill a transaction with coins.
* @param {TX} tx
* @returns {Promise} - Returns {@link TX}.
*/
TXDB.prototype.fillCoins = co(function* fillCoins(tx) {
var i, input, prevout, credit;
if (tx.isCoinbase())
return;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
if (input.coin)
continue;
credit = yield this.getCredit(prevout.hash, prevout.index);
if (!credit)
continue;
if (credit.spent)
continue;
input.coin = credit.coin;
}
});
/**
* Get TXDB state.
* @returns {Promise}
@ -2496,7 +2463,7 @@ TXDB.prototype.toDetails = co(function* toDetails(txs) {
TXDB.prototype._toDetails = co(function* _toDetails(tx) {
var details = new Details(this, tx);
var coins = yield this.fillHistory(tx);
var coins = yield this.getSpentCoins(tx);
var i, coin, path, output;
for (i = 0; i < tx.inputs.length; i++) {

View File

@ -1629,10 +1629,10 @@ Wallet.prototype._send = co(function* send(options, passphrase) {
if (!tx.isSigned())
throw new Error('TX could not be fully signed.');
tx = tx.toTX();
assert(tx.getFee() <= constants.tx.MAX_FEE, 'TX exceeds maxfee.');
tx = tx.toTX();
yield this.db.addTX(tx);
this.logger.debug('Sending wallet tx (%s): %s', this.id, tx.txid());
@ -1766,6 +1766,8 @@ Wallet.prototype.deriveInputs = co(function* deriveInputs(tx) {
var rings = [];
var i, paths, path, account, ring;
assert(tx.mutable);
paths = yield this.getInputPaths(tx);
for (i = 0; i < paths.length; i++) {
@ -1855,7 +1857,7 @@ Wallet.prototype.getInputPaths = co(function* getInputPaths(tx) {
var paths = [];
var i, hashes, hash, path;
yield this.fillCoins(tx);
assert(tx.mutable);
if (!tx.hasCoins())
throw new Error('Not all coins available.');
@ -2077,16 +2079,6 @@ Wallet.prototype.sign = co(function* sign(tx, passphrase) {
return yield tx.signAsync(rings);
});
/**
* Fill transaction with coins.
* @param {TX} tx
* @returns {Promise} - Returns {@link TX}.
*/
Wallet.prototype.fillCoins = function fillCoins(tx) {
return this.txdb.fillCoins(tx);
};
/**
* Fill transaction with historical coins.
* @param {TX} tx

View File

@ -46,7 +46,7 @@ jobs.execute = function execute(p) {
jobs._execute = function execute(p) {
switch (p.cmd) {
case packets.types.VERIFY:
return jobs.verify(p.tx, p.flags);
return jobs.verify(p.tx, p.view, p.flags);
case packets.types.VERIFYINPUT:
return jobs.verifyInput(p.tx, p.index, p.coin, p.flags);
case packets.types.SIGN:
@ -74,8 +74,8 @@ jobs._execute = function execute(p) {
* @returns {Boolean}
*/
jobs.verify = function verify(tx, flags) {
var result = tx.verify(flags);
jobs.verify = function verify(tx, view, flags) {
var result = tx.verify(view, flags);
return new packets.VerifyResultPacket(result);
};

View File

@ -110,6 +110,13 @@ Master.prototype._initChildProcess = function _initChildProcess() {
process.stdin.on('error', util.nop);
process.stdout.on('error', util.nop);
process.stderr.on('error', util.nop);
process.on('uncaughtException', function(err) {
self.send(new packets.ErrorPacket(err));
util.nextTick(function() {
process.exit(1);
});
});
};
/**

View File

@ -11,7 +11,6 @@ var util = require('../utils/util');
var BufferReader = require('../utils/reader');
var Script = require('../script/script');
var Witness = require('../script/witness');
var Coin = require('../primitives/coin');
var Output = require('../primitives/output');
/*
@ -171,9 +170,10 @@ ErrorResultPacket.fromRaw = function fromRaw(data) {
* @constructor
*/
function VerifyPacket(tx, flags) {
function VerifyPacket(tx, view, flags) {
Packet.call(this);
this.tx = tx || null;
this.view = view || null;
this.flags = flags != null ? flags : null;
}
@ -182,15 +182,17 @@ util.inherits(VerifyPacket, Packet);
VerifyPacket.prototype.cmd = packetTypes.VERIFY;
VerifyPacket.prototype.toWriter = function(bw) {
frameTX(this.tx, bw);
this.tx.toWriter(bw);
this.view.toPrevWriter(bw, this.tx);
bw.write32(this.flags != null ? this.flags : -1);
};
VerifyPacket.fromRaw = function fromRaw(TX, data) {
VerifyPacket.fromRaw = function fromRaw(TX, CoinView, data) {
var br = new BufferReader(data, true);
var packet = new VerifyPacket();
packet.tx = parseTX(TX, br);
packet.tx = TX.fromReader(br);
packet.view = CoinView.fromPrevReader(br, packet.tx);
packet.flags = br.read32();
@ -244,7 +246,8 @@ SignPacket.prototype.cmd = packetTypes.SIGN;
SignPacket.prototype.toWriter = function toWriter(bw) {
var i, ring;
frameTX(this.tx, bw);
this.tx.toWriter(bw);
this.tx.view.toPrevWriter(bw, this.tx);
bw.writeVarint(this.rings.length);
@ -261,7 +264,8 @@ SignPacket.fromRaw = function fromRaw(MTX, KeyRing, data) {
var packet = new SignPacket();
var i, count, ring;
packet.tx = parseTX(MTX, br);
packet.tx = MTX.fromReader(br);
packet.tx.view.fromPrevReader(br, packet.tx);
count = br.readVarint();
@ -747,53 +751,6 @@ ScryptResultPacket.fromRaw = function fromRaw(data) {
return packet;
};
/*
* Helpers
*/
function frameTX(tx, bw) {
var i, input;
tx.toWriter(bw);
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (!input.coin) {
bw.writeU8(0);
continue;
}
bw.writeU8(1);
bw.writeVarint(input.coin.value);
input.coin.script.toWriter(bw);
}
}
function parseTX(TX, br) {
var tx = TX.fromReader(br);
var i, input, prevout, coin;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
if (br.readU8() === 0)
continue;
coin = new Coin();
coin.value = br.readVarint();
coin.script.fromReader(br);
coin.hash = prevout.hash;
coin.index = prevout.index;
input.coin = coin;
}
return tx;
}
/*
* Expose
*/

View File

@ -13,6 +13,7 @@ var ServerParser = require('./parser');
var MTX = require('../primitives/mtx');
var TX = require('../primitives/tx');
var KeyRing = require('../primitives/keyring');
var CoinView = require('../blockchain/coinview');
/**
* Parser
@ -37,7 +38,7 @@ Parser.prototype.parsePacket = function parsePacket(header, data) {
case packets.types.ERROR:
return packets.ErrorPacket.fromRaw(data);
case packets.types.VERIFY:
return packets.VerifyPacket.fromRaw(TX, data);
return packets.VerifyPacket.fromRaw(TX, CoinView, data);
case packets.types.SIGN:
return packets.SignPacket.fromRaw(MTX, KeyRing, data);
case packets.types.VERIFYINPUT:

View File

@ -264,8 +264,8 @@ WorkerPool.prototype.execute = function execute(packet, timeout) {
* @returns {Promise} - Returns Boolean.
*/
WorkerPool.prototype.verify = co(function* verify(tx, flags) {
var packet = new packets.VerifyPacket(tx, flags);
WorkerPool.prototype.verify = co(function* verify(tx, view, flags) {
var packet = new packets.VerifyPacket(tx, view, flags);
var result = yield this.execute(packet, -1);
return result.value;
});

View File

@ -6,6 +6,8 @@ var util = bcoin.util;
var btcutils = require('../lib/btc/utils');
var crypto = require('../lib/crypto/crypto');
var Bloom = require('../lib/utils/bloom');
var CoinView = require('../lib/blockchain/coinview');
var Coin = require('../lib/primitives/coin');
var constants = bcoin.constants;
var network = bcoin.networks;
var assert = require('assert');
@ -143,6 +145,15 @@ describe('Block', function() {
});
it('should verify a historical block', function() {
var view = new CoinView();
for (var i = 1; i < block300025.txs.length; i++) {
var tx = block300025.txs[i];
for (var j = 0; j < tx.inputs.length; j++) {
var input = tx.inputs[j];
var coin = Coin.fromJSON(input.coin);
view.addCoin(coin);
}
}
assert(block.verify());
assert(block.txs[0].isCoinbase());
assert(block.txs[0].isSane());
@ -152,12 +163,13 @@ describe('Block', function() {
for (var i = 1; i < block.txs.length; i++) {
var tx = block.txs[i];
assert(tx.isSane());
assert(tx.checkInputs(block.height));
assert(tx.verify(flags));
assert(tx.checkInputs(view, block300025.height));
assert(tx.verify(view, flags));
assert(!tx.hasWitness());
view.addTX(tx, block300025.height);
}
assert.equal(block.getReward(), 2507773345);
assert.equal(block.getReward(), block.txs[0].outputs[0].value);
assert.equal(block.getReward(view), 2507773345);
assert.equal(block.getReward(view), block.txs[0].outputs[0].value);
});
it('should fail with a bad merkle root', function() {

View File

@ -9,10 +9,11 @@ var assert = require('assert');
var opcodes = constants.opcodes;
var co = require('../lib/utils/co');
var cob = co.cob;
var CoinView = require('../lib/blockchain/coinview');
// var Client = require('../lib/wallet/client');
describe('Chain', function() {
var chain, wallet, node, miner, walletdb;
var chain, wallet, node, miner, walletdb, mempool;
var tip1, tip2, cb1, cb2, mineBlock;
this.timeout(5000);
@ -20,6 +21,7 @@ describe('Chain', function() {
node = new bcoin.fullnode({ db: 'memory', apiKey: 'foo' });
// node.walletdb.client = new Client({ apiKey: 'foo', network: 'regtest' });
chain = node.chain;
mempool = node.mempool;
walletdb = node.walletdb;
walletdb.options.resolution = false;
miner = node.miner;
@ -50,7 +52,7 @@ describe('Chain', function() {
yield wallet.sign(redeemer);
attempt.addTX(redeemer.toTX());
attempt.addTX(redeemer.toTX(), redeemer.view);
return yield attempt.mineAsync();
});
@ -332,7 +334,7 @@ describe('Chain', function() {
yield wallet.sign(redeemer);
attempt.addTX(redeemer.toTX());
attempt.addTX(redeemer.toTX(), redeemer.view);
return yield attempt.mineAsync();
});
@ -361,7 +363,7 @@ describe('Chain', function() {
attempt = yield miner.createBlock();
attempt.addTX(redeemer.toTX());
attempt.addTX(redeemer.toTX(), redeemer.view);
block = yield attempt.mineAsync();
@ -387,7 +389,7 @@ describe('Chain', function() {
attempt = yield miner.createBlock();
attempt.addTX(redeemer.toTX());
attempt.addTX(redeemer.toTX(), redeemer.view);
block = yield attempt.mineAsync();
@ -431,7 +433,7 @@ describe('Chain', function() {
attempt = yield miner.createBlock();
attempt.addTX(redeemer.toTX());
attempt.addTX(redeemer.toTX(), redeemer.view);
block = yield attempt.mineAsync();

View File

@ -16,19 +16,7 @@ var dummyInput = {
prevout: {
hash: constants.NULL_HASH,
index: 0
},
coin: {
version: 1,
height: 0,
value: constants.MAX_MONEY,
script: new bcoin.script([]),
coinbase: false,
hash: constants.NULL_HASH,
index: 0
},
script: new bcoin.script([]),
witness: new bcoin.witness([]),
sequence: 0xffffffff
}
};
describe('HTTP', function() {

View File

@ -35,35 +35,28 @@ describe('Mempool', function() {
function dummy(prev, prevHash) {
var funding = bcoin.mtx();
var coin, entry;
if (!prevHash)
prevHash = constants.ONE_HASH.toString('hex');
funding.addInput({
prevout: {
hash: prevHash,
index: 0
},
coin: {
version: 1,
height: 0,
value: 0,
script: prev,
coinbase: false,
hash: prevHash,
index: 0
},
script: new bcoin.script(),
sequence: 0xffffffff
coin = new bcoin.coin({
version: 1,
height: 0,
value: 0,
script: prev,
coinbase: false,
hash: prevHash,
index: 0
});
funding.addInput(coin);
funding.addOutput({ value: 70000, script: prev });
funding = funding.toTX();
entry = MempoolEntry.fromTX(funding.toTX(), funding.view, 0);
var entry = MempoolEntry.fromTX(funding, 0);
mempool.trackEntry(entry);
mempool.trackEntry(entry, funding.view);
return bcoin.coin.fromTX(funding, 0);
}
@ -92,7 +85,7 @@ describe('Mempool', function() {
prev = new bcoin.script([kp.publicKey, opcodes.OP_CHECKSIG]);
t1.addInput(dummy(prev));
sig = t1.signature(0, prev, t1.inputs[0].coin.value, kp.privateKey, 'all', 0);
sig = t1.signature(0, prev, 70000, kp.privateKey, 'all', 0);
t1.inputs[0].script = new bcoin.script([sig]);
// balance: 51000
@ -148,12 +141,6 @@ describe('Mempool', function() {
fake = fake.toTX();
// balance: 11000
[t2, t3, t4, f1, fake].forEach(function(tx) {
tx.inputs.forEach(function(input) {
input.coin = null;
});
});
yield mempool.addTX(fake);
yield mempool.addTX(t4);
@ -203,7 +190,7 @@ describe('Mempool', function() {
chain.tip.height = 200;
sig = tx.signature(0, prev, tx.inputs[0].coin.value, kp.privateKey, 'all', 0);
sig = tx.signature(0, prev, 70000, kp.privateKey, 'all', 0);
tx.inputs[0].script = new bcoin.script([sig]),
tx = tx.toTX();
@ -228,7 +215,7 @@ describe('Mempool', function() {
tx.setLocktime(200);
chain.tip.height = 200 - 1;
sig = tx.signature(0, prev, tx.inputs[0].coin.value, kp.privateKey, 'all', 0);
sig = tx.signature(0, prev, 70000, kp.privateKey, 'all', 0);
tx.inputs[0].script = new bcoin.script([sig]),
tx = tx.toTX();
@ -261,7 +248,7 @@ describe('Mempool', function() {
prevs = bcoin.script.fromPubkeyhash(kp.getKeyHash());
sig = tx.signature(0, prevs, tx.inputs[0].coin.value, kp.privateKey, 'all', 1);
sig = tx.signature(0, prevs, 70000, kp.privateKey, 'all', 1);
sig[sig.length - 1] = 0;
tx.inputs[0].witness = new bcoin.witness([sig, kp.publicKey]);
tx = tx.toTX();
@ -290,7 +277,7 @@ describe('Mempool', function() {
tx.addInput(dummy(prev, prevHash));
sig = tx.signature(0, prev, tx.inputs[0].coin.value, kp.privateKey, 'all', 0);
sig = tx.signature(0, prev, 70000, kp.privateKey, 'all', 0);
tx.inputs[0].script = new bcoin.script([sig]);
tx.inputs[0].witness.push(new Buffer(0));
tx = tx.toTX();
@ -373,10 +360,7 @@ describe('Mempool', function() {
prevout: {
hash: constants.NULL_HASH,
index: 0xffffffff
},
coin: null,
script: new bcoin.script(),
sequence: 0xffffffff
}
};
tx.addInput(input);

View File

@ -279,6 +279,8 @@ describe('Script', function() {
var expected = data[3] || '';
var comments = Array.isArray(data[4]) ? data[4].join('. ') : data[4] || '';
var amount = 0;
var flag = 0;
var i, name, err, res;
if (data.length === 1)
return;
@ -295,16 +297,21 @@ describe('Script', function() {
input = bcoin.script.fromString(input);
output = bcoin.script.fromString(output);
var flag = 0;
for (var i = 0; i < flags.length; i++) {
flag |= constants.flags['VERIFY_' + flags[i]];
for (i = 0; i < flags.length; i++) {
name = 'VERIFY_' + flags[i];
assert(constants.flags[name] != null, 'Unknown flag.');
flag |= constants.flags[name];
}
flags = flag;
[false, true].forEach(function(nocache) {
var suffix = nocache ? ' without cache' : ' with cache';
it('should handle script test' + suffix + ': ' + comments, function() {
var coin = bcoin.tx({
var prev, tx, err, res;
// Funding transaction.
prev = bcoin.tx({
version: 1,
flag: 1,
inputs: [{
@ -312,7 +319,6 @@ describe('Script', function() {
hash: constants.NULL_HASH,
index: 0xffffffff
},
coin: null,
script: [bcoin.script.array(0), bcoin.script.array(0)],
witness: new bcoin.witness(),
sequence: 0xffffffff
@ -323,15 +329,16 @@ describe('Script', function() {
}],
locktime: 0
});
var tx = bcoin.tx({
// Spending transaction.
tx = bcoin.tx({
version: 1,
flag: 1,
inputs: [{
prevout: {
hash: coin.hash('hex'),
hash: prev.hash('hex'),
index: 0
},
coin: bcoin.coin.fromTX(coin, 0),
script: input,
witness: witness,
sequence: 0xffffffff
@ -342,11 +349,11 @@ describe('Script', function() {
}],
locktime: 0
});
if (nocache) {
tx._raw = null;
tx._size = -1;
tx._witnessSize = -1;
tx._lastWitnessSize = 0;
tx._hash = null;
tx._hhash = null;
tx._whash = null;
@ -356,33 +363,30 @@ describe('Script', function() {
tx._hashSequence = null;
tx._hashOutputs = null;
coin._raw = null;
coin._size = -1;
coin._witnessSize = -1;
coin._lastWitnessSize = 0;
coin._hash = null;
coin._inputValue = -1;
coin._outputValue = -1;
coin._hashPrevouts = null;
coin._hashSequence = null;
coin._hashOutputs = null;
delete input._address;
delete output._address;
prev._raw = null;
prev._size = -1;
prev._witnessSize = -1;
prev._hash = null;
prev._inputValue = -1;
prev._outputValue = -1;
prev._hashPrevouts = null;
prev._hashSequence = null;
prev._hashOutputs = null;
}
var err, res;
var value = tx.inputs[0].coin.value;
try {
res = Script.verify(input, witness, output, tx, 0, value, flags);
res = Script.verify(input, witness, output, tx, 0, amount, flags);
} catch (e) {
err = e;
}
if (expected !== 'OK') {
assert(!res);
assert(err);
assert.equal(err.code, expected);
return;
}
assert.ifError(err);
assert(res);
});

View File

@ -12,6 +12,7 @@ var valid = require('./data/tx_valid.json');
var invalid = require('./data/tx_invalid.json');
var sighash = require('./data/sighash.json');
var fs = require('fs');
var CoinView = require('../lib/blockchain/coinview');
var tx1 = parseTX('data/tx1.hex');
var tx2 = parseTX('data/tx2.hex');
var tx3 = parseTX('data/tx3.hex');
@ -20,24 +21,23 @@ var wtx = parseTX('data/wtx.hex');
var coolest = parseTX('data/coolest-tx-ever-sent.hex');
function parseTX(file) {
file = fs.readFileSync(__dirname + '/' + file, 'utf8').trim().split(/\n+/);
var tx = bcoin.tx.fromRaw(file.shift().trim(), 'hex');
for (var i = 0; i < file.length; i++) {
var coin = bcoin.tx.fromRaw(file[i].trim(), 'hex');
tx.fillCoins(coin);
var data = fs.readFileSync(__dirname + '/' + file, 'utf8');
var parts = data.trim().split(/\n+/);
var raw = parts[0];
var tx = bcoin.tx.fromRaw(raw.trim(), 'hex');
var view = new CoinView();
var i, prev;
for (i = 1; i < parts.length; i++) {
raw = parts[i];
prev = bcoin.tx.fromRaw(raw.trim(), 'hex');
view.addTX(prev, -1);
}
return tx;
return { tx: tx, view: view };
}
function clearCache(tx, nocache) {
var i, input, output;
if (tx instanceof bcoin.script) {
if (!nocache)
return;
return;
}
if (!nocache) {
assert.equal(tx.hash('hex'), tx.clone().hash('hex'));
return;
@ -46,7 +46,6 @@ function clearCache(tx, nocache) {
tx._raw = null;
tx._size = -1;
tx._witnessSize = -1;
tx._lastWitnessSize = 0;
tx._hash = null;
tx._hhash = null;
tx._whash = null;
@ -55,16 +54,6 @@ function clearCache(tx, nocache) {
tx._hashPrevouts = null;
tx._hashSequence = null;
tx._hashOutputs = null;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
input._address = null;
}
for (i = 0; i < tx.outputs.length; i++) {
output = tx.outputs[i];
output._address = null;
}
}
describe('TX', function() {
@ -117,69 +106,83 @@ describe('TX', function() {
it('should be verifiable' + suffix, function() {
var tx = bcoin.tx.fromRaw(raw, 'hex');
var p = bcoin.tx.fromRaw(inp, 'hex');
tx.fillCoins(p);
var view = new CoinView();
view.addTX(p, -1);
clearCache(tx, nocache);
clearCache(p, nocache);
assert(tx.verify());
assert(tx.verify(view));
});
it('should verify non-minimal output' + suffix, function() {
clearCache(tx1, nocache);
assert(tx1.verify(constants.flags.VERIFY_P2SH));
clearCache(tx1.tx, nocache);
assert(tx1.tx.verify(tx1.view, constants.flags.VERIFY_P2SH));
});
it('should verify tx.version == 0' + suffix, function() {
clearCache(tx2, nocache);
assert(tx2.verify(constants.flags.VERIFY_P2SH));
clearCache(tx2.tx, nocache);
assert(tx2.tx.verify(tx2.view, constants.flags.VERIFY_P2SH));
});
it('should verify sighash_single bug w/ findanddelete' + suffix, function() {
clearCache(tx3, nocache);
assert(tx3.verify(constants.flags.VERIFY_P2SH));
clearCache(tx3.tx, nocache);
assert(tx3.tx.verify(tx3.view, constants.flags.VERIFY_P2SH));
});
it('should verify high S value with only DERSIG enabled' + suffix, function() {
var coin = tx4.inputs[0].coin;
clearCache(tx4, nocache);
assert(tx4.verifyInput(0, coin, constants.flags.VERIFY_P2SH | constants.flags.VERIFY_DERSIG));
var coin = tx4.view.getOutput(tx4.tx.inputs[0]);
var flags = constants.flags.VERIFY_P2SH | constants.flags.VERIFY_DERSIG;
clearCache(tx4.tx, nocache);
assert(tx4.tx.verifyInput(0, coin, flags));
});
it('should verify the coolest tx ever sent' + suffix, function() {
clearCache(coolest, nocache);
assert(coolest.verify(constants.flags.VERIFY_NONE));
clearCache(coolest.tx, nocache);
assert(coolest.tx.verify(coolest.view, constants.flags.VERIFY_NONE));
});
it('should parse witness tx properly' + suffix, function() {
clearCache(wtx, nocache);
assert.equal(wtx.inputs.length, 5);
assert.equal(wtx.outputs.length, 1980);
assert(wtx.hasWitness());
assert.notEqual(wtx.hash('hex'), wtx.witnessHash('hex'));
assert.equal(wtx.witnessHash('hex'),
var raw1, raw2, wtx2;
clearCache(wtx.tx, nocache);
assert.equal(wtx.tx.inputs.length, 5);
assert.equal(wtx.tx.outputs.length, 1980);
assert(wtx.tx.hasWitness());
assert.notEqual(wtx.tx.hash('hex'), wtx.tx.witnessHash('hex'));
assert.equal(wtx.tx.witnessHash('hex'),
'088c919cd8408005f255c411f786928385688a9e8fdb2db4c9bc3578ce8c94cf');
assert.equal(wtx.getSize(), 62138);
assert.equal(wtx.getVirtualSize(), 61813);
assert.equal(wtx.getWeight(), 247250);
var raw1 = wtx.toRaw();
clearCache(wtx, true);
var raw2 = wtx.toRaw();
assert.equal(wtx.tx.getSize(), 62138);
assert.equal(wtx.tx.getVirtualSize(), 61813);
assert.equal(wtx.tx.getWeight(), 247250);
raw1 = wtx.tx.toRaw();
clearCache(wtx.tx, true);
raw2 = wtx.tx.toRaw();
assert.deepEqual(raw1, raw2);
var wtx2 = bcoin.tx.fromRaw(raw2);
wtx2 = bcoin.tx.fromRaw(raw2);
clearCache(wtx2, nocache);
assert.equal(wtx.hash('hex'), wtx2.hash('hex'));
assert.equal(wtx.witnessHash('hex'), wtx2.witnessHash('hex'));
assert.equal(wtx.tx.hash('hex'), wtx2.hash('hex'));
assert.equal(wtx.tx.witnessHash('hex'), wtx2.witnessHash('hex'));
});
function parseTest(data) {
var coins = data[0];
var tx = bcoin.tx.fromRaw(data[1], 'hex');
var flags = data[2] ? data[2].trim().split(/,\s*/) : [];
var view = new CoinView();
var flag = 0;
var i, name;
for (var i = 0; i < flags.length; i++)
flag |= constants.flags['VERIFY_' + flags[i]];
for (i = 0; i < flags.length; i++) {
name = 'VERIFY_' + flags[i];
assert(constants.flags[name] != null, 'Unknown flag.');
flag |= constants.flags[name];
}
flags = flag;
@ -188,7 +191,9 @@ describe('TX', function() {
var index = data[1];
var script = bcoin.script.fromString(data[2]);
var value = data[3];
var coin = new bcoin.coin({
var coin;
coin = new bcoin.coin({
version: 1,
height: -1,
coinbase: false,
@ -197,14 +202,17 @@ describe('TX', function() {
script: script,
value: value != null ? parseInt(value, 10) : 0
});
tx.fillCoins(coin);
if (index !== -1)
view.addCoin(coin);
});
return {
tx: tx,
flags: flags,
comments: tx.hasCoins()
? util.inspectify(tx.inputs[0].coin.script, false)
view: view,
comments: tx.hasCoins(view)
? util.inspectify(view.getOutput(tx.inputs[0]).script, false)
: 'coinbase',
data: data
};
@ -215,24 +223,30 @@ describe('TX', function() {
var arr = test[0];
var valid = test[1];
var comment = '';
arr.forEach(function(json, i) {
var data, tx, view, flags, comments;
if (json.length === 1) {
comment += ' ' + json[0];
return;
}
var data = parseTest(json);
data = parseTest(json);
if (!data) {
comment = '';
return;
}
var tx = data.tx;
var flags = data.flags;
var comments = comment.trim();
tx = data.tx;
view = data.view;
flags = data.flags;
comments = comment.trim();
if (!comments)
comments = data.comments;
comment = '';
if (valid) {
@ -245,13 +259,13 @@ describe('TX', function() {
}
it('should handle valid tx test' + suffix + ': ' + comments, function() {
clearCache(tx, nocache);
assert.ok(tx.verify(flags));
assert.ok(tx.verify(view, flags));
});
} else {
if (comments === 'Duplicate inputs') {
it('should handle duplicate input test' + suffix + ': ' + comments, function() {
clearCache(tx, nocache);
assert.ok(tx.verify(flags));
assert.ok(tx.verify(view, flags));
assert.ok(!tx.isSane());
});
return;
@ -259,7 +273,7 @@ describe('TX', function() {
if (comments === 'Negative output') {
it('should handle invalid tx (negative)' + suffix + ': ' + comments, function() {
clearCache(tx, nocache);
assert.ok(tx.verify(flags));
assert.ok(tx.verify(view, flags));
assert.ok(!tx.isSane());
});
return;
@ -273,29 +287,38 @@ describe('TX', function() {
}
it('should handle invalid tx test' + suffix + ': ' + comments, function() {
clearCache(tx, nocache);
assert.ok(!tx.verify(flags));
assert.ok(!tx.verify(view, flags));
});
}
});
});
sighash.forEach(function(data) {
var tx, script, index, type, expected, hexType;
// ["raw_transaction, script, input_index, hashType, signature_hash (result)"],
if (data.length === 1)
return;
var tx = bcoin.tx.fromRaw(data[0], 'hex');
tx = bcoin.tx.fromRaw(data[0], 'hex');
clearCache(tx, nocache);
var script = bcoin.script.fromRaw(data[1], 'hex');
clearCache(script, nocache);
var index = data[2];
var type = data[3];
var expected = util.revHex(data[4]);
var hexType = type & 3;
script = bcoin.script.fromRaw(data[1], 'hex');
index = data[2];
type = data[3];
expected = util.revHex(data[4]);
hexType = type & 3;
if (type & 0x80)
hexType |= 0x80;
hexType = hexType.toString(16);
if (hexType.length % 2 !== 0)
hexType = '0' + hexType;
it('should get signature hash of ' + data[4] + ' (' + hexType + ')' + suffix, function() {
var subscript = script.getSubscript(0).removeSeparators();
var hash = tx.signatureHash(index, subscript, 0, type, 0).toString('hex');
@ -338,7 +361,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should handle 51 bit coin values', function() {
@ -353,7 +376,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(tx.checkInputs(0));
// assert.ok(tx.checkInputs(view, 0));
});
it('should fail on >51 bit output values', function() {
@ -368,7 +391,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(!tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should handle 51 bit output values', function() {
@ -383,7 +406,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(tx.checkInputs(0));
// assert.ok(tx.checkInputs(view, 0));
});
it('should fail on >51 bit fees', function() {
@ -398,7 +421,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on >51 bit values from multiple', function() {
@ -417,7 +440,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on >51 bit output values from multiple', function() {
@ -442,7 +465,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(!tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on >51 bit fees from multiple', function() {
@ -461,7 +484,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on >51 bit fees from multiple txs', function() {
@ -482,7 +505,7 @@ describe('TX', function() {
});
block.txs.push(tx);
}
assert.equal(block.getReward(), -1);
// assert.equal(block.getReward(), -1);
});
it('should fail to parse >53 bit values', function() {
@ -502,7 +525,7 @@ describe('TX', function() {
assert(encoding.readU64(raw, 47) === 0xdeadbeef);
raw[54] = 0x7f;
assert.throws(function() {
console.log(bcoin.tx.fromRaw(raw));
bcoin.tx.fromRaw(raw);
});
tx._raw = null;
tx.outputs[0].value = 0;
@ -510,7 +533,7 @@ describe('TX', function() {
assert(encoding.readU64(raw, 47) === 0x00);
raw[54] = 0x80;
assert.throws(function() {
console.log(bcoin.tx.fromRaw(raw));
bcoin.tx.fromRaw(raw);
});
});
@ -526,7 +549,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on 53 bit output values', function() {
@ -541,7 +564,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(!tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on 53 bit fees', function() {
@ -556,7 +579,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
[util.MAX_SAFE_ADDITION, util.MAX_SAFE_INTEGER].forEach(function(MAX) {
@ -576,7 +599,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on >53 bit output values from multiple', function() {
@ -601,7 +624,7 @@ describe('TX', function() {
locktime: 0
});
assert.ok(!tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on >53 bit fees from multiple', function() {
@ -620,14 +643,16 @@ describe('TX', function() {
locktime: 0
});
assert.ok(tx.isSane());
assert.ok(!tx.checkInputs(0));
// assert.ok(!tx.checkInputs(view, 0));
});
it('should fail on >53 bit fees from multiple txs', function() {
var data = util.merge({}, bcoin.network.get().genesis, { height: 0 });
var block = new bcoin.block(data);
for (var i = 0; i < 3; i++) {
var tx = bcoin.tx({
var genesis = bcoin.network.get().genesis;
var block = new bcoin.block(genesis);
var i, tx;
for (i = 0; i < 3; i++) {
tx = bcoin.tx({
version: 1,
flag: 1,
inputs: [
@ -641,7 +666,8 @@ describe('TX', function() {
});
block.txs.push(tx);
}
assert.equal(block.getReward(), -1);
// assert.equal(block.getReward(view), -1);
});
});
});

View File

@ -42,24 +42,14 @@ function nextBlock(height) {
}
function dummy(hash) {
hash = hash || crypto.randomBytes(32).toString('hex');
if (!hash)
hash = crypto.randomBytes(32).toString('hex');
return {
prevout: {
hash: hash,
index: 0
},
coin: {
version: 1,
height: 0,
value: constants.MAX_MONEY,
script: new bcoin.script(),
coinbase: false,
hash: hash,
index: 0
},
script: new bcoin.script(),
witness: new bcoin.witness(),
sequence: 0xffffffff
}
};
}
@ -218,7 +208,7 @@ describe('Wallet', function() {
t1.ts = util.now();
// balance: 51000
yield w.sign(t1);
// yield w.sign(t1);
t1 = t1.toTX();
t2 = bcoin.mtx()
@ -226,7 +216,7 @@ describe('Wallet', function() {
.addOutput(w.getAddress(), 24000)
.addOutput(w.getAddress(), 24000);
doubleSpend = t2.inputs[0];
doubleSpend = bcoin.coin.fromTX(t1, 0);
// balance: 49000
yield w.sign(t2);
@ -318,7 +308,7 @@ describe('Wallet', function() {
var tx, txs, total, balance;
tx = bcoin.mtx().addOutput(w.getAddress(), 5000);
tx.addInput(doubleSpend.coin);
tx.addInput(doubleSpend);
txs = yield w.getHistory();
assert.equal(txs.length, 5);
@ -370,7 +360,7 @@ describe('Wallet', function() {
t1.addInput(dummy());
// balance: 51000
yield w.sign(t1);
// yield w.sign(t1);
t1 = t1.toTX();
t2 = bcoin.mtx()
@ -480,7 +470,7 @@ describe('Wallet', function() {
it('should fill tx with inputs', cob(function* () {
var w1 = yield walletdb.create();
var w2 = yield walletdb.create();
var t1, t2, t3, err;
var view, t1, t2, t3, err;
// Coinbase
t1 = bcoin.mtx()
@ -498,17 +488,18 @@ describe('Wallet', function() {
t2 = bcoin.mtx().addOutput(w2.getAddress(), 5460);
yield w1.fund(t2, { rate: 10000, round: true });
yield w1.sign(t2);
view = t2.view;
t2 = t2.toTX();
assert(t2.verify());
assert(t2.verify(view));
assert.equal(t2.getInputValue(), 16380);
assert.equal(t2.getInputValue(view), 16380);
// assert.equal(t2.getOutputValue(), 5460); // minrelay=10000
// assert.equal(t2.getFee(), 10920); // minrelay=10000
// assert.equal(t2.getFee(view), 10920); // minrelay=10000
assert.equal(t2.getOutputValue(), 6380); // minrelay=1000
assert.equal(t2.getFee(), 10000); // minrelay=1000
assert.equal(t2.getFee(view), 10000); // minrelay=1000
// Create new transaction
t3 = bcoin.mtx().addOutput(w2.getAddress(), 15000);
@ -526,7 +517,7 @@ describe('Wallet', function() {
it('should fill tx with inputs with accurate fee', cob(function* () {
var w1 = yield walletdb.create({ master: KEY1 });
var w2 = yield walletdb.create({ master: KEY2 });
var t1, t2, t3, balance, err;
var view, t1, t2, t3, balance, err;
// Coinbase
t1 = bcoin.mtx()
@ -545,15 +536,16 @@ describe('Wallet', function() {
yield w1.fund(t2, { rate: 10000 });
yield w1.sign(t2);
view = t2.view;
t2 = t2.toTX();
assert(t2.verify());
assert(t2.verify(view));
assert.equal(t2.getInputValue(), 16380);
assert.equal(t2.getInputValue(view), 16380);
// Should now have a change output:
assert.equal(t2.getOutputValue(), 11130);
assert.equal(t2.getFee(), 5250);
assert.equal(t2.getFee(view), 5250);
assert.equal(t2.getWeight(), 2084);
assert.equal(t2.getBaseSize(), 521);
@ -666,6 +658,7 @@ describe('Wallet', function() {
var multisig = co(function* multisig(witness, bullshitNesting, cb) {
var flags = bcoin.constants.flags.STANDARD_VERIFY_FLAGS;
var options, w1, w2, w3, receive, b58, addr, paddr, utx, send, change;
var view;
var rec = bullshitNesting ? 'nested' : 'receive';
var depth = bullshitNesting ? 'nestedDepth' : 'receiveDepth';
@ -758,8 +751,9 @@ describe('Wallet', function() {
yield w2.sign(send);
view = send.view;
send = send.toTX();
assert(send.verify(flags));
assert(send.verify(view, flags));
assert.equal(w1.account.changeDepth, 1);
@ -794,8 +788,8 @@ describe('Wallet', function() {
send.inputs[0].script.compile();
}
assert(!send.verify(flags));
assert.equal(send.getFee(), 10000);
assert(!send.verify(view, flags));
assert.equal(send.getFee(view), 10000);
});
it('should verify 2-of-3 scripthash tx', cob(function* () {
@ -1224,7 +1218,7 @@ describe('Wallet', function() {
.addOutput(addr, 50000);
t1.addInput(dummy());
yield alice.sign(t1);
// yield alice.sign(t1);
t1 = t1.toTX();
yield alice.add(t1);
@ -1291,7 +1285,7 @@ describe('Wallet', function() {
.addOutput(addr, 50000);
t1.addInput(dummy());
yield alice.sign(t1);
// yield alice.sign(t1);
t1 = t1.toTX();
yield alice.add(t1);