diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index 0fd11349..6a525bd7 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -84,7 +84,7 @@ ChainDB.prototype.open = async function open() { this.logger.info('Opening ChainDB...'); await this.db.open(); - await this.db.checkVersion('V', 3); + await this.db.checkVersion('V', 4); state = await this.getState(); @@ -1685,23 +1685,19 @@ ChainDB.prototype.removeBlock = async function removeBlock(entry) { ChainDB.prototype.saveView = function saveView(view) { for (let [hash, coins] of view.map) { - for (let i = 0; i < coins.outputs.length; i++) { - let coin = coins.outputs[i]; + for (let [index, coin] of coins.outputs) { let raw; - if (!coin) - continue; - if (coin.spent) { - this.del(layout.c(hash, i)); - this.coinCache.unpush(hash + i); + this.del(layout.c(hash, index)); + this.coinCache.unpush(hash + index); continue; } raw = coin.toRaw(); - this.put(layout.c(hash, i), raw); - this.coinCache.push(hash + i, raw); + this.put(layout.c(hash, index), raw); + this.coinCache.push(hash + index, raw); } } }; diff --git a/lib/coins/coins.js b/lib/coins/coins.js index 38d5a577..37c0b547 100644 --- a/lib/coins/coins.js +++ b/lib/coins/coins.js @@ -21,7 +21,7 @@ function Coins(options) { if (!(this instanceof Coins)) return new Coins(options); - this.outputs = []; + this.outputs = new Map(); } /** @@ -33,12 +33,9 @@ function Coins(options) { Coins.prototype.add = function add(index, coin) { assert(index >= 0); - while (this.outputs.length <= index) - this.outputs.push(null); + assert(!this.outputs.has(index)); - assert(!this.outputs[index]); - - this.outputs[index] = coin; + this.outputs.set(index, coin); }; /** @@ -69,10 +66,7 @@ Coins.prototype.addCoin = function addCoin(coin) { */ Coins.prototype.has = function has(index) { - if (index >= this.outputs.length) - return false; - - return this.outputs[index] != null; + return this.outputs.has(index); }; /** @@ -82,12 +76,7 @@ Coins.prototype.has = function has(index) { */ Coins.prototype.isUnspent = function isUnspent(index) { - let coin; - - if (index >= this.outputs.length) - return false; - - coin = this.outputs[index]; + let coin = this.outputs.get(index); if (!coin || coin.spent) return false; @@ -102,10 +91,7 @@ Coins.prototype.isUnspent = function isUnspent(index) { */ Coins.prototype.get = function get(index) { - if (index >= this.outputs.length) - return; - - return this.outputs[index]; + return this.outputs.get(index); }; /** @@ -115,10 +101,12 @@ Coins.prototype.get = function get(index) { */ Coins.prototype.getOutput = function getOutput(index) { - if (index >= this.outputs.length) + let coin = this.outputs.get(index); + + if (!coin) return; - return this.outputs[index].output; + return coin.output; }; /** @@ -128,10 +116,12 @@ Coins.prototype.getOutput = function getOutput(index) { */ Coins.prototype.getCoin = function getCoin(prevout) { - if (prevout.index >= this.outputs.length) + let coin = this.outputs.get(prevout.index); + + if (!coin) return; - return this.outputs[prevout.index].toCoin(prevout); + return coin.toCoin(prevout); }; /** @@ -163,8 +153,7 @@ Coins.prototype.remove = function remove(index) { if (!coin) return false; - this.outputs[index] = null; - this.cleanup(); + this.outputs.delete(index); return coin; }; @@ -175,25 +164,16 @@ Coins.prototype.remove = function remove(index) { */ Coins.prototype.length = function length() { - let len = this.outputs.length; + let len = -1; - while (len > 0 && !this.isUnspent(len - 1)) - len--; + for (let [index, coin] of this.outputs) { + if (!coin.spent) { + if (index > len) + len = index; + } + } - return len; -}; - -/** - * Cleanup spent outputs (remove pruned). - */ - -Coins.prototype.cleanup = function cleanup() { - let len = this.outputs.length; - - while (len > 0 && !this.outputs[len - 1]) - len--; - - this.outputs.length = len; + return len + 1; }; /** @@ -202,7 +182,7 @@ Coins.prototype.cleanup = function cleanup() { */ Coins.prototype.isEmpty = function isEmpty() { - return this.length() === 0; + return this.outputs.size === 0; }; /** @@ -218,16 +198,12 @@ Coins.prototype.fromTX = function fromTX(tx, height) { for (let i = 0; i < tx.outputs.length; i++) { let output = tx.outputs[i]; - if (output.script.isUnspendable()) { - this.outputs.push(null); + if (output.script.isUnspendable()) continue; - } - this.outputs.push(CoinEntry.fromTX(tx, i, height)); + this.outputs.set(i, CoinEntry.fromTX(tx, i, height)); } - this.cleanup(); - return this; }; diff --git a/lib/coins/coinview.js b/lib/coins/coinview.js index e6835fbb..e8dec2d4 100644 --- a/lib/coins/coinview.js +++ b/lib/coins/coinview.js @@ -93,7 +93,7 @@ CoinView.prototype.addTX = function addTX(tx, height) { CoinView.prototype.removeTX = function removeTX(tx, height) { let coins = Coins.fromTX(tx, height); - for (let coin of coins.outputs) + for (let coin of coins.outputs.values()) coin.spent = true; return this.add(tx.hash('hex'), coins); @@ -173,12 +173,7 @@ CoinView.prototype.addOutput = function addOutput(prevout, output) { */ CoinView.prototype.spendOutput = function spendOutput(prevout) { - let coins = this.get(prevout.hash); - - if (!coins) - return false; - - return this.spendFrom(coins, prevout.index); + return this.spend(prevout.hash, prevout.index); }; /** @@ -198,12 +193,17 @@ CoinView.prototype.removeOutput = function removeOutput(prevout) { /** * Spend a coin from coins object. - * @param {Coins} coins + * @param {Hash} hash * @param {Number} index * @returns {Boolean} */ -CoinView.prototype.spendFrom = function spendFrom(coins, index) { +CoinView.prototype.spend = function spend(hash, index) { + let coins = this.get(hash); + + if (!coins) + return false; + let coin = coins.spend(index); if (!coin) @@ -313,19 +313,20 @@ CoinView.prototype.isCoinbase = function isCoinbase(input) { */ CoinView.prototype.readCoin = async function readCoin(db, input) { - let coin = this.hasEntry(input); + let coin = this.getEntry(input); let prevout = input.prevout; - if (!coin) { - coin = await db.readCoin(prevout); + if (coin) + return coin; - if (!coin) - return; + coin = await db.readCoin(prevout); - return this.addEntry(prevout, coin); - } + if (!coin) + return null; - return this.get(prevout.hash); + this.addEntry(prevout, coin); + + return coin; }; /** @@ -336,7 +337,7 @@ CoinView.prototype.readCoin = async function readCoin(db, input) { * @returns {Promise} - Returns {Boolean}. */ -CoinView.prototype.ensureInputs = async function ensureInputs(db, tx) { +CoinView.prototype.readInputs = async function readInputs(db, tx) { let found = true; for (let input of tx.inputs) { @@ -356,33 +357,39 @@ CoinView.prototype.ensureInputs = async function ensureInputs(db, tx) { */ CoinView.prototype.spendInputs = async function spendInputs(db, tx) { + if (tx.inputs.length < 4) { + let jobs = []; + let coins; + + for (let input of tx.inputs) + jobs.push(this.readCoin(db, input)); + + coins = await Promise.all(jobs); + + for (let coin of coins) { + if (!coin || coin.spent) + return false; + + coin.spent = true; + this.undo.push(coin); + } + + return true; + } + for (let input of tx.inputs) { - let coins = await this.readCoin(db, input); + let coin = await this.readCoin(db, input); - if (!coins) + if (!coin || coin.spent) return false; - if (!this.spendFrom(coins, input.prevout.index)) - return false; + coin.spent = true; + this.undo.push(coin); } return true; }; -/** - * Convert collection to an array. - * @returns {Coins[]} - */ - -CoinView.prototype.toArray = function toArray() { - let out = []; - - for (let coins of this.map.values()) - out.push(coins); - - return out; -}; - /** * Calculate serialization size. * @returns {Number} diff --git a/test/coins-test.js b/test/coins-test.js index cc9de450..7d4c1468 100644 --- a/test/coins-test.js +++ b/test/coins-test.js @@ -38,7 +38,7 @@ describe('Coins', function() { view.addTX(tx1, 1); coins = view.get(hash); - assert.equal(coins.outputs.length, tx1.outputs.length); + assert.equal(coins.outputs.size, tx1.outputs.length); entry = coins.get(0); assert(entry); @@ -103,7 +103,7 @@ describe('Coins', function() { coins = res.get(prev.hash); assert.strictEqual(coins.length(), 2); - assert.strictEqual(coins.get(0), null); + assert.strictEqual(coins.get(0), undefined); deepCoinsEqual(coins.get(1), reserialize(coins.get(1))); }); });