From b77aa9240e1fa0c3aa6f7e5ea775ca19cb4ec8ed Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 10 Dec 2016 19:42:46 -0800 Subject: [PATCH] coins: refactor and comments. --- lib/blockchain/chain.js | 16 +- lib/blockchain/chaindb.js | 8 +- lib/blockchain/coins.js | 117 +++++++++--- lib/blockchain/coinview.js | 361 ++++++++++++++++++++++++++++-------- lib/blockchain/undocoins.js | 7 +- lib/http/server.js | 26 +-- lib/mempool/mempool.js | 35 ++-- lib/primitives/block.js | 23 ++- lib/primitives/coin.js | 54 +++++- lib/primitives/input.js | 25 ++- lib/primitives/mtx.js | 25 +-- lib/primitives/outpoint.js | 49 +++++ lib/primitives/output.js | 13 +- lib/primitives/tx.js | 74 +++++--- lib/wallet/txdb.js | 20 +- lib/wallet/wallet.js | 2 +- lib/workers/jobs.js | 1 + lib/workers/packets.js | 8 +- lib/workers/workerpool.js | 1 + scripts/dump.js | 28 +-- scripts/gen.js | 46 ++--- 21 files changed, 690 insertions(+), 249 deletions(-) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 1a34dadd..706a48db 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -237,8 +237,8 @@ Chain.prototype._close = function close() { * Perform all necessary contextual verification on a block. * @private * @param {Block|MerkleBlock} block - * @param {ChainEntry} entry - * @returns {Promise} + * @param {ChainEntry} prev + * @returns {Promise} - Returns {@link ContextResult}. */ Chain.prototype.verifyContext = co(function* verifyContext(block, prev) { @@ -608,7 +608,7 @@ Chain.prototype.verifyDuplicates = co(function* verifyDuplicates(block, prev, st * @param {Block} block * @param {ChainEntry} prev * @param {DeploymentState} state - * @returns {Promise} + * @returns {Promise} - Returns {@link CoinView}. */ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { @@ -2244,9 +2244,10 @@ Chain.prototype.verifyFinal = co(function* verifyFinal(prev, tx, flags) { /** * Get the necessary minimum time and height sequence locks for a transaction. - * @param {TX} tx - * @param {LockFlags} flags * @param {ChainEntry} prev + * @param {TX} tx + * @param {CoinView} view + * @param {LockFlags} flags * @returns {Promise} * [Error, Number(minTime), Number(minHeight)]. */ @@ -2295,9 +2296,10 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, view, flags) { /** * Verify sequence locks. - * @param {TX} tx - * @param {LockFlags} flags * @param {ChainEntry} prev + * @param {TX} tx + * @param {CoinView} view + * @param {LockFlags} flags * @returns {Promise} - Returns Boolean. */ diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index dd118478..e698b1a5 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -937,9 +937,9 @@ ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) { }); /** - * Get a block and fill it with coins (historical). - * @param {Hash} hash - * @returns {Promise} - Returns {@link Block}. + * Get a historical block coin viewpoint. + * @param {Block} hash + * @returns {Promise} - Returns {@link CoinView}. */ ChainDB.prototype.getBlockView = co(function* getBlockView(block) { @@ -1789,6 +1789,7 @@ ChainDB.prototype.saveOptions = function saveOptions() { * Index a transaction by txid and address. * @private * @param {TX} tx + * @param {CoinView} view */ ChainDB.prototype.indexTX = function indexTX(tx, view) { @@ -1838,6 +1839,7 @@ ChainDB.prototype.indexTX = function indexTX(tx, view) { * Remove transaction from index. * @private * @param {TX} tx + * @param {CoinView} view */ ChainDB.prototype.unindexTX = function unindexTX(tx, view) { diff --git a/lib/blockchain/coins.js b/lib/blockchain/coins.js index 6d81d3d0..a1a7ff42 100644 --- a/lib/blockchain/coins.js +++ b/lib/blockchain/coins.js @@ -19,15 +19,14 @@ var decompress = compressor.decompress; /** * Represents the outputs for a single transaction. - * @exports Coins * @constructor - * @param {TX|Object} tx/options - TX or options object. + * @param {Object?} options - Options object. * @property {Hash} hash - Transaction hash. * @property {Number} version - Transaction version. * @property {Number} height - Transaction height (-1 if unconfirmed). * @property {Boolean} coinbase - Whether the containing * transaction is a coinbase. - * @property {Coin[]} outputs - Coins. + * @property {CoinEntry[]} outputs - Coins. */ function Coins(options) { @@ -52,7 +51,7 @@ function Coins(options) { Coins.prototype.fromOptions = function fromOptions(options) { if (options.version != null) { - assert(util.isNumber(options.version)); + assert(util.isUInt32(options.version)); this.version = options.version; } @@ -74,6 +73,7 @@ Coins.prototype.fromOptions = function fromOptions(options) { if (options.outputs) { assert(Array.isArray(options.outputs)); this.outputs = options.outputs; + this.cleanup(); } return this; @@ -96,6 +96,8 @@ Coins.fromOptions = function fromOptions(options) { */ Coins.prototype.add = function add(index, entry) { + assert(index >= 0); + while (this.outputs.length <= index) this.outputs.push(null); @@ -139,7 +141,8 @@ Coins.prototype.has = function has(index) { }; /** - * Test whether the collection has a coin. + * Test whether the collection + * has an unspent coin. * @param {Number} index * @returns {Boolean} */ @@ -202,7 +205,7 @@ Coins.prototype.getCoin = function getCoin(index) { }; /** - * Remove a coin entry and return it. + * Spend a coin entry and return it. * @param {Number} index * @returns {CoinEntry} */ @@ -219,7 +222,28 @@ Coins.prototype.spend = function spend(index) { }; /** - * Cleanup spent outputs. + * Remove a coin entry and return it. + * @param {Number} index + * @returns {CoinEntry} + */ + +Coins.prototype.remove = function remove(index) { + var entry; + + if (index >= this.outputs.length) + return false; + + entry = this.outputs[index]; + + this.outputs[index] = null; + this.cleanup(); + + return entry; +}; + +/** + * Calculate unspent length of coins. + * @returns {Number} */ Coins.prototype.length = function length() { @@ -232,7 +256,7 @@ Coins.prototype.length = function length() { }; /** - * Cleanup spent outputs. + * Cleanup spent outputs (remove pruned). */ Coins.prototype.cleanup = function cleanup() { @@ -284,12 +308,12 @@ Coins.prototype.isEmpty = function isEmpty() { */ /** - * Serialize the coins object. - * @returns {Buffer} + * Write the coins object to writer. + * @param {BufferWriter} bw + * @returns {BufferWriter} */ -Coins.prototype.toRaw = function toRaw() { - var bw = new BufferWriter(); +Coins.prototype.toWriter = function toWriter(bw) { var len = this.length(); var size = Math.floor((len + 5) / 8); var first = this.isUnspent(0); @@ -344,19 +368,27 @@ Coins.prototype.toRaw = function toRaw() { output.toWriter(bw); } - return bw.render(); + return bw; }; /** - * Inject data from serialized coins. + * Serialize the coins object. + * @returns {Buffer} + */ + +Coins.prototype.toRaw = function toRaw() { + return this.toWriter(new BufferWriter()).render(); +}; + +/** + * Inject data from buffer reader. * @private - * @param {Buffer} data + * @param {BufferReader} br * @param {Hash} hash * @returns {Coins} */ -Coins.prototype.fromRaw = function fromRaw(data, hash) { - var br = new BufferReader(data); +Coins.prototype.fromReader = function fromReader(br, hash) { var first = null; var second = null; var i, j, code, size, offset, ch; @@ -407,6 +439,18 @@ Coins.prototype.fromRaw = function fromRaw(data, hash) { return this; }; +/** + * Inject data from serialized coins. + * @private + * @param {Buffer} data + * @param {Hash} hash + * @returns {Coins} + */ + +Coins.prototype.fromRaw = function fromRaw(data, hash) { + return this.fromReader(new BufferReader(data), hash); +}; + /** * Parse a single serialized coin. * @param {Buffer} data @@ -478,7 +522,18 @@ Coins.parseCoin = function parseCoin(data, hash, index) { }; /** - * Instantiate coins from a serialized Buffer. + * Instantiate coins from a buffer reader. + * @param {Buffer} data + * @param {Hash} hash - Transaction hash. + * @returns {Coins} + */ + +Coins.fromReader = function fromReader(br, hash) { + return new Coins().fromReader(br, hash); +}; + +/** + * Instantiate coins from a buffer. * @param {Buffer} data * @param {Hash} hash - Transaction hash. * @returns {Coins} @@ -533,22 +588,24 @@ Coins.fromTX = function fromTX(tx, height) { }; /** - * A compressed coin is an object which defers + * A coin entry is an object which defers * parsing of a coin. Say there is a transaction * with 100 outputs. When a block comes in, * there may only be _one_ input in that entire * block which redeems an output from that * transaction. When parsing the Coins, there * is no sense to get _all_ of them into their - * abstract form. A compressed coin is just a + * abstract form. A coin entry is just a * pointer to that coin in the Coins buffer, as - * well as a size. Parsing is done only if that - * coin is being redeemed. + * well as a size. Parsing and decompression + * is done only if that coin is being redeemed. * @constructor * @private - * @param {Number} offset - * @param {Number} size - * @param {Buffer} raw + * @property {Number} offset + * @property {Number} size + * @property {Buffer} raw + * @property {Output|null} output + * @property {Boolean} spent */ function CoinEntry() { @@ -577,7 +634,7 @@ CoinEntry.prototype.reader = function reader() { }; /** - * Parse the deferred data and return a Coin. + * Parse the deferred data and return a coin. * @param {Coins} coins * @param {Number} index * @returns {Coin} @@ -601,7 +658,7 @@ CoinEntry.prototype.toCoin = function toCoin(coins, index) { }; /** - * Parse the deferred data and return an Output. + * Parse the deferred data and return an output. * @returns {Output} */ @@ -639,7 +696,7 @@ CoinEntry.prototype.toWriter = function toWriter(bw) { }; /** - * Instantiate compressed coin from reader. + * Instantiate coin entry from reader. * @param {BufferReader} br * @returns {CoinEntry} */ @@ -653,7 +710,7 @@ CoinEntry.fromReader = function fromReader(br) { }; /** - * Instantiate compressed coin from output. + * Instantiate coin entry from output. * @param {Output} output * @returns {CoinEntry} */ @@ -665,7 +722,7 @@ CoinEntry.fromOutput = function fromOutput(output) { }; /** - * Instantiate compressed coin from coin. + * Instantiate coin entry from coin. * @param {Coin} coin * @returns {CoinEntry} */ diff --git a/lib/blockchain/coinview.js b/lib/blockchain/coinview.js index 86842f09..dfdfee8e 100644 --- a/lib/blockchain/coinview.js +++ b/lib/blockchain/coinview.js @@ -1,5 +1,5 @@ /*! - * coinview.js - coinview object for bcoin + * coinview.js - coin viewpoint object for bcoin * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ @@ -12,20 +12,22 @@ var Coins = require('./coins'); var UndoCoins = require('./undocoins'); var BufferReader = require('../utils/reader'); var BufferWriter = require('../utils/writer'); +var CoinEntry = Coins.CoinEntry; /** - * A collections of {@link Coins} objects. - * @exports CoinView + * A collection of {@link Coins} objects. * @constructor - * @param {Object} coins - A hash-to-coins map. - * @property {Object} coins + * @param {Object} map - A hash-to-coins map. + * @param {UndoCoins} undo - Spent coins. + * @property {Object} map + * @property {UndoCoins} undo */ function CoinView() { if (!(this instanceof CoinView)) return new CoinView(); - this.unspent = {}; + this.map = {}; this.undo = new UndoCoins(); } @@ -36,7 +38,7 @@ function CoinView() { */ CoinView.prototype.get = function get(hash) { - return this.unspent[hash]; + return this.map[hash]; }; /** @@ -46,7 +48,7 @@ CoinView.prototype.get = function get(hash) { */ CoinView.prototype.has = function has(hash) { - return this.unspent[hash] != null; + return this.map[hash] != null; }; /** @@ -55,10 +57,25 @@ CoinView.prototype.has = function has(hash) { */ CoinView.prototype.add = function add(coins) { - this.unspent[coins.hash] = coins; + this.map[coins.hash] = coins; return coins; }; +/** + * Remove coins from the collection. + * @param {Coins} coins + * @returns {Boolean} + */ + +CoinView.prototype.remove = function remove(hash) { + if (!this.map[hash]) + return false; + + delete this.map[hash]; + + return true; +}; + /** * Add a tx to the collection. * @param {TX} tx @@ -83,9 +100,8 @@ CoinView.prototype.removeTX = function removeTX(tx, height) { }; /** - * Add a tx to the collection. - * @param {TX} tx - * @param {Number} height + * Add a coin to the collection. + * @param {Coin} coin */ CoinView.prototype.addCoin = function addCoin(coin) { @@ -104,9 +120,10 @@ CoinView.prototype.addCoin = function addCoin(coin) { }; /** - * Add a tx to the collection. - * @param {TX} tx - * @param {Number} height + * Add an output to the collection. + * @param {Hash} hash + * @param {Number} index + * @param {Output} output */ CoinView.prototype.addOutput = function addOutput(hash, index, output) { @@ -125,10 +142,42 @@ CoinView.prototype.addOutput = function addOutput(hash, index, output) { }; /** - * Remove a coin and return it. + * Spend an output. + * @param {Hash} hash + * @param {Number} index + * @returns {Boolean} + */ + +CoinView.prototype.spendOutput = function spendOutput(hash, index) { + var coins = this.get(hash); + + if (!coins) + return false; + + return this.spendFrom(coins, index); +}; + +/** + * Remove an output. + * @param {Hash} hash + * @param {Number} index + * @returns {Boolean} + */ + +CoinView.prototype.removeOutput = function removeOutput(hash, index) { + var coins = this.get(hash); + + if (!coins) + return false; + + return coins.remove(index); +}; + +/** + * Spend a coin from coins object. * @param {Coins} coins * @param {Number} index - * @returns {Coin} + * @returns {Boolean} */ CoinView.prototype.spendFrom = function spendFrom(coins, index) { @@ -152,9 +201,8 @@ CoinView.prototype.spendFrom = function spendFrom(coins, index) { }; /** - * Get a single coin. - * @param {Hash} hash - * @param {Number} index + * Get a single coin by input. + * @param {Input} input * @returns {Coin} */ @@ -168,26 +216,9 @@ CoinView.prototype.getCoin = function getCoin(input) { }; /** - * 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} + * Get a single output by input. + * @param {Input} input + * @returns {Output} */ CoinView.prototype.getOutput = function getOutput(input) { @@ -200,13 +231,74 @@ CoinView.prototype.getOutput = function getOutput(input) { }; /** - * Retrieve coins from database. - * @param {TX} tx - * @returns {Promise} + * Get a single entry by input. + * @param {Input} input + * @returns {CoinEntry} */ -CoinView.prototype.getCoins = co(function* getCoins(db, hash) { - var coins = this.unspent[hash]; +CoinView.prototype.getEntry = function getEntry(input) { + var coins = this.get(input.prevout.hash); + + if (!coins) + return; + + return coins.get(input.prevout.index); +}; + +/** + * Test whether the view has an entry by input. + * @param {Input} input + * @returns {Boolean} + */ + +CoinView.prototype.hasEntry = function hasEntry(input) { + var coins = this.get(input.prevout.hash); + + if (!coins) + return false; + + return coins.has(input.prevout.index); +}; + +/** + * Get coins height by input. + * @param {Input} input + * @returns {Number} + */ + +CoinView.prototype.getHeight = function getHeight(input) { + var coins = this.get(input.prevout.hash); + + if (!coins) + return -1; + + return coins.height; +}; + +/** + * Get coins coinbase flag by input. + * @param {Input} input + * @returns {Boolean} + */ + +CoinView.prototype.isCoinbase = function isCoinbase(input) { + var coins = this.get(input.prevout.hash); + + if (!coins) + return false; + + return coins.coinbase; +}; + +/** + * Retrieve coins from database. + * @param {ChainDB} db + * @param {TX} tx + * @returns {Promise} - Returns {@link Coins}. + */ + +CoinView.prototype.readCoins = co(function* readCoins(db, hash) { + var coins = this.map[hash]; if (!coins) { coins = yield db.getCoins(hash); @@ -214,7 +306,7 @@ CoinView.prototype.getCoins = co(function* getCoins(db, hash) { if (!coins) return; - this.unspent[hash] = coins; + this.map[hash] = coins; } return coins; @@ -224,21 +316,27 @@ CoinView.prototype.getCoins = co(function* getCoins(db, hash) { * Read all input coins into unspent map. * @param {ChainDB} db * @param {TX} tx + * @returns {Promise} - Returns {Boolean}. */ CoinView.prototype.ensureInputs = co(function* ensureInputs(db, tx) { + var found = true; var i, input; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - yield this.getCoins(db, input.prevout.hash); + if (!(yield this.readCoins(db, input.prevout.hash))) + found = false; } + + return found; }); /** * Spend coins for transaction. + * @param {ChainDB} db * @param {TX} tx - * @returns {Boolean} + * @returns {Promise} - Returns {Boolean}. */ CoinView.prototype.spendInputs = co(function* spendInputs(db, tx) { @@ -247,7 +345,7 @@ CoinView.prototype.spendInputs = co(function* spendInputs(db, tx) { for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; prevout = input.prevout; - coins = yield this.getCoins(db, prevout.hash); + coins = yield this.readCoins(db, prevout.hash); if (!coins) return false; @@ -265,24 +363,26 @@ CoinView.prototype.spendInputs = co(function* spendInputs(db, tx) { */ CoinView.prototype.toArray = function toArray() { - var keys = Object.keys(this.unspent); + var keys = Object.keys(this.map); var out = []; var i, hash; for (i = 0; i < keys.length; i++) { hash = keys[i]; - out.push(this.unspent[hash]); + out.push(this.map[hash]); } return out; }; /** - * Convert collection to an array. - * @returns {Coins[]} + * Write coin data to buffer writer + * as it pertains to a transaction. + * @param {BufferWriter} bw + * @param {TX} tx */ -CoinView.prototype.toPrevWriter = function toPrevWriter(bw, tx) { +CoinView.prototype.toFast = function toFast(bw, tx) { var i, input, coins, entry; for (i = 0; i < tx.inputs.length; i++) { @@ -309,58 +409,157 @@ CoinView.prototype.toPrevWriter = function toPrevWriter(bw, tx) { }; /** - * Convert collection to an array. - * @returns {Coins[]} + * Read serialized view data from a buffer + * reader as it pertains to a transaction. + * @private + * @param {BufferReader} br + * @param {TX} tx */ -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; +CoinView.prototype.fromFast = function fromFast(br, tx) { + var i, input, prevout, coins, entry; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - coins = this.get(input.prevout.hash); + prevout = input.prevout.hash; + + if (br.readU8() === 0) + continue; + + coins = this.get(prevout.hash); if (!coins) { coins = new Coins(); - coins.hash = input.prevout.hash; + coins.hash = prevout.hash; coins.coinbase = false; this.add(coins); } - if (br.readU8() === 1) { - entry = Coins.CoinEntry.fromReader(br); - coins.add(input.prevout.index, entry); - } + entry = CoinEntry.fromReader(br); + coins.add(prevout.index, entry); } return this; }; /** - * Convert collection to an array. - * @returns {Coins[]} + * Write coin data to buffer writer + * as it pertains to a transaction. + * @param {BufferWriter} bw + * @param {TX} tx */ -CoinView.fromPrevReader = function fromPrevReader(br, tx) { - return new CoinView().fromPrevReader(br, tx); +CoinView.prototype.toWriter = function toWriter(bw, tx) { + var map = {}; + var i, input, prevout, coins, entry, height; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + coins = this.get(prevout.hash); + + if (!coins) { + bw.writeU8(0); + continue; + } + + entry = coins.get(prevout.index); + + if (!entry) { + bw.writeU8(0); + continue; + } + + bw.writeU8(1); + + if (!map[prevout.hash]) { + height = coins.height; + if (height === -1) + height = 0; + bw.writeVarint(height * 2 + (coins.coinbase ? 1 : 0)); + bw.writeVarint(coins.version); + map[prevout.hash] = true; + } + + entry.toWriter(bw); + } + + return bw; }; /** - * Convert collection to an array. - * @returns {Coins[]} + * Serialize coin data to as it + * pertains to a transaction. + * @param {TX} tx + * @returns {Buffer} */ -CoinView.fromPrev = function fromPrev(data, tx) { - return new CoinView().fromPrevReader(new BufferReader(data), tx); +CoinView.prototype.toRaw = function toRaw(tx) { + return this.toWriter(new BufferWriter()).render(); +}; + +/** + * Read serialized view data from a buffer + * reader as it pertains to a transaction. + * @private + * @param {BufferReader} br + * @param {TX} tx + */ + +CoinView.prototype.fromReader = function fromReader(br, tx) { + var i, input, prevout, coins, entry, height; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout.hash; + + if (br.readU8() === 0) + continue; + + coins = this.get(prevout.hash); + + if (!coins) { + coins = new Coins(); + coins.hash = prevout.hash; + height = br.readVarint(); + coins.coinbase = (height & 1) !== 0; + height = height / 2 | 0; + if (height === 0) + height = -1; + coins.height = height; + coins.version = br.readVarint(); + this.add(coins); + } + + entry = CoinEntry.fromReader(br); + coins.add(prevout.index, entry); + } + + return this; +}; + +/** + * Read serialized view data from a buffer + * reader as it pertains to a transaction. + * @param {BufferReader} br + * @param {TX} tx + * @returns {CoinView} + */ + +CoinView.fromReader = function fromReader(br, tx) { + return new CoinView().fromReader(br, tx); +}; + +/** + * Read serialized view data from a buffer + * as it pertains to a transaction. + * @param {Buffer} data + * @param {TX} tx + * @returns {CoinView} + */ + +CoinView.fromRaw = function fromRaw(data, tx) { + return new CoinView().fromReader(new BufferReader(data), tx); }; /* diff --git a/lib/blockchain/undocoins.js b/lib/blockchain/undocoins.js index 90181df8..b00c7706 100644 --- a/lib/blockchain/undocoins.js +++ b/lib/blockchain/undocoins.js @@ -123,7 +123,6 @@ UndoCoins.prototype.top = function top() { * Re-apply undo coins to a view, effectively unspending them. * @param {CoinView} view * @param {Outpoint} outpoint - * @returns {Coin} */ UndoCoins.prototype.apply = function apply(view, outpoint) { @@ -137,15 +136,15 @@ UndoCoins.prototype.apply = function apply(view, outpoint) { if (undo.height !== -1) { coins = new Coins(); - assert(!view.unspent[hash]); - view.unspent[hash] = coins; + assert(!view.map[hash]); + view.map[hash] = coins; coins.hash = hash; coins.coinbase = undo.coinbase; coins.height = undo.height; coins.version = undo.version; } else { - coins = view.unspent[hash]; + coins = view.map[hash]; assert(coins); } diff --git a/lib/http/server.js b/lib/http/server.js index 13307778..a947ebca 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -617,7 +617,7 @@ HTTPServer.prototype._init = function _init() { coins = yield this.node.getCoinsByAddress(req.options.address); send(200, coins.map(function(coin) { - return coin.toJSON(this.network); + return coin.getJSON(this.network); }, this)); })); @@ -633,7 +633,7 @@ HTTPServer.prototype._init = function _init() { if (!coin) return send(404); - send(200, coin.toJSON(this.network)); + send(200, coin.getJSON(this.network)); })); // Bulk read UTXOs @@ -645,7 +645,7 @@ HTTPServer.prototype._init = function _init() { coins = yield this.node.getCoinsByAddress(req.options.address); send(200, coins.map(function(coin) { - return coin.toJSON(this.network); + return coin.getJSON(this.network); }, this)); })); @@ -660,7 +660,7 @@ HTTPServer.prototype._init = function _init() { if (!tx) return send(404); - send(200, tx.toJSON(this.network)); + send(200, tx.getJSON(this.network)); })); // TX by address @@ -672,7 +672,7 @@ HTTPServer.prototype._init = function _init() { txs = yield this.node.getTXByAddress(req.options.address); send(200, txs.map(function(tx) { - return tx.toJSON(this.network); + return tx.getJSON(this.network); }, this)); })); @@ -685,7 +685,7 @@ HTTPServer.prototype._init = function _init() { txs = yield this.node.getTXByAddress(req.options.address); send(200, txs.map(function(tx) { - return tx.toJSON(this.network); + return tx.getJSON(this.network); }, this)); })); @@ -706,7 +706,7 @@ HTTPServer.prototype._init = function _init() { if (!view) return send(404); - send(200, block.toJSON(this.network, view)); + send(200, block.getJSON(this.network, view)); })); // Mempool snapshot @@ -719,7 +719,7 @@ HTTPServer.prototype._init = function _init() { txs = this.mempool.getHistory(); send(200, txs.map(function(tx) { - return tx.toJSON(this.network); + return tx.getJSON(this.network); }, this)); })); @@ -910,7 +910,7 @@ HTTPServer.prototype._init = function _init() { var passphrase = options.passphrase; var tx = yield req.wallet.createTX(options); yield req.wallet.sign(tx, passphrase); - send(200, tx.toJSON(this.network)); + send(200, tx.getJSON(this.network)); })); // Sign TX @@ -920,7 +920,7 @@ HTTPServer.prototype._init = function _init() { var tx = req.options.tx; enforce(tx, 'TX is required.'); yield req.wallet.sign(tx, passphrase); - send(200, tx.toJSON(this.network)); + send(200, tx.getJSON(this.network)); })); // Zap Wallet TXs @@ -1060,7 +1060,7 @@ HTTPServer.prototype._init = function _init() { sortCoins(coins); send(200, coins.map(function(coin) { - return coin.toJSON(this.network); + return coin.getJSON(this.network); }, this)); })); @@ -1112,7 +1112,7 @@ HTTPServer.prototype._init = function _init() { if (!coin) return send(404); - send(200, coin.toJSON(this.network)); + send(200, coin.getJSON(this.network)); })); // Wallet TXs @@ -1898,7 +1898,7 @@ ClientSocket.prototype.frameEntry = function frameEntry(entry) { ClientSocket.prototype.frameTX = function frameTX(tx) { if (this.raw) return tx.toRaw(); - return tx.toJSON(this.network); + return tx.getJSON(this.network); }; ClientSocket.prototype.join = function join(id) { diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index d939de86..7f6377b2 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -420,7 +420,8 @@ Mempool.prototype.getCoin = function getCoin(hash, index) { */ Mempool.prototype.isSpent = function isSpent(hash, index) { - return this.spents[hash + index] != null; + var key = Outpoint.toKey(hash, index); + return this.spents[key] != null; }; /** @@ -431,7 +432,8 @@ Mempool.prototype.isSpent = function isSpent(hash, index) { */ Mempool.prototype.getSpent = function getSpent(hash, index) { - return this.spents[hash + index]; + var key = Outpoint.toKey(hash, index); + return this.spents[key]; }; /** @@ -442,7 +444,8 @@ Mempool.prototype.getSpent = function getSpent(hash, index) { */ Mempool.prototype.getSpentTX = function getSpentTX(hash, index) { - var entry = this.spents[hash + index]; + var key = Outpoint.toKey(hash, index); + var entry = this.spents[key]; if (!entry) return; @@ -732,6 +735,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { * This function will also resolve orphans if possible (the * resolved orphans _will_ be validated). * @param {MempoolEntry} entry + * @param {CoinView} view * @returns {Promise} */ @@ -809,6 +813,7 @@ Mempool.prototype.removeEntry = function removeEntry(entry, limit) { /** * Verify a transaction with mempool standards. * @param {TX} tx + * @param {CoinView} view * @returns {Promise} */ @@ -947,6 +952,7 @@ Mempool.prototype.verify = co(function* verify(entry, view) { * Verify inputs, return a boolean * instead of an error based on success. * @param {TX} tx + * @param {CoinView} view * @param {VerifyFlags} flags * @returns {Promise} */ @@ -966,6 +972,7 @@ Mempool.prototype.verifyResult = co(function* verifyResult(tx, view, flags) { * Verify inputs for standard * _and_ mandatory flags on failure. * @param {TX} tx + * @param {CoinView} view * @param {VerifyFlags} flags * @returns {Promise} */ @@ -1418,21 +1425,13 @@ Mempool.prototype.removeOrphan = function removeOrphan(hash) { */ Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx) { - var spent = {}; - var i, input, prevout, key; + var i, input, prevout; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; prevout = input.prevout; - key = prevout.hash + prevout.index; - if (this.isSpent(prevout.hash, prevout.index)) return true; - - if (spent[key]) - return true; - - spent[key] = true; } return false; @@ -1441,6 +1440,7 @@ Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx) { /** * Get coin viewpoint. * @param {TX} tx + * @param {CoinView} view * @returns {Promise} - Returns {@link CoinView}. */ @@ -1476,6 +1476,7 @@ Mempool.prototype.getCoinView = co(function* getCoinView(tx) { /** * Spend coins for transaction. * @param {TX} tx + * @param {CoinView} view * @returns {Boolean} */ @@ -1511,6 +1512,7 @@ Mempool.prototype.getSnapshot = function getSnapshot() { /** * Check sequence locks on a transaction against the current tip. * @param {TX} tx + * @param {CoinView} view * @param {LockFlags} flags * @returns {Promise} - Returns Boolean. */ @@ -1534,6 +1536,7 @@ Mempool.prototype.verifyFinal = function verifyFinal(tx, flags) { * Map a transaction to the mempool. * @private * @param {MempoolEntry} entry + * @param {CoinView} view */ Mempool.prototype.trackEntry = function trackEntry(entry, view) { @@ -1550,7 +1553,7 @@ Mempool.prototype.trackEntry = function trackEntry(entry, view) { for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - key = input.prevout.hash + input.prevout.index; + key = input.prevout.toKey(); if (this.options.indexAddress) this.coinIndex.removeCoin(input.prevout); @@ -1595,7 +1598,7 @@ Mempool.prototype.untrackEntry = function untrackEntry(entry) { for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - key = input.prevout.hash + input.prevout.index; + key = input.prevout.toKey(); if (this.options.indexAddress) this.coinIndex.removeCoin(input.prevout); @@ -1821,7 +1824,7 @@ AddressIndex.prototype.getCoins = function getCoins(address) { }; AddressIndex.prototype.addCoin = function addCoin(coin) { - var key = coin.hash + coin.index; + var key = coin.toKey(); var hash = coin.getHash('hex'); var outpoint, items; @@ -1842,7 +1845,7 @@ AddressIndex.prototype.addCoin = function addCoin(coin) { }; AddressIndex.prototype.removeCoin = function removeCoin(coin) { - var key = coin.hash + coin.index; + var key = coin.toKey(); var hash = this.map[key]; var outpoint, items; diff --git a/lib/primitives/block.js b/lib/primitives/block.js index f917b11a..e2832033 100644 --- a/lib/primitives/block.js +++ b/lib/primitives/block.js @@ -617,6 +617,7 @@ Block.prototype.inspect = function inspect() { /** * Inspect the block and return a more * user-friendly representation of the data. + * @param {CoinView?} view * @returns {Object} */ @@ -645,13 +646,25 @@ Block.prototype.format = function format(view) { /** * Convert the block to an object suitable - * for JSON serialization. Note that the hashes - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. + * for JSON serialization. * @returns {Object} */ -Block.prototype.toJSON = function toJSON(network, view) { +Block.prototype.toJSON = function toJSON() { + return this.getJSON(); +}; + +/** + * Convert the block to an object suitable + * for JSON serialization. Note that the hashes + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {CoinView} view + * @returns {Object} + */ + +Block.prototype.getJSON = function getJSON(network, view) { network = Network.get(network); return { hash: this.rhash(), @@ -664,7 +677,7 @@ Block.prototype.toJSON = function toJSON(network, view) { nonce: this.nonce, totalTX: this.totalTX, txs: this.txs.map(function(tx) { - return tx.toJSON(network, view); + return tx.getJSON(network, view); }) }; }; diff --git a/lib/primitives/coin.js b/lib/primitives/coin.js index aa6f475c..3d0731d7 100644 --- a/lib/primitives/coin.js +++ b/lib/primitives/coin.js @@ -110,6 +110,40 @@ Coin.prototype.getConfirmations = function getConfirmations(height) { return height - this.height + 1; }; +/** + * Serialize coin to a key + * suitable for a hash table. + * @returns {String} + */ + +Coin.prototype.toKey = function toKey() { + return this.hash + this.index; +}; + +/** + * Inject properties from hash table key. + * @private + * @param {String} key + * @returns {Coin} + */ + +Coin.prototype.fromKey = function fromKey(key) { + assert(key.length > 64); + this.hash = key.slice(0, 64); + this.index = +key.slice(64); + return this; +}; + +/** + * Instantiate coin from hash table key. + * @param {String} key + * @returns {Coin} + */ + +Coin.fromKey = function fromKey(key) { + return new Coin().fromKey(key); +}; + /** * Convert the coin to a more user-friendly object. * @returns {Object} @@ -131,13 +165,25 @@ Coin.prototype.inspect = function inspect() { /** * Convert the coin to an object suitable - * for JSON serialization. Note that the hash - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. + * for JSON serialization. * @returns {Object} */ -Coin.prototype.toJSON = function toJSON(network, minimal) { +Coin.prototype.toJSON = function toJSON() { + return this.getJSON(); +}; + +/** + * Convert the coin to an object suitable + * for JSON serialization. Note that the hash + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {Boolean} minimal + * @returns {Object} + */ + +Coin.prototype.getJSON = function getJSON(network, minimal) { var address = this.getAddress(); network = Network.get(network); diff --git a/lib/primitives/input.js b/lib/primitives/input.js index 5710c153..fc05e88e 100644 --- a/lib/primitives/input.js +++ b/lib/primitives/input.js @@ -81,6 +81,7 @@ Input.fromOptions = function fromOptions(options) { * Get the previous output script type as a string. * Will "guess" based on the input script and/or * witness if coin is not available. + * @param {Coin?} coin * @returns {ScriptType} type */ @@ -104,6 +105,7 @@ Input.prototype.getType = function getType(coin) { /** * Get the redeem script. Will attempt to resolve nested * redeem scripts if witnessscripthash is behind a scripthash. + * @param {Coin?} coin * @returns {Script?} Redeem script. */ @@ -140,6 +142,7 @@ Input.prototype.getRedeem = function getRedeem(coin) { /** * Get the redeem script type. + * @param {Coin?} coin * @returns {String} subtype */ @@ -163,6 +166,7 @@ Input.prototype.getSubtype = function getSubtype(coin) { * Get the previous output script's address. Will "guess" * based on the input script and/or witness if coin * is not available. + * @param {Coin?} coin * @returns {Address?} address */ @@ -230,6 +234,7 @@ Input.prototype.inspect = function inspect() { /** * Convert the input to a more user-friendly object. + * @param {Coin?} coin * @returns {Object} */ @@ -249,13 +254,25 @@ Input.prototype.format = function format(coin) { /** * Convert the input to an object suitable - * for JSON serialization. Note that the hashes - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. + * for JSON serialization. * @returns {Object} */ Input.prototype.toJSON = function toJSON(network, coin) { + return this.getJSON(); +}; + +/** + * Convert the input to an object suitable + * for JSON serialization. Note that the hashes + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {Coin} coin + * @returns {Object} + */ + +Input.prototype.getJSON = function getJSON(network, coin) { var address; network = Network.get(network); @@ -272,7 +289,7 @@ Input.prototype.toJSON = function toJSON(network, coin) { witness: this.witness.toJSON(), sequence: this.sequence, address: address, - coin: coin ? coin.toJSON(network, true) : null + coin: coin ? coin.getJSON(network, true) : null }; }; diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index 37e85015..e6b2a73a 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -20,7 +20,7 @@ var Input = require('./input'); var Output = require('./output'); var Coin = require('./coin'); var Outpoint = require('./outpoint'); -var CoinMap = require('../blockchain/coinview'); +var CoinView = require('../blockchain/coinview'); var KeyRing = require('./keyring'); var Address = require('./address'); var workerPool = require('../workers/workerpool').pool; @@ -55,13 +55,7 @@ var encoding = require('../utils/encoding'); * @property {Number} changeIndex - Index of the change output (-1 if unknown). * @property {Number} height - Height of the block the * transaction was included in (-1 if unconfirmed). - * @property {ReversedHash|null} rblock - Reversed block hash (uint256le). - * @property {ReversedHash} rhash - Reversed transaction hash (uint256le). - * @property {ReversedHash} rwhash - Reversed witness - * transaction hash (uint256le). - * @property {String} txid - Transaction ID. - * @property {String} wtxid - Witness transaction ID (Same as txid if no - * witness is present. All zeroes if coinbase). + * @property {CoinView} view */ function MTX(options) { @@ -72,7 +66,7 @@ function MTX(options) { this.mutable = true; this.changeIndex = -1; - this.view = new CoinMap(); + this.view = new CoinView(); if (options) this.fromOptions(options); @@ -1341,14 +1335,23 @@ MTX.prototype.format = function format() { return TX.prototype.inspect.call(this, this.view); }; +/** + * Convert transaction to JSON. + * @returns {Object} + */ + +MTX.prototype.toJSON = function toJSON() { + return TX.prototype.toJSON.call(this, null, 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); +MTX.prototype.getJSON = function getJSON(network) { + return TX.prototype.getJSON.call(this, network, this.view); }; /** diff --git a/lib/primitives/outpoint.js b/lib/primitives/outpoint.js index 5e769559..a5719d6f 100644 --- a/lib/primitives/outpoint.js +++ b/lib/primitives/outpoint.js @@ -64,6 +64,40 @@ Outpoint.prototype.isNull = function isNull() { return this.index === 0xffffffff && this.hash === constants.NULL_HASH; }; +/** + * Serialize outpoint to a key + * suitable for a hash table. + * @returns {String} + */ + +Outpoint.prototype.toKey = function toKey() { + return Outpoint.toKey(this.hash, this.index); +}; + +/** + * Inject properties from hash table key. + * @private + * @param {String} key + * @returns {Outpoint} + */ + +Outpoint.prototype.fromKey = function fromKey(key) { + assert(key.length > 64); + this.hash = key.slice(0, 64); + this.index = +key.slice(64); + return this; +}; + +/** + * Instantiate outpoint from hash table key. + * @param {String} key + * @returns {Outpoint} + */ + +Outpoint.fromKey = function fromKey(key) { + return new Outpoint().fromKey(key); +}; + /** * Write outpoint to a buffer writer. * @param {BufferWriter} bw @@ -190,6 +224,21 @@ Outpoint.fromTX = function fromTX(tx, index) { return new Outpoint().fromTX(tx, index); }; +/** + * Serialize outpoint to a key + * suitable for a hash table. + * @param {Hash} hash + * @param {Number} index + * @returns {String} + */ + +Outpoint.toKey = function toKey(hash, index) { + assert(typeof hash === 'string'); + assert(hash.length === 64); + assert(index >= 0); + return hash + index; +}; + /** * Convert the outpoint to a user-friendly string. * @returns {String} diff --git a/lib/primitives/output.js b/lib/primitives/output.js index 0a7679b7..651198f0 100644 --- a/lib/primitives/output.js +++ b/lib/primitives/output.js @@ -131,7 +131,18 @@ Output.prototype.inspect = function inspect() { * @returns {Object} */ -Output.prototype.toJSON = function toJSON(network) { +Output.prototype.toJSON = function toJSON() { + return this.getJSON(); +}; + +/** + * Convert the output to an object suitable + * for JSON serialization. + * @param {Network} network + * @returns {Object} + */ + +Output.prototype.getJSON = function getJSON(network) { var address = this.getAddress(); network = Network.get(network); diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index e252f28e..6df894f2 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -58,13 +58,6 @@ var BAD_NONSTD_P2WSH = 3; * was first seen. Only non-zero on unconfirmed transactions. * @property {Number} height - Height of the block the * transaction was included in (-1 if unconfirmed). - * @property {ReversedHash|null} rblock - Reversed block hash (uint256le). - * @property {ReversedHash} rhash - Reversed transaction hash (uint256le). - * @property {ReversedHash} rwhash - Reversed witness - * transaction hash (uint256le). - * @property {String} txid - Transaction ID. - * @property {String} wtxid - Witness transaction ID (Same as txid if no - * witness is present. All zeroes if coinbase). */ function TX(options) { @@ -689,7 +682,8 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type /** * Verify all transaction inputs. - * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] + * @param {CoinView} view + * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] * @returns {Boolean} Whether the inputs are valid. */ @@ -753,6 +747,7 @@ TX.prototype.verifyInput = function verifyInput(index, coin, flags) { /** * Verify the transaction inputs on the worker pool * (if workers are enabled). + * @param {CoinView} view * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] * @returns {Promise} * @returns {Boolean} Whether the inputs are valid. @@ -817,6 +812,7 @@ TX.prototype.isRBF = function isRBF() { /** * Calculate the fee for the transaction. + * @param {CoinView} view * @returns {Amount} fee (zero if not all coins are available). */ @@ -829,6 +825,7 @@ TX.prototype.getFee = function getFee(view) { /** * Calculate the total input value. + * @param {CoinView} view * @returns {Amount} value */ @@ -881,6 +878,7 @@ TX.prototype.getOutputValue = function getOutputValue() { /** * Get all input addresses. * @private + * @param {CoinView} view * @returns {Array} */ @@ -943,6 +941,7 @@ TX.prototype._getOutputAddresses = function getOutputAddresses() { /** * Get all addresses. * @private + * @param {CoinView} view * @returns {Array} */ @@ -968,6 +967,7 @@ TX.prototype._getAddresses = function getAddresses(view) { /** * Get all input addresses. * @private + * @param {CoinView} view * @returns {Address[]} addresses */ @@ -986,6 +986,7 @@ TX.prototype.getOutputAddresses = function getOutputAddresses() { /** * Get all addresses. + * @param {CoinView} view * @returns {Address[]} addresses */ @@ -995,6 +996,7 @@ TX.prototype.getAddresses = function getAddresses(view) { /** * Get all input address hashes. + * @param {CoinView} view * @returns {Hash[]} hashes */ @@ -1037,6 +1039,7 @@ TX.prototype.getOutputHashes = function getOutputHashes(enc) { /** * Get all address hashes. + * @param {CoinView} view * @returns {Hash[]} hashes */ @@ -1059,6 +1062,7 @@ TX.prototype.getHashes = function getHashes(view, enc) { /** * Test whether the transaction has * all coins available/filled. + * @param {CoinView} view * @returns {Boolean} */ @@ -1135,6 +1139,7 @@ TX.prototype.getLegacySigops = function getLegacySigops() { /** * Calculate accurate sigop count, taking into account redeem scripts. + * @param {CoinView} view * @returns {Number} sigop count */ @@ -1161,6 +1166,7 @@ TX.prototype.getScripthashSigops = function getScripthashSigops(view) { /** * Calculate sigops weight, taking into account witness programs. + * @param {CoinView} view * @param {VerifyFlags?} flags * @returns {Number} sigop weight */ @@ -1197,6 +1203,7 @@ TX.prototype.getSigopsWeight = function getSigopsWeight(view, flags) { /** * Calculate virtual sigop count. + * @param {CoinView} view * @param {VerifyFlags?} flags * @returns {Number} sigop count */ @@ -1271,7 +1278,7 @@ TX.prototype.isSane = function isSane(ret) { for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; - key = input.prevout.hash + input.prevout.index; + key = input.prevout.toKey(); if (prevout[key]) { ret.reason = 'bad-txns-inputs-duplicate'; ret.score = 100; @@ -1388,6 +1395,7 @@ TX.prototype.isStandard = function isStandard(ret) { * Perform contextual checks to verify coin and input * script standardness (including the redeem script). * @see AreInputsStandard() + * @param {CoinView} view * @param {VerifyFlags?} flags * @returns {Boolean} */ @@ -1431,6 +1439,7 @@ TX.prototype.hasStandardInputs = function hasStandardInputs(view) { /** * Perform contextual checks to verify coin and witness standardness. * @see IsBadWitness() + * @param {CoinView} view * @returns {Boolean} */ @@ -1464,6 +1473,7 @@ TX.prototype.hasStandardWitness = function hasStandardWitness(view, ret) { * Perform contextual checks to verify coin and witness standardness. * @private * @see IsBadWitness() + * @param {CoinView} view * @returns {Boolean} */ @@ -1595,6 +1605,7 @@ TX.prototype.getWitnessStandard = function getWitnessStandard(view) { * (coinbases can only be spent 100 blocks or more * after they're created). Note that this function is * consensus critical. + * @param {CoinView} view * @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. @@ -1698,6 +1709,7 @@ TX.prototype.getModifiedSize = function getModifiedSize(size) { /** * Calculate the transaction priority. + * @param {CoinView} view * @param {Number?} height - If not present, tx height * or network height will be used. * @param {Number?} size - Size to calculate priority @@ -1740,6 +1752,7 @@ TX.prototype.getPriority = function getPriority(view, height, size) { /** * Calculate the transaction's on-chain value. + * @param {CoinView} view * @param {Number?} height * @returns {Number} */ @@ -1778,6 +1791,7 @@ TX.prototype.getChainValue = function getChainValue(view, height) { * free threshold in priority. A transaction which * passed this test is most likely relayable * without a fee. + * @param {CoinView} view * @param {Number?} height - If not present, tx * height or network height will be used. * @param {Number?} size - If not present, modified @@ -1826,6 +1840,7 @@ TX.prototype.getRoundFee = function getRoundFee(size, rate) { /** * Calculate the transaction's rate based on size * and fees. Size will be calculated if not present. + * @param {CoinView} view * @param {Number?} size * @returns {Rate} */ @@ -2009,6 +2024,7 @@ TX.prototype.inspect = function inspect() { /** * Inspect the transaction and return a more * user-friendly representation of the data. + * @param {CoinView} view * @returns {Object} */ @@ -2026,8 +2042,8 @@ TX.prototype.format = function format(view) { } return { - hash: this.rhash(), - witnessHash: this.rwhash(), + hash: this.txid(), + witnessHash: this.wtxid(), size: this.getSize(), virtualSize: this.getVirtualSize(), height: this.height, @@ -2053,13 +2069,25 @@ TX.prototype.format = function format(view) { /** * Convert the transaction to an object suitable - * for JSON serialization. Note that the hashes - * will be reversed to abide by bitcoind's legacy - * of little-endian uint256s. + * for JSON serialization. * @returns {Object} */ -TX.prototype.toJSON = function toJSON(network, view) { +TX.prototype.toJSON = function toJSON() { + return this.getJSON(); +}; + +/** + * Convert the transaction to an object suitable + * for JSON serialization. Note that the hashes + * will be reversed to abide by bitcoind's legacy + * of little-endian uint256s. + * @param {Network} network + * @param {CoinView} view + * @returns {Object} + */ + +TX.prototype.getJSON = function getJSON(network, view) { var rate = 0; var fee = 0; @@ -2075,10 +2103,10 @@ TX.prototype.toJSON = function toJSON(network, view) { network = Network.get(network); return { - hash: util.revHex(this.hash('hex')), - witnessHash: util.revHex(this.witnessHash('hex')), + hash: this.txid(), + witnessHash: this.wtxid(), height: this.height, - block: this.block ? util.revHex(this.block) : null, + block: this.rblock(), ts: this.ts, ps: this.ps, date: util.date(this.ts || this.ps), @@ -2089,10 +2117,10 @@ TX.prototype.toJSON = function toJSON(network, view) { flag: this.flag, inputs: this.inputs.map(function(input) { var coin = view ? view.getCoin(input) : null; - return input.toJSON(network, coin); + return input.getJSON(network, coin); }), outputs: this.outputs.map(function(output) { - return output.toJSON(network); + return output.getJSON(network); }), locktime: this.locktime }; @@ -2398,8 +2426,10 @@ TX.prototype.frameWitnessWriter = function frameWitnessWriter(bw) { start = bw.written; - for (i = 0; i < this.inputs.length; i++) - this.inputs[i].witness.toWriter(bw); + for (i = 0; i < this.inputs.length; i++) { + input = this.inputs[i]; + input.witness.toWriter(bw); + } witnessSize += bw.written - start; diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index aa567ff5..b0c2409d 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -390,7 +390,7 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx, block) { if (!orphans[i]) continue; - key = prevout.hash + prevout.index; + key = prevout.toKey(); // In theory, someone could try to DoS // us by creating tons of fake transactions @@ -452,7 +452,7 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, block, resolved) for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; - key = hash + i; + key = Outpoint.toKey(hash, i); orphans = this.orphans[key]; if (!orphans) @@ -505,7 +505,7 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, block, resolved) TXDB.prototype.saveCredit = co(function* saveCredit(credit, path) { var coin = credit.coin; - var key = coin.hash + coin.index; + var key = coin.toKey(); var raw = credit.toRaw(); yield this.addOutpointMap(coin.hash, coin.index); @@ -524,7 +524,7 @@ TXDB.prototype.saveCredit = co(function* saveCredit(credit, path) { TXDB.prototype.removeCredit = co(function* removeCredit(credit, path) { var coin = credit.coin; - var key = coin.hash + coin.index; + var key = coin.toKey(); yield this.removeOutpointMap(coin.hash, coin.index); @@ -1789,7 +1789,7 @@ TXDB.prototype.unlockTX = function unlockTX(tx) { */ TXDB.prototype.lockCoin = function lockCoin(coin) { - var key = coin.hash + coin.index; + var key = coin.toKey(); this.locked[key] = true; }; @@ -1799,7 +1799,7 @@ TXDB.prototype.lockCoin = function lockCoin(coin) { */ TXDB.prototype.unlockCoin = function unlockCoin(coin) { - var key = coin.hash + coin.index; + var key = coin.toKey(); delete this.locked[key]; }; @@ -1809,7 +1809,7 @@ TXDB.prototype.unlockCoin = function unlockCoin(coin) { */ TXDB.prototype.isLocked = function isLocked(coin) { - var key = coin.hash + coin.index; + var key = coin.toKey(); return this.locked[key] === true; }; @@ -2237,7 +2237,7 @@ TXDB.prototype.getCredits = function getCredits(account) { var hash = parts[0]; var index = parts[1]; var credit = Credit.fromRaw(value); - var ckey = hash + index; + var ckey = Outpoint.toKey(hash, index); credit.coin.hash = hash; credit.coin.index = index; self.coinCache.set(ckey, value); @@ -2514,7 +2514,7 @@ TXDB.prototype.getCoin = co(function* getCoin(hash, index) { TXDB.prototype.getCredit = co(function* getCredit(hash, index) { var state = this.state; - var key = hash + index; + var key = Outpoint.toKey(hash, index); var data = this.coinCache.get(key); var credit; @@ -2603,7 +2603,7 @@ TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(tx, index) { */ TXDB.prototype.hasCoin = function hasCoin(hash, index) { - var key = hash + index; + var key = Outpoint.toKey(hash, index); if (this.coinCache.has(key)) return Promise.resolve(true); diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 92da6b90..bc229096 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -1583,7 +1583,7 @@ Wallet.prototype.createTX = co(function* createTX(options, force) { if (!tx.isSane()) throw new Error('CheckTransaction failed.'); - if (!tx.checkInputs(this.db.state.height)) + if (!tx.checkInputs(this.db.state.height + 1)) throw new Error('CheckInputs failed.'); total = yield this.template(tx); diff --git a/lib/workers/jobs.js b/lib/workers/jobs.js index ece4033c..51edd590 100644 --- a/lib/workers/jobs.js +++ b/lib/workers/jobs.js @@ -70,6 +70,7 @@ jobs._execute = function execute(p) { * Execute tx.verify() on worker. * @see TX#verify * @param {TX} tx + * @param {CoinView} view * @param {VerifyFlags} flags * @returns {Boolean} */ diff --git a/lib/workers/packets.js b/lib/workers/packets.js index cb1812de..737a0f09 100644 --- a/lib/workers/packets.js +++ b/lib/workers/packets.js @@ -183,7 +183,7 @@ VerifyPacket.prototype.cmd = packetTypes.VERIFY; VerifyPacket.prototype.toWriter = function(bw) { this.tx.toWriter(bw); - this.view.toPrevWriter(bw, this.tx); + this.view.toWriter(bw, this.tx); bw.write32(this.flags != null ? this.flags : -1); }; @@ -192,7 +192,7 @@ VerifyPacket.fromRaw = function fromRaw(TX, CoinView, data) { var packet = new VerifyPacket(); packet.tx = TX.fromReader(br); - packet.view = CoinView.fromPrevReader(br, packet.tx); + packet.view = CoinView.fromReader(br, packet.tx); packet.flags = br.read32(); @@ -247,7 +247,7 @@ SignPacket.prototype.toWriter = function toWriter(bw) { var i, ring; this.tx.toWriter(bw); - this.tx.view.toPrevWriter(bw, this.tx); + this.tx.view.toWriter(bw, this.tx); bw.writeVarint(this.rings.length); @@ -265,7 +265,7 @@ SignPacket.fromRaw = function fromRaw(MTX, KeyRing, data) { var i, count, ring; packet.tx = MTX.fromReader(br); - packet.tx.view.fromPrevReader(br, packet.tx); + packet.tx.view.fromReader(br, packet.tx); count = br.readVarint(); diff --git a/lib/workers/workerpool.js b/lib/workers/workerpool.js index 71f9e02f..45ffe3a5 100644 --- a/lib/workers/workerpool.js +++ b/lib/workers/workerpool.js @@ -260,6 +260,7 @@ WorkerPool.prototype.execute = function execute(packet, timeout) { /** * Execute the tx verification job (default timeout). * @param {TX} tx + * @param {CoinView} view * @param {VerifyFlags} flags * @returns {Promise} - Returns Boolean. */ diff --git a/scripts/dump.js b/scripts/dump.js index 41a823dc..4e62a26a 100644 --- a/scripts/dump.js +++ b/scripts/dump.js @@ -1,32 +1,36 @@ +'use strict'; + var fs = require('fs'); var heapdump = require('heapdump'); - var MempoolEntry = require('../lib/mempool/mempoolentry'); var Coins = require('../lib/blockchain/coins'); var TX = require('../lib/primitives/tx'); +var CoinView = require('../lib/blockchain/coinview'); var SNAPSHOT = __dirname + '/../dump.heapsnapshot'; var tx = parseTX('../test/data/tx4.hex'); +var raw, coins, entry; function parseTX(file) { - var filename = __dirname + '/' + file; - var data = fs.readFileSync(filename, 'utf8'); + var data = fs.readFileSync(__dirname + '/' + file, 'utf8'); var parts = data.trim().split(/\n+/); - var hex = parts[0].trim(); - var tx = TX.fromRaw(hex, 'hex'); - var i, tx, coin; + var raw = parts[0]; + var tx = TX.fromRaw(raw.trim(), 'hex'); + var view = new CoinView(); + var i, prev; for (i = 1; i < parts.length; i++) { - hex = parts[i].trim(); - coin = TX.fromRaw(hex, 'hex'); - tx.fillCoins(coin); + raw = parts[i]; + prev = TX.fromRaw(raw.trim(), 'hex'); + view.addTX(prev, -1); } - return tx; + return { tx: tx, view: view }; } -var coins = Coins.fromRaw(Coins.fromTX(tx).toRaw(), tx.hash('hex')); -var entry = MempoolEntry.fromTX(tx, 1000000); +raw = Coins.fromTX(tx.tx, 0).toRaw(); +coins = Coins.fromRaw(raw, tx.hash('hex')); +entry = MempoolEntry.fromTX(tx, tx.view, 1000000); setInterval(function() { console.log(tx.hash('hex')); diff --git a/scripts/gen.js b/scripts/gen.js index 2db7c30b..78bc2370 100644 --- a/scripts/gen.js +++ b/scripts/gen.js @@ -1,10 +1,14 @@ 'use strict'; var BN = require('bn.js'); -var bcoin = require('bcoin'); -var constants = bcoin.constants; +var util = require('../lib/utils/util'); +var constants = require('../lib/protocol/constants'); +var TX = require('../lib/primitives/tx'); +var Block = require('../lib/primitives/block'); +var Script = require('../lib/script/script'); +var Opcode = require('../lib/script/opcode'); var opcodes = constants.opcodes; -var util = bcoin.util; +var main, testnet, regtest, segnet3, segnet4, btcd; function createGenesisBlock(options) { var flags = options.flags; @@ -19,7 +23,7 @@ function createGenesisBlock(options) { } if (!script) { - script = bcoin.script.fromArray([ + script = Script.fromArray([ new Buffer('04678afdb0fe5548271967f1a67130b7105cd6a828e039' + '09a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c3' + '84df7ba0b8d578a4c702b6bf11d5f', 'hex'), @@ -30,7 +34,7 @@ function createGenesisBlock(options) { if (!reward) reward = 50 * constants.COIN; - tx = new bcoin.tx({ + tx = new TX({ version: 1, flag: 1, inputs: [{ @@ -39,9 +43,9 @@ function createGenesisBlock(options) { index: 0xffffffff }, script: [ - bcoin.opcode.fromNumber(new BN(486604799)), - bcoin.opcode.fromPush(new Buffer([4])), - bcoin.opcode.fromData(flags) + Opcode.fromNumber(new BN(486604799)), + Opcode.fromPush(new Buffer([4])), + Opcode.fromData(flags) ], sequence: 0xffffffff }], @@ -52,7 +56,7 @@ function createGenesisBlock(options) { locktime: 0 }); - block = new bcoin.block({ + block = new Block({ version: options.version, prevBlock: constants.NULL_HASH, merkleRoot: tx.hash('hex'), @@ -67,42 +71,42 @@ function createGenesisBlock(options) { return block; } -var main = createGenesisBlock({ +main = createGenesisBlock({ version: 1, ts: 1231006505, bits: 486604799, nonce: 2083236893 }); -var testnet = createGenesisBlock({ +testnet = createGenesisBlock({ version: 1, ts: 1296688602, bits: 486604799, nonce: 414098458 }); -var regtest = createGenesisBlock({ +regtest = createGenesisBlock({ version: 1, ts: 1296688602, bits: 545259519, nonce: 2 }); -var segnet3 = createGenesisBlock({ +segnet3 = createGenesisBlock({ version: 1, ts: 1452831101, bits: 486604799, nonce: 0 }); -var segnet4 = createGenesisBlock({ +segnet4 = createGenesisBlock({ version: 1, ts: 1452831101, bits: 503447551, nonce: 0 }); -var btcd = createGenesisBlock({ +btcd = createGenesisBlock({ version: 1, ts: 1401292357, bits: 545259519, @@ -120,20 +124,20 @@ util.log(''); util.log(segnet4); util.log(''); util.log(''); -util.log('main hash: %s', main.rhash); +util.log('main hash: %s', main.rhash()); util.log('main raw: %s', main.toRaw().toString('hex')); util.log(''); -util.log('testnet hash: %s', testnet.rhash); +util.log('testnet hash: %s', testnet.rhash()); util.log('testnet raw: %s', testnet.toRaw().toString('hex')); util.log(''); -util.log('regtest hash: %s', regtest.rhash); +util.log('regtest hash: %s', regtest.rhash()); util.log('regtest raw: %s', regtest.toRaw().toString('hex')); util.log(''); -util.log('segnet3 hash: %s', segnet3.rhash); +util.log('segnet3 hash: %s', segnet3.rhash()); util.log('segnet3 raw: %s', segnet3.toRaw().toString('hex')); util.log(''); -util.log('segnet4 hash: %s', segnet4.rhash); +util.log('segnet4 hash: %s', segnet4.rhash()); util.log('segnet4 raw: %s', segnet4.toRaw().toString('hex')); util.log(''); -util.log('btcd simnet hash: %s', btcd.rhash); +util.log('btcd simnet hash: %s', btcd.rhash()); util.log('btcd simnet raw: %s', btcd.toRaw().toString('hex'));