From a1af3ab9809bd141f435885b22d662ce308cb8d6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 2 Jul 2017 18:10:05 -0700 Subject: [PATCH] chaindb: switch to pertxout. --- lib/blockchain/chaindb.js | 159 +++++---- lib/blockchain/layout-browser.js | 4 +- lib/blockchain/layout.js | 12 +- lib/coins/coinentry.js | 280 +++++++++++++++ lib/coins/coins.js | 566 ++----------------------------- lib/coins/coinview.js | 231 ++++++------- lib/coins/undocoins.js | 204 +---------- lib/mempool/mempool.js | 18 +- lib/primitives/tx.js | 21 +- test/block-test.js | 14 - test/coins-test.js | 52 +-- test/tx-test.js | 3 +- 12 files changed, 552 insertions(+), 1012 deletions(-) create mode 100644 lib/coins/coinentry.js diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index 1f144829..0fd11349 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -25,6 +25,7 @@ const Outpoint = require('../primitives/outpoint'); const Address = require('../primitives/address'); const ChainEntry = require('./chainentry'); const TXMeta = require('../primitives/txmeta'); +const CoinEntry = require('../coins/coinentry'); const U8 = encoding.U8; const U32 = encoding.U32; @@ -83,7 +84,7 @@ ChainDB.prototype.open = async function open() { this.logger.info('Opening ChainDB...'); await this.db.open(); - await this.db.checkVersion('V', 2); + await this.db.checkVersion('V', 3); state = await this.getState(); @@ -839,6 +840,40 @@ ChainDB.prototype.getTips = function getTips() { }); }; +/** + * Get a coin (unspents only). + * @method + * @private + * @param {Outpoint} outpoint + * @returns {Promise} - Returns {@link CoinEntry}. + */ + +ChainDB.prototype.readCoin = async function readCoin(outpoint) { + let state = this.state; + let key = outpoint.toKey(); + let hash = outpoint.hash; + let index = outpoint.index; + let raw; + + if (this.options.spv) + return; + + raw = this.coinCache.get(key); + + if (raw) + return CoinEntry.fromRaw(raw); + + raw = await this.db.get(layout.c(hash, index)); + + if (!raw) + return; + + if (state === this.state) + this.coinCache.set(key, raw); + + return CoinEntry.fromRaw(raw); +}; + /** * Get a coin (unspents only). * @method @@ -848,52 +883,9 @@ ChainDB.prototype.getTips = function getTips() { */ ChainDB.prototype.getCoin = async function getCoin(hash, index) { - let state = this.state; - let raw; - - if (this.options.spv) - return; - - raw = this.coinCache.get(hash); - - if (raw) - return Coins.parseCoin(raw, hash, index); - - raw = await this.db.get(layout.c(hash)); - - if (!raw) - return; - - if (state === this.state) - this.coinCache.set(hash, raw); - - return Coins.parseCoin(raw, hash, index); -}; - -/** - * Get coins (unspents only). - * @method - * @param {Hash} hash - * @returns {Promise} - Returns {@link Coins}. - */ - -ChainDB.prototype.getCoins = async function getCoins(hash) { - let raw; - - if (this.options.spv) - return; - - raw = this.coinCache.get(hash); - - if (raw) - return Coins.fromRaw(raw, hash); - - raw = await this.db.get(layout.c(hash)); - - if (!raw) - return; - - return Coins.fromRaw(raw, hash); + let prevout = new Outpoint(hash, index); + let coin = await this.readCoin(prevout); + return coin.toCoin(prevout); }; /** @@ -903,8 +895,18 @@ ChainDB.prototype.getCoins = async function getCoins(hash) { * @returns {Promise} - Returns Boolean. */ -ChainDB.prototype.hasCoins = function hasCoins(hash) { - return this.db.has(layout.c(hash)); +ChainDB.prototype.hasCoins = async function hasCoins(hash) { + let iter = this.db.iterator({ + gte: layout.c(hash, 0x00000000), + lte: layout.c(hash, 0xffffffff) + }); + + if (!(await iter.next())) + return false; + + await iter.end(); + + return true; }; /** @@ -916,19 +918,17 @@ ChainDB.prototype.hasCoins = function hasCoins(hash) { ChainDB.prototype.getCoinView = async function getCoinView(tx) { let view = new CoinView(); - let prevout = tx.getPrevout(); - for (let hash of prevout) { - let coins = await this.getCoins(hash); + for (let {prevout} of tx.inputs) { + let coin = await this.readCoin(prevout); - if (!coins) { - coins = new Coins(); - coins.hash = hash; - view.add(coins); + if (!coin) { + let coins = new Coins(); + view.add(prevout.hash, coins); continue; } - view.add(coins); + view.addEntry(prevout, coin); } return view; @@ -944,13 +944,13 @@ ChainDB.prototype.getCoinView = async function getCoinView(tx) { ChainDB.prototype.getSpentView = async function getSpentView(tx) { let view = await this.getCoinView(tx); - for (let coins of view.map.values()) { + for (let [hash, coins] of view.map) { let meta; if (!coins.isEmpty()) continue; - meta = await this.getMeta(coins.hash); + meta = await this.getMeta(hash); if (!meta) continue; @@ -1031,19 +1031,6 @@ ChainDB.prototype.getBlockView = async function getBlockView(block) { for (let j = tx.inputs.length - 1; j >= 0; j--) { let input = tx.inputs[j]; - let prev = input.prevout.hash; - - if (!view.has(prev)) { - assert(!undo.isEmpty()); - - if (undo.top().height === -1) { - let coins = new Coins(); - coins.hash = prev; - coins.coinbase = false; - view.add(coins); - } - } - undo.apply(view, input.prevout); } } @@ -1697,14 +1684,24 @@ ChainDB.prototype.removeBlock = async function removeBlock(entry) { */ ChainDB.prototype.saveView = function saveView(view) { - for (let coins of view.map.values()) { - if (coins.isEmpty()) { - this.del(layout.c(coins.hash)); - this.coinCache.unpush(coins.hash); - } else { - let raw = coins.toRaw(); - this.put(layout.c(coins.hash), raw); - this.coinCache.push(coins.hash, raw); + for (let [hash, coins] of view.map) { + for (let i = 0; i < coins.outputs.length; i++) { + let coin = coins.outputs[i]; + let raw; + + if (!coin) + continue; + + if (coin.spent) { + this.del(layout.c(hash, i)); + this.coinCache.unpush(hash + i); + continue; + } + + raw = coin.toRaw(); + + this.put(layout.c(hash, i), raw); + this.coinCache.push(hash + i, raw); } } }; @@ -1786,8 +1783,6 @@ ChainDB.prototype.disconnectBlock = async function disconnectBlock(entry, block) let tx = block.txs[i]; if (i > 0) { - await view.ensureInputs(this, tx); - for (let j = tx.inputs.length - 1; j >= 0; j--) { let input = tx.inputs[j]; undo.apply(view, input.prevout); diff --git a/lib/blockchain/layout-browser.js b/lib/blockchain/layout-browser.js index fc82b13c..d5eb315e 100644 --- a/lib/blockchain/layout-browser.js +++ b/lib/blockchain/layout-browser.js @@ -36,8 +36,8 @@ const layout = { t: function t(hash) { return 't' + hex(hash); }, - c: function c(hash) { - return 'c' + hex(hash); + c: function c(hash, index) { + return 'c' + hex(hash) + pad32(index); }, u: function u(hash) { return 'u' + hex(hash); diff --git a/lib/blockchain/layout.js b/lib/blockchain/layout.js index 162ffaee..e0c667e0 100644 --- a/lib/blockchain/layout.js +++ b/lib/blockchain/layout.js @@ -53,8 +53,8 @@ const layout = { t: function t(hash) { return pair(0x74, hash); }, - c: function c(hash) { - return pair(0x63, hash); + c: function c(hash, index) { + return bpair(0x63, hash, index); }, u: function u(hash) { return pair(0x75, hash); @@ -160,6 +160,14 @@ function ipair(prefix, num) { return key; } +function bpair(prefix, hash, index) { + let key = Buffer.allocUnsafe(37); + key[0] = prefix; + write(key, hash, 1); + key.writeUInt32BE(index, 33, true); + return key; +} + /* * Expose */ diff --git a/lib/coins/coinentry.js b/lib/coins/coinentry.js new file mode 100644 index 00000000..1e991054 --- /dev/null +++ b/lib/coins/coinentry.js @@ -0,0 +1,280 @@ +/*! + * coinentry.js - coin entry object for bcoin + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +const assert = require('assert'); +const Output = require('../primitives/output'); +const BufferReader = require('../utils/reader'); +const StaticWriter = require('../utils/staticwriter'); +const encoding = require('../utils/encoding'); +const Coin = require('../primitives/coin'); +const {compress, decompress} = require('../coins/compress'); + +/** + * Represents an unspent output. + * @alias module:coins.CoinEntry + * @constructor + * @property {Number} version - Transaction version. + * @property {Number} height - Transaction height (-1 if unconfirmed). + * @property {Boolean} coinbase - Whether the containing + * transaction is a coinbase. + * @property {Output} output + * @property {Boolean} spent + * @property {Buffer} raw + */ + +function CoinEntry() { + if (!(this instanceof CoinEntry)) + return new CoinEntry(); + + this.version = 1; + this.height = -1; + this.coinbase = false; + this.output = new Output(); + this.spent = false; + this.raw = null; +} + +/** + * Convert coin entry to an output. + * @returns {Output} + */ + +CoinEntry.prototype.toOutput = function toOutput() { + return this.output; +}; + +/** + * Convert coin entry to a coin. + * @param {Outpoint} prevout + * @returns {Coin} + */ + +CoinEntry.prototype.toCoin = function toCoin(prevout) { + let coin = new Coin(); + coin.version = this.version; + coin.height = this.height; + coin.coinbase = this.coinbase; + coin.script = this.output.script; + coin.value = this.output.value; + coin.hash = prevout.hash; + coin.index = prevout.index; + return coin; +}; + +/** + * Inject properties from TX. + * @param {TX} tx + * @param {Number} index + */ + +CoinEntry.prototype.fromOutput = function fromOutput(output) { + this.output = output; + return this; +}; + +/** + * Instantiate a coin from a TX + * @param {TX} tx + * @param {Number} index - Output index. + * @returns {CoinEntry} + */ + +CoinEntry.fromOutput = function fromOutput(output) { + return new CoinEntry().fromOutput(output); +}; + +/** + * Inject properties from TX. + * @param {TX} tx + * @param {Number} index + */ + +CoinEntry.prototype.fromCoin = function fromCoin(coin) { + this.version = coin.version; + this.height = coin.height; + this.coinbase = coin.coinbase; + this.output.script = coin.script; + this.output.value = coin.value; + return this; +}; + +/** + * Instantiate a coin from a TX + * @param {TX} tx + * @param {Number} index - Output index. + * @returns {CoinEntry} + */ + +CoinEntry.fromCoin = function fromCoin(coin) { + return new CoinEntry().fromCoin(coin); +}; + +/** + * Inject properties from TX. + * @param {TX} tx + * @param {Number} index + */ + +CoinEntry.prototype.fromTX = function fromTX(tx, index, height) { + assert(typeof index === 'number'); + assert(typeof height === 'number'); + assert(index >= 0 && index < tx.outputs.length); + this.version = tx.version; + this.height = height; + this.coinbase = tx.isCoinbase(); + this.output = tx.outputs[index]; + return this; +}; + +/** + * Instantiate a coin from a TX + * @param {TX} tx + * @param {Number} index - Output index. + * @returns {CoinEntry} + */ + +CoinEntry.fromTX = function fromTX(tx, index, height) { + return new CoinEntry().fromTX(tx, index, height); +}; + +/** + * Calculate size of coin. + * @returns {Number} + */ + +CoinEntry.prototype.getSize = function getSize() { + let size; + + if (this.raw) + return this.raw.length; + + size = 0; + size += encoding.sizeVarint(this.version); + size += 4; + size += compress.size(this.output); + + return size; +}; + +/** + * Write the coin to a buffer writer. + * @param {BufferWriter} bw + */ + +CoinEntry.prototype.toWriter = function toWriter(bw) { + let flags = 0; + let height = this.height; + let field; + + if (this.raw) { + bw.writeBytes(this.raw); + return bw; + } + + // We save 29 bits for the height. + // This should be good for the next 4000 years. + if (height === -1) + height = 0x1fffffff; + + if (this.coinbase) + flags |= 1; + + field = (height * 8) | flags; + + bw.writeVarint(this.version); + bw.writeU32(field); + compress.output(this.output, bw); + + return bw; +}; + +/** + * Serialize the coin. + * @returns {Buffer} + */ + +CoinEntry.prototype.toRaw = function toRaw() { + let size, bw; + + if (this.raw) + return this.raw; + + size = this.getSize(); + bw = new StaticWriter(size); + + this.toWriter(bw); + + this.raw = bw.render(); + + return this.raw; +}; + +/** + * Inject properties from serialized buffer writer. + * @private + * @param {BufferReader} br + */ + +CoinEntry.prototype.fromReader = function fromReader(br) { + let field, flags, height; + + this.version = br.readVarint(); + + field = br.readU32(); + + height = field / 8 | 0; + flags = field & 0x07; + + if (height === 0x1fffffff) + height = -1; + + this.coinbase = (flags & 1) !== 0; + this.height = height; + + decompress.output(this.output, br); + + return this; +}; + +/** + * Instantiate a coin from a serialized Buffer. + * @param {Buffer} data + * @returns {CoinEntry} + */ + +CoinEntry.fromReader = function fromReader(data) { + return new CoinEntry().fromReader(data); +}; + +/** + * Inject properties from serialized data. + * @private + * @param {Buffer} data + */ + +CoinEntry.prototype.fromRaw = function fromRaw(data) { + this.fromReader(new BufferReader(data)); + this.raw = data; + return this; +}; + +/** + * Instantiate a coin from a serialized Buffer. + * @param {Buffer} data + * @returns {CoinEntry} + */ + +CoinEntry.fromRaw = function fromRaw(data) { + return new CoinEntry().fromRaw(data); +}; + +/* + * Expose + */ + +module.exports = CoinEntry; diff --git a/lib/coins/coins.js b/lib/coins/coins.js index df706bdd..38d5a577 100644 --- a/lib/coins/coins.js +++ b/lib/coins/coins.js @@ -6,27 +6,14 @@ 'use strict'; -const util = require('../utils/util'); const assert = require('assert'); -const Coin = require('../primitives/coin'); -const Output = require('../primitives/output'); -const BufferReader = require('../utils/reader'); -const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); -const compressor = require('./compress'); -const compress = compressor.compress; -const decompress = compressor.decompress; +const CoinEntry = require('./coinentry'); /** * Represents the outputs for a single transaction. * @alias module:coins.Coins * @constructor - * @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 {CoinEntry[]} outputs - Coins. */ @@ -34,69 +21,16 @@ function Coins(options) { if (!(this instanceof Coins)) return new Coins(options); - this.version = 1; - this.hash = encoding.NULL_HASH; - this.height = -1; - this.coinbase = true; this.outputs = []; - - if (options) - this.fromOptions(options); } -/** - * Inject properties from options object. - * @private - * @param {Object} options - */ - -Coins.prototype.fromOptions = function fromOptions(options) { - if (options.version != null) { - assert(util.isUInt32(options.version)); - this.version = options.version; - } - - if (options.hash) { - assert(typeof options.hash === 'string'); - this.hash = options.hash; - } - - if (options.height != null) { - assert(util.isNumber(options.height)); - this.height = options.height; - } - - if (options.coinbase != null) { - assert(typeof options.coinbase === 'boolean'); - this.coinbase = options.coinbase; - } - - if (options.outputs) { - assert(Array.isArray(options.outputs)); - this.outputs = options.outputs; - this.cleanup(); - } - - return this; -}; - -/** - * Instantiate coins from options object. - * @param {Object} options - * @returns {Coins} - */ - -Coins.fromOptions = function fromOptions(options) { - return new Coins().fromOptions(options); -}; - /** * Add a single entry to the collection. * @param {Number} index - * @param {CoinEntry} entry + * @param {CoinEntry} coin */ -Coins.prototype.add = function add(index, entry) { +Coins.prototype.add = function add(index, coin) { assert(index >= 0); while (this.outputs.length <= index) @@ -104,7 +38,7 @@ Coins.prototype.add = function add(index, entry) { assert(!this.outputs[index]); - this.outputs[index] = entry; + this.outputs[index] = coin; }; /** @@ -142,21 +76,20 @@ Coins.prototype.has = function has(index) { }; /** - * Test whether the collection - * has an unspent coin. + * Test whether the collection has an unspent coin. * @param {Number} index * @returns {Boolean} */ Coins.prototype.isUnspent = function isUnspent(index) { - let output; + let coin; if (index >= this.outputs.length) return false; - output = this.outputs[index]; + coin = this.outputs[index]; - if (!output || output.spent) + if (!coin || coin.spent) return false; return true; @@ -182,27 +115,23 @@ Coins.prototype.get = function get(index) { */ Coins.prototype.getOutput = function getOutput(index) { - let entry = this.get(index); - - if (!entry) + if (index >= this.outputs.length) return; - return entry.toOutput(); + return this.outputs[index].output; }; /** * Get a coin. - * @param {Number} index + * @param {Outpoint} prevout * @returns {Coin} */ -Coins.prototype.getCoin = function getCoin(index) { - let entry = this.get(index); - - if (!entry) +Coins.prototype.getCoin = function getCoin(prevout) { + if (prevout.index >= this.outputs.length) return; - return entry.toCoin(this, index); + return this.outputs[prevout.index].toCoin(prevout); }; /** @@ -212,14 +141,14 @@ Coins.prototype.getCoin = function getCoin(index) { */ Coins.prototype.spend = function spend(index) { - let entry = this.get(index); + let coin = this.get(index); - if (!entry || entry.spent) + if (!coin || coin.spent) return; - entry.spent = true; + coin.spent = true; - return entry; + return coin; }; /** @@ -229,15 +158,15 @@ Coins.prototype.spend = function spend(index) { */ Coins.prototype.remove = function remove(index) { - let entry = this.get(index); + let coin = this.get(index); - if (!entry) + if (!coin) return false; this.outputs[index] = null; this.cleanup(); - return entry; + return coin; }; /** @@ -276,284 +205,6 @@ Coins.prototype.isEmpty = function isEmpty() { return this.length() === 0; }; -/* - * Coins serialization: - * version: varint - * height: uint32 - * header-code: varint - * bit 1: coinbase - * bit 2: first output unspent - * bit 3: second output unspent - * bit 4-32: spent-field size - * spent-field: bitfield (0=spent, 1=unspent) - * outputs (repeated): - * value: varint - * compressed-script: - * prefix: 0x00 = 20 byte pubkey hash - * 0x01 = 20 byte script hash - * 0x02-0x05 = 32 byte ec-key x-value - * 0x06-0x09 = reserved - * >=0x10 = varint-size + 10 | raw script - * data: script data, dictated by the prefix - * - * The compression below sacrifices some cpu in exchange - * for reduced size, but in some cases the use of varints - * actually increases speed (varint versions and values - * for example). We do as much compression as possible - * without sacrificing too much cpu. Value compression - * is intentionally excluded for now as it seems to be - * too much of a perf hit. Maybe when v8 optimizes - * non-smi arithmetic better we can enable it. - */ - -/** - * Calculate header code. - * @param {Number} len - * @param {Number} size - * @returns {Number} - */ - -Coins.prototype.header = function header(len, size) { - let first = this.isUnspent(0); - let second = this.isUnspent(1); - let offset = 0; - let code; - - // Throw if we're fully spent. - assert(len !== 0, 'Cannot serialize fully-spent coins.'); - - // First and second bits - // have a double meaning. - if (!first && !second) { - assert(size !== 0); - offset = 1; - } - - // Calculate header code. - code = 8 * (size - offset); - - if (this.coinbase) - code += 1; - - if (first) - code += 2; - - if (second) - code += 4; - - return code; -}; - -/** - * Serialize the coins object. - * @returns {Buffer} - */ - -Coins.prototype.toRaw = function toRaw() { - let len = this.length(); - let size = Math.floor((len + 5) / 8); - let code = this.header(len, size); - let total = this.getSize(len, size, code); - let bw = new StaticWriter(total); - - // Write headers. - bw.writeVarint(this.version); - bw.writeU32(this.height); - bw.writeVarint(code); - - // Write the spent field. - for (let i = 0; i < size; i++) { - let ch = 0; - for (let j = 0; j < 8 && 2 + i * 8 + j < len; j++) { - if (this.isUnspent(2 + i * 8 + j)) - ch |= 1 << j; - } - bw.writeU8(ch); - } - - // Write the compressed outputs. - for (let i = 0; i < len; i++) { - let output = this.outputs[i]; - - if (!output || output.spent) - continue; - - output.toWriter(bw); - } - - return bw.render(); -}; - -/** - * Calculate coins size. - * @param {Number} code - * @param {Number} size - * @param {Number} len - * @returns {Number} - */ - -Coins.prototype.getSize = function getSize(len, size, code) { - let total = 0; - - total += encoding.sizeVarint(this.version); - total += 4; - total += encoding.sizeVarint(code); - total += size; - - // Write the compressed outputs. - for (let i = 0; i < len; i++) { - let output = this.outputs[i]; - - if (!output || output.spent) - continue; - - total += output.getSize(); - } - - return total; -}; - -/** - * Inject data from serialized coins. - * @private - * @param {Buffer} data - * @param {Hash} hash - * @returns {Coins} - */ - -Coins.prototype.fromRaw = function fromRaw(data, hash) { - let br = new BufferReader(data); - let first = null; - let second = null; - let code, size, offset; - - // Inject hash (passed by caller). - this.hash = hash; - - // Read headers. - this.version = br.readVarint(); - this.height = br.readU32(); - code = br.readVarint(); - this.coinbase = (code & 1) !== 0; - - // Recalculate size. - size = code / 8 | 0; - - if ((code & 6) === 0) - size += 1; - - // Setup spent field. - offset = br.offset; - br.seek(size); - - // Read first two outputs. - if ((code & 2) !== 0) - first = CoinEntry.fromReader(br); - - if ((code & 4) !== 0) - second = CoinEntry.fromReader(br); - - this.outputs.push(first); - this.outputs.push(second); - - // Read outputs. - for (let i = 0; i < size; i++) { - let ch = br.data[offset++]; - for (let j = 0; j < 8; j++) { - if ((ch & (1 << j)) === 0) { - this.outputs.push(null); - continue; - } - this.outputs.push(CoinEntry.fromReader(br)); - } - } - - this.cleanup(); - - return this; -}; - -/** - * Parse a single serialized coin. - * @param {Buffer} data - * @param {Hash} hash - * @param {Number} index - * @returns {Coin} - */ - -Coins.parseCoin = function parseCoin(data, hash, index) { - let br = new BufferReader(data); - let coin = new Coin(); - let code, size, offset; - - // Inject outpoint (passed by caller). - coin.hash = hash; - coin.index = index; - - // Read headers. - coin.version = br.readVarint(); - coin.height = br.readU32(); - code = br.readVarint(); - coin.coinbase = (code & 1) !== 0; - - // Recalculate size. - size = code / 8 | 0; - - if ((code & 6) === 0) - size += 1; - - if (index >= 2 + size * 8) - return; - - // Setup spent field. - offset = br.offset; - br.seek(size); - - // Read first two outputs. - for (let i = 0; i < 2; i++) { - if ((code & (2 << i)) !== 0) { - if (index === 0) { - decompress.coin(coin, br); - return coin; - } - decompress.skip(br); - } else { - if (index === 0) - return; - } - index -= 1; - } - - // Read outputs. - for (let i = 0; i < size; i++) { - let ch = br.data[offset++]; - for (let j = 0; j < 8; j++) { - if ((ch & (1 << j)) !== 0) { - if (index === 0) { - decompress.coin(coin, br); - return coin; - } - decompress.skip(br); - } else { - if (index === 0) - return; - } - index -= 1; - } - } -}; - -/** - * Instantiate coins from a buffer. - * @param {Buffer} data - * @param {Hash} hash - Transaction hash. - * @returns {Coins} - */ - -Coins.fromRaw = function fromRaw(data, hash) { - return new Coins().fromRaw(data, hash); -}; - /** * Inject properties from tx. * @private @@ -562,21 +213,17 @@ Coins.fromRaw = function fromRaw(data, hash) { */ Coins.prototype.fromTX = function fromTX(tx, height) { - let output; - assert(typeof height === 'number'); - this.version = tx.version; - this.hash = tx.hash('hex'); - this.height = height; - this.coinbase = tx.isCoinbase(); + for (let i = 0; i < tx.outputs.length; i++) { + let output = tx.outputs[i]; - for (output of tx.outputs) { if (output.script.isUnspendable()) { this.outputs.push(null); continue; } - this.outputs.push(CoinEntry.fromOutput(output)); + + this.outputs.push(CoinEntry.fromTX(tx, i, height)); } this.cleanup(); @@ -595,169 +242,8 @@ Coins.fromTX = function fromTX(tx, height) { return new Coins().fromTX(tx, height); }; -/** - * 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 coin entry is just a - * pointer to that coin in the Coins buffer, as - * well as a size. Parsing and decompression - * is done only if that coin is being redeemed. - * @alias module:coins.CoinEntry - * @constructor - * @property {Number} offset - * @property {Number} size - * @property {Buffer} raw - * @property {Output|null} output - * @property {Boolean} spent - */ - -function CoinEntry() { - this.offset = 0; - this.size = 0; - this.raw = null; - this.output = null; - this.spent = false; -} - -/** - * Instantiate a reader at the correct offset. - * @private - * @returns {BufferReader} - */ - -CoinEntry.prototype.reader = function reader() { - let br; - - assert(this.raw); - - br = new BufferReader(this.raw); - br.offset = this.offset; - - return br; -}; - -/** - * Parse the deferred data and return a coin. - * @param {Coins} coins - * @param {Number} index - * @returns {Coin} - */ - -CoinEntry.prototype.toCoin = function toCoin(coins, index) { - let coin = new Coin(); - let output = this.toOutput(); - - // Load in all necessary properties - // from the parent Coins object. - coin.version = coins.version; - coin.coinbase = coins.coinbase; - coin.height = coins.height; - coin.hash = coins.hash; - coin.index = index; - coin.script = output.script; - coin.value = output.value; - - return coin; -}; - -/** - * Parse the deferred data and return an output. - * @returns {Output} - */ - -CoinEntry.prototype.toOutput = function toOutput() { - if (!this.output) { - this.output = new Output(); - decompress.output(this.output, this.reader()); - } - return this.output; -}; - -/** - * Calculate coin entry size. - * @returns {Number} - */ - -CoinEntry.prototype.getSize = function getSize() { - if (!this.raw) - return compress.size(this.output); - - return this.size; -}; - -/** - * Slice off the part of the buffer - * relevant to this particular coin. - */ - -CoinEntry.prototype.toWriter = function toWriter(bw) { - if (!this.raw) { - assert(this.output); - compress.output(this.output, bw); - return bw; - } - - // If we read this coin from the db and - // didn't use it, it's still in its - // compressed form. Just write it back - // as a buffer for speed. - bw.copy(this.raw, this.offset, this.offset + this.size); - - return bw; -}; - -/** - * Instantiate coin entry from reader. - * @param {BufferReader} br - * @returns {CoinEntry} - */ - -CoinEntry.fromReader = function fromReader(br) { - let entry = new CoinEntry(); - entry.offset = br.offset; - entry.size = decompress.skip(br); - entry.raw = br.data; - return entry; -}; - -/** - * Instantiate coin entry from output. - * @param {Output} output - * @returns {CoinEntry} - */ - -CoinEntry.fromOutput = function fromOutput(output) { - let entry = new CoinEntry(); - entry.output = output; - return entry; -}; - -/** - * Instantiate coin entry from coin. - * @param {Coin} coin - * @returns {CoinEntry} - */ - -CoinEntry.fromCoin = function fromCoin(coin) { - let entry = new CoinEntry(); - let output = new Output(); - output.value = coin.value; - output.script = coin.script; - entry.output = output; - return entry; -}; - /* * Expose */ -exports = Coins; -exports.Coins = Coins; -exports.CoinEntry = CoinEntry; - -module.exports = exports; +module.exports = Coins; diff --git a/lib/coins/coinview.js b/lib/coins/coinview.js index 00e4864f..e6835fbb 100644 --- a/lib/coins/coinview.js +++ b/lib/coins/coinview.js @@ -6,10 +6,9 @@ 'use strict'; -const assert = require('assert'); const Coins = require('./coins'); const UndoCoins = require('./undocoins'); -const CoinEntry = Coins.CoinEntry; +const CoinEntry = require('./coinentry'); /** * Represents a coin viewpoint: @@ -50,11 +49,12 @@ CoinView.prototype.has = function has(hash) { /** * Add coins to the collection. + * @param {Hash} hash * @param {Coins} coins */ -CoinView.prototype.add = function add(coins) { - this.map.set(coins.hash, coins); +CoinView.prototype.add = function add(hash, coins) { + this.map.set(hash, coins); return coins; }; @@ -81,7 +81,7 @@ CoinView.prototype.remove = function remove(hash) { CoinView.prototype.addTX = function addTX(tx, height) { let coins = Coins.fromTX(tx, height); - return this.add(coins); + return this.add(tx.hash('hex'), coins); }; /** @@ -92,8 +92,35 @@ CoinView.prototype.addTX = function addTX(tx, height) { CoinView.prototype.removeTX = function removeTX(tx, height) { let coins = Coins.fromTX(tx, height); - coins.outputs.length = 0; - return this.add(coins); + + for (let coin of coins.outputs) + coin.spent = true; + + return this.add(tx.hash('hex'), coins); +}; + +/** + * Add an entry to the collection. + * @param {Outpoint} prevout + * @param {CoinEntry} coin + * @returns {Coins|null} + */ + +CoinView.prototype.addEntry = function addEntry(prevout, coin) { + let coins = this.get(prevout.hash); + + if (!coins) { + coins = new Coins(); + this.add(prevout.hash, coins); + } + + if (coin.output.script.isUnspendable()) + return; + + if (!coins.has(prevout.index)) + coins.add(prevout.index, coin); + + return coins; }; /** @@ -106,10 +133,7 @@ CoinView.prototype.addCoin = function addCoin(coin) { if (!coins) { coins = new Coins(); - coins.hash = coin.hash; - coins.height = coin.height; - coins.coinbase = coin.coinbase; - this.add(coins); + this.add(coin.hash, coins); } if (coin.script.isUnspendable()) @@ -117,63 +141,59 @@ CoinView.prototype.addCoin = function addCoin(coin) { if (!coins.has(coin.index)) coins.addCoin(coin); + + return coins; }; /** * Add an output to the collection. - * @param {Hash} hash - * @param {Number} index + * @param {Outpoint} prevout * @param {Output} output */ -CoinView.prototype.addOutput = function addOutput(hash, index, output) { - let coins = this.get(hash); +CoinView.prototype.addOutput = function addOutput(prevout, output) { + let coins = this.get(prevout.hash); if (!coins) { coins = new Coins(); - coins.hash = hash; - coins.height = -1; - coins.coinbase = false; - this.add(coins); + this.add(prevout.hash, coins); } if (output.script.isUnspendable()) return; - if (!coins.has(index)) - coins.addOutput(index, output); + if (!coins.has(prevout.index)) + coins.addOutput(prevout.index, output); }; /** * Spend an output. - * @param {Hash} hash - * @param {Number} index + * @param {Outpoint} prevout * @returns {Boolean} */ -CoinView.prototype.spendOutput = function spendOutput(hash, index) { - let coins = this.get(hash); +CoinView.prototype.spendOutput = function spendOutput(prevout) { + let coins = this.get(prevout.hash); if (!coins) return false; - return this.spendFrom(coins, index); + return this.spendFrom(coins, prevout.index); }; /** * Remove an output. - * @param {Hash} hash - * @param {Number} index + * @param {Outpoint} prevout * @returns {Boolean} */ -CoinView.prototype.removeOutput = function removeOutput(hash, index) { - let coins = this.get(hash); +CoinView.prototype.removeOutput = function removeOutput(prevout) { + let coins = this.get(prevout.hash); if (!coins) return false; - return coins.remove(index); + return coins.remove(prevout.index); }; /** @@ -184,55 +204,16 @@ CoinView.prototype.removeOutput = function removeOutput(hash, index) { */ CoinView.prototype.spendFrom = function spendFrom(coins, index) { - let entry = coins.spend(index); - let undo; + let coin = coins.spend(index); - if (!entry) + if (!coin) return false; - this.undo.push(entry); - - if (coins.isEmpty()) { - undo = this.undo.top(); - undo.height = coins.height; - undo.coinbase = coins.coinbase; - undo.version = coins.version; - assert(undo.height !== -1); - } + this.undo.push(coin); return true; }; -/** - * Get a single coin by input. - * @param {Input} input - * @returns {Coin} - */ - -CoinView.prototype.getCoin = function getCoin(input) { - let coins = this.get(input.prevout.hash); - - if (!coins) - return; - - return coins.getCoin(input.prevout.index); -}; - -/** - * Get a single output by input. - * @param {Input} input - * @returns {Output} - */ - -CoinView.prototype.getOutput = function getOutput(input) { - let coins = this.get(input.prevout.hash); - - if (!coins) - return; - - return coins.getOutput(input.prevout.index); -}; - /** * Get a single entry by input. * @param {Input} input @@ -248,6 +229,36 @@ CoinView.prototype.getEntry = function getEntry(input) { return coins.get(input.prevout.index); }; +/** + * Get a single coin by input. + * @param {Input} input + * @returns {Coin} + */ + +CoinView.prototype.getCoin = function getCoin(input) { + let coins = this.get(input.prevout.hash); + + if (!coins) + return; + + return coins.getCoin(input.prevout); +}; + +/** + * Get a single output by input. + * @param {Input} input + * @returns {Output} + */ + +CoinView.prototype.getOutput = function getOutput(input) { + let coins = this.get(input.prevout.hash); + + if (!coins) + return; + + return coins.getOutput(input.prevout.index); +}; + /** * Test whether the view has an entry by input. * @param {Input} input @@ -270,12 +281,12 @@ CoinView.prototype.hasEntry = function hasEntry(input) { */ CoinView.prototype.getHeight = function getHeight(input) { - let coins = this.get(input.prevout.hash); + let coin = this.getEntry(input); - if (!coins) + if (!coin) return -1; - return coins.height; + return coin.height; }; /** @@ -285,35 +296,36 @@ CoinView.prototype.getHeight = function getHeight(input) { */ CoinView.prototype.isCoinbase = function isCoinbase(input) { - let coins = this.get(input.prevout.hash); + let coin = this.getEntry(input); - if (!coins) + if (!coin) return false; - return coins.coinbase; + return coin.coinbase; }; /** * Retrieve coins from database. * @method * @param {ChainDB} db - * @param {TX} tx + * @param {Input} input * @returns {Promise} - Returns {@link Coins}. */ -CoinView.prototype.readCoins = async function readCoins(db, hash) { - let coins = this.map.get(hash); +CoinView.prototype.readCoin = async function readCoin(db, input) { + let coin = this.hasEntry(input); + let prevout = input.prevout; - if (!coins) { - coins = await db.getCoins(hash); + if (!coin) { + coin = await db.readCoin(prevout); - if (!coins) + if (!coin) return; - this.map.set(hash, coins); + return this.addEntry(prevout, coin); } - return coins; + return this.get(prevout.hash); }; /** @@ -328,7 +340,7 @@ CoinView.prototype.ensureInputs = async function ensureInputs(db, tx) { let found = true; for (let input of tx.inputs) { - if (!(await this.readCoins(db, input.prevout.hash))) + if (!(await this.readCoin(db, input))) found = false; } @@ -345,13 +357,12 @@ CoinView.prototype.ensureInputs = async function ensureInputs(db, tx) { CoinView.prototype.spendInputs = async function spendInputs(db, tx) { for (let input of tx.inputs) { - let prevout = input.prevout; - let coins = await this.readCoins(db, prevout.hash); + let coins = await this.readCoin(db, input); if (!coins) return false; - if (!this.spendFrom(coins, prevout.index)) + if (!this.spendFrom(coins, input.prevout.index)) return false; } @@ -383,12 +394,12 @@ CoinView.prototype.getSize = function getSize(tx) { size += tx.inputs.length; for (let input of tx.inputs) { - let entry = this.getEntry(input); + let coin = this.getEntry(input); - if (!entry) + if (!coin) continue; - size += entry.getSize(); + size += coin.getSize(); } return size; @@ -403,24 +414,15 @@ CoinView.prototype.getSize = function getSize(tx) { CoinView.prototype.toWriter = function toWriter(bw, tx) { for (let input of tx.inputs) { - let prevout = input.prevout; - let coins = this.get(prevout.hash); - let entry; + let coin = this.getEntry(input); - if (!coins) { - bw.writeU8(0); - continue; - } - - entry = coins.get(prevout.index); - - if (!entry) { + if (!coin) { bw.writeU8(0); continue; } bw.writeU8(1); - entry.toWriter(bw); + coin.toWriter(bw); } return bw; @@ -435,24 +437,15 @@ CoinView.prototype.toWriter = function toWriter(bw, tx) { */ CoinView.prototype.fromReader = function fromReader(br, tx) { - for (let input of tx.inputs) { - let prevout = input.prevout; - let coins, entry; + for (let {prevout} of tx.inputs) { + let coin; if (br.readU8() === 0) continue; - coins = this.get(prevout.hash); + coin = CoinEntry.fromReader(br); - if (!coins) { - coins = new Coins(); - coins.hash = prevout.hash; - coins.coinbase = false; - this.add(coins); - } - - entry = CoinEntry.fromReader(br); - coins.add(prevout.index, entry); + this.addEntry(prevout, coin); } return this; diff --git a/lib/coins/undocoins.js b/lib/coins/undocoins.js index a8790950..2494b171 100644 --- a/lib/coins/undocoins.js +++ b/lib/coins/undocoins.js @@ -9,12 +9,7 @@ const assert = require('assert'); const BufferReader = require('../utils/reader'); const StaticWriter = require('../utils/staticwriter'); -const encoding = require('../utils/encoding'); -const Output = require('../primitives/output'); -const Coins = require('./coins'); -const compressor = require('./compress'); -const compress = compressor.compress; -const decompress = compressor.decompress; +const CoinEntry = require('../coins/coinentry'); /** * UndoCoins @@ -39,10 +34,8 @@ function UndoCoins() { * @param {CoinEntry} */ -UndoCoins.prototype.push = function push(entry) { - let undo = new UndoCoin(); - undo.entry = entry; - this.items.push(undo); +UndoCoins.prototype.push = function push(coin) { + this.items.push(coin); }; /** @@ -90,7 +83,7 @@ UndoCoins.prototype.fromRaw = function fromRaw(data) { let count = br.readU32(); for (let i = 0; i < count; i++) - this.items.push(UndoCoin.fromReader(br)); + this.items.push(CoinEntry.fromReader(br)); return this; }; @@ -137,200 +130,19 @@ UndoCoins.prototype.top = function top() { /** * Re-apply undo coins to a view, effectively unspending them. * @param {CoinView} view - * @param {Outpoint} outpoint + * @param {Outpoint} prevout */ -UndoCoins.prototype.apply = function apply(view, outpoint) { +UndoCoins.prototype.apply = function apply(view, prevout) { let undo = this.items.pop(); - let hash = outpoint.hash; - let index = outpoint.index; - let coins; assert(undo); - if (undo.height !== -1) { - coins = new Coins(); - - assert(!view.map.has(hash)); - view.map.set(hash, coins); - - coins.hash = hash; - coins.coinbase = undo.coinbase; - coins.height = undo.height; - coins.version = undo.version; - } else { - coins = view.map.get(hash); - assert(coins); - } - - coins.addOutput(index, undo.toOutput()); - - assert(coins.has(index)); -}; - -/** - * UndoCoin - * @alias module:coins.UndoCoin - * @constructor - * @property {CoinEntry|null} entry - * @property {Output|null} output - * @property {Number} version - * @property {Number} height - * @property {Boolean} coinbase - */ - -function UndoCoin() { - this.entry = null; - this.output = null; - this.version = -1; - this.height = -1; - this.coinbase = false; -} - -/** - * Convert undo coin to an output. - * @returns {Output} - */ - -UndoCoin.prototype.toOutput = function toOutput() { - if (!this.output) { - assert(this.entry); - return this.entry.toOutput(); - } - return this.output; -}; - -/** - * Calculate undo coin size. - * @returns {Number} - */ - -UndoCoin.prototype.getSize = function getSize() { - let height = this.height; - let size = 0; - - if (height === -1) - height = 0; - - size += encoding.sizeVarint(height * 2 + (this.coinbase ? 1 : 0)); - - if (this.height !== -1) - size += encoding.sizeVarint(this.version); - - if (this.entry) { - // Cached from spend. - size += this.entry.getSize(); - } else { - size += compress.size(this.output); - } - - return size; -}; - -/** - * Write the undo coin to a buffer writer. - * @param {BufferWriter} bw - */ - -UndoCoin.prototype.toWriter = function toWriter(bw) { - let height = this.height; - - assert(height !== 0); - - if (height === -1) - height = 0; - - bw.writeVarint(height * 2 + (this.coinbase ? 1 : 0)); - - if (this.height !== -1) { - assert(this.version !== -1); - bw.writeVarint(this.version); - } - - if (this.entry) { - // Cached from spend. - this.entry.toWriter(bw); - } else { - compress.output(this.output, bw); - } - - return bw; -}; - -/** - * Serialize the undo coin. - * @returns {Buffer} - */ - -UndoCoin.prototype.toRaw = function toRaw() { - let size = this.getSize(); - return this.toWriter(new StaticWriter(size)).render(); -}; - -/** - * Inject properties from buffer reader. - * @private - * @param {BufferReader} br - * @returns {UndoCoin} - */ - -UndoCoin.prototype.fromReader = function fromReader(br) { - let code = br.readVarint(); - - this.output = new Output(); - - this.height = code / 2 | 0; - - if (this.height === 0) - this.height = -1; - - this.coinbase = (code & 1) !== 0; - - if (this.height !== -1) - this.version = br.readVarint(); - - decompress.output(this.output, br); - - return this; -}; - -/** - * Inject properties from serialized data. - * @private - * @param {Buffer} data - * @returns {UndoCoin} - */ - -UndoCoin.prototype.fromRaw = function fromRaw(data) { - return this.fromReader(new BufferReader(data)); -}; - -/** - * Instantiate undo coin from serialized data. - * @param {Buffer} data - * @returns {UndoCoin} - */ - -UndoCoin.fromReader = function fromReader(br) { - return new UndoCoin().fromReader(br); -}; - -/** - * Instantiate undo coin from serialized data. - * @param {Buffer} data - * @returns {UndoCoin} - */ - -UndoCoin.fromRaw = function fromRaw(data) { - return new UndoCoin().fromRaw(data); + view.addEntry(prevout, undo); }; /* * Expose */ -exports = UndoCoins; -exports.UndoCoins = UndoCoins; -exports.UndoCoin = UndoCoin; - -module.exports = exports; +module.exports = UndoCoins; diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index c380909b..ec107791 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -1657,27 +1657,25 @@ Mempool.prototype.getSpentView = async function getSpentView(tx) { Mempool.prototype.getCoinView = async function getCoinView(tx) { let view = new CoinView(); - let prevout = tx.getPrevout(); - for (let hash of prevout) { - let entry = this.getEntry(hash); - let coins; + for (let {prevout} of tx.inputs) { + let entry = this.getEntry(prevout.hash); + let coin; if (entry) { view.addTX(entry.tx, -1); continue; } - coins = await this.chain.db.getCoins(hash); + coin = await this.chain.db.readCoin(prevout); - if (!coins) { - coins = new Coins(); - coins.hash = hash; - view.add(coins); + if (!coin) { + let coins = new Coins(); + view.add(prevout.hash, coins); continue; } - view.add(coins); + view.addEntry(prevout, coin); } return view; diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index 5c924855..aad294b4 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -1731,22 +1731,19 @@ TX.prototype.checkInputs = function checkInputs(view, height) { assert(typeof height === 'number'); for (let input of this.inputs) { - let coins = view.get(input.prevout.hash); - let coin; - - if (!coins) - return [-1, 'bad-txns-inputs-missingorspent', 0]; - - if (coins.coinbase) { - if (height - coins.height < consensus.COINBASE_MATURITY) - return [-1, 'bad-txns-premature-spend-of-coinbase', 0]; - } - - coin = coins.getOutput(input.prevout.index); + let coin = view.getEntry(input); if (!coin) return [-1, 'bad-txns-inputs-missingorspent', 0]; + if (coin.coinbase) { + if (height - coin.height < consensus.COINBASE_MATURITY) + return [-1, 'bad-txns-premature-spend-of-coinbase', 0]; + } + + coin = view.getOutput(input); + assert(coin); + if (coin.value < 0 || coin.value > consensus.MAX_MONEY) return [-1, 'bad-txns-inputvalues-outofrange', 100]; diff --git a/test/block-test.js b/test/block-test.js index e66e604f..5083dfef 100644 --- a/test/block-test.js +++ b/test/block-test.js @@ -8,7 +8,6 @@ const Headers = require('../lib/primitives/headers'); const MerkleBlock = require('../lib/primitives/merkleblock'); const CoinView = require('../lib/coins/coinview'); const Coin = require('../lib/primitives/coin'); -const Coins = require('../lib/coins/coins'); const UndoCoins = require('../lib/coins/undocoins'); const consensus = require('../lib/protocol/consensus'); const Script = require('../lib/script/script'); @@ -32,19 +31,6 @@ function applyUndo(block, undo) { for (let j = tx.inputs.length - 1; j >= 0; j--) { let input = tx.inputs[j]; - let prev = input.prevout.hash; - - if (!view.has(prev)) { - assert(!undo.isEmpty()); - - if (undo.top().height === -1) { - let coins = new Coins(); - coins.hash = prev; - coins.coinbase = false; - view.add(coins); - } - } - undo.apply(view, input.prevout); } } diff --git a/test/coins-test.js b/test/coins-test.js index 81f4281d..cc9de450 100644 --- a/test/coins-test.js +++ b/test/coins-test.js @@ -5,41 +5,26 @@ const Output = require('../lib/primitives/output'); const Input = require('../lib/primitives/input'); const Outpoint = require('../lib/primitives/outpoint'); const CoinView = require('../lib/coins/coinview'); -const Coins = require('../lib/coins/coins'); +const CoinEntry = require('../lib/coins/coinentry'); const StaticWriter = require('../lib/utils/staticwriter'); const BufferReader = require('../lib/utils/reader'); const parseTX = require('./util/common').parseTX; -let data = parseTX('data/tx1.hex'); -let tx1 = data.tx; +const data = parseTX('data/tx1.hex'); +const tx1 = data.tx; -function collect(coins) { - let outputs = []; - let i; - - for (i = 0; i < coins.outputs.length; i++) { - if (!coins.isUnspent(i)) - continue; - outputs.push(coins.getOutput(i)); - } - - return outputs; -} - -function reserialize(coins) { - let raw = coins.toRaw(); - return Coins.fromRaw(raw); +function reserialize(coin) { + let raw = coin.toRaw(); + let entry = CoinEntry.fromRaw(raw); + entry.raw = null; + return CoinEntry.fromRaw(entry.toRaw()); } function deepCoinsEqual(a, b) { - assert(a.outputs.length > 0); - assert(b.outputs.length > 0); - assert.strictEqual(a.version, b.version); assert.strictEqual(a.height, b.height); assert.strictEqual(a.coinbase, b.coinbase); - assert.strictEqual(a.length(), b.length()); - assert.deepStrictEqual(collect(a), collect(b)); + assert.deepStrictEqual(a.raw, b.raw); } describe('Coins', function() { @@ -53,18 +38,15 @@ describe('Coins', function() { view.addTX(tx1, 1); coins = view.get(hash); - - assert.equal(coins.version, 1); - assert.equal(coins.height, 1); - assert.equal(coins.coinbase, false); assert.equal(coins.outputs.length, tx1.outputs.length); entry = coins.get(0); assert(entry); assert(!entry.spent); - assert.equal(entry.offset, 0); - assert.equal(entry.size, 0); + assert.equal(entry.version, 1); + assert.equal(entry.height, 1); + assert.equal(entry.coinbase, false); assert.equal(entry.raw, null); assert(entry.output instanceof Output); assert.equal(entry.spent, false); @@ -72,7 +54,7 @@ describe('Coins', function() { output = view.getOutput(input); assert(output); - deepCoinsEqual(coins, reserialize(coins)); + deepCoinsEqual(entry, reserialize(entry)); }); it('should spend an output', () => { @@ -86,7 +68,7 @@ describe('Coins', function() { assert(coins); length = coins.length(); - view.spendOutput(hash, 0); + view.spendOutput(new Outpoint(hash, 0)); coins = view.get(hash); assert(coins); @@ -95,7 +77,7 @@ describe('Coins', function() { assert(entry); assert(entry.spent); - deepCoinsEqual(coins, reserialize(coins)); + deepCoinsEqual(entry, reserialize(entry)); assert.strictEqual(coins.length(), length); assert.equal(view.undo.items.length, 1); @@ -120,6 +102,8 @@ describe('Coins', function() { prev = tx1.inputs[0].prevout; coins = res.get(prev.hash); - assert.deepStrictEqual(coins.get(0), reserialize(coins).get(0)); + assert.strictEqual(coins.length(), 2); + assert.strictEqual(coins.get(0), null); + deepCoinsEqual(coins.get(1), reserialize(coins.get(1))); }); }); diff --git a/test/tx-test.js b/test/tx-test.js index ef0dda2e..3db8bba3 100644 --- a/test/tx-test.js +++ b/test/tx-test.js @@ -8,6 +8,7 @@ const consensus = require('../lib/protocol/consensus'); const TX = require('../lib/primitives/tx'); const Coin = require('../lib/primitives/coin'); const Output = require('../lib/primitives/output'); +const Outpoint = require('../lib/primitives/outpoint'); const Script = require('../lib/script/script'); const Witness = require('../lib/script/witness'); const Input = require('../lib/primitives/input'); @@ -349,7 +350,7 @@ describe('TX', function() { let hash = random.randomBytes(32).toString('hex'); let output = new Output(); output.value = value; - view.addOutput(hash, 0, output); + view.addOutput(new Outpoint(hash, 0), output); return { prevout: { hash: hash,