chaindb: switch to pertxout.

This commit is contained in:
Christopher Jeffrey 2017-07-02 18:10:05 -07:00
parent 396ba5ee5f
commit a1af3ab980
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
12 changed files with 552 additions and 1012 deletions

View File

@ -25,6 +25,7 @@ const Outpoint = require('../primitives/outpoint');
const Address = require('../primitives/address'); const Address = require('../primitives/address');
const ChainEntry = require('./chainentry'); const ChainEntry = require('./chainentry');
const TXMeta = require('../primitives/txmeta'); const TXMeta = require('../primitives/txmeta');
const CoinEntry = require('../coins/coinentry');
const U8 = encoding.U8; const U8 = encoding.U8;
const U32 = encoding.U32; const U32 = encoding.U32;
@ -83,7 +84,7 @@ ChainDB.prototype.open = async function open() {
this.logger.info('Opening ChainDB...'); this.logger.info('Opening ChainDB...');
await this.db.open(); await this.db.open();
await this.db.checkVersion('V', 2); await this.db.checkVersion('V', 3);
state = await this.getState(); 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). * Get a coin (unspents only).
* @method * @method
@ -848,52 +883,9 @@ ChainDB.prototype.getTips = function getTips() {
*/ */
ChainDB.prototype.getCoin = async function getCoin(hash, index) { ChainDB.prototype.getCoin = async function getCoin(hash, index) {
let state = this.state; let prevout = new Outpoint(hash, index);
let raw; let coin = await this.readCoin(prevout);
return coin.toCoin(prevout);
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);
}; };
/** /**
@ -903,8 +895,18 @@ ChainDB.prototype.getCoins = async function getCoins(hash) {
* @returns {Promise} - Returns Boolean. * @returns {Promise} - Returns Boolean.
*/ */
ChainDB.prototype.hasCoins = function hasCoins(hash) { ChainDB.prototype.hasCoins = async function hasCoins(hash) {
return this.db.has(layout.c(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) { ChainDB.prototype.getCoinView = async function getCoinView(tx) {
let view = new CoinView(); let view = new CoinView();
let prevout = tx.getPrevout();
for (let hash of prevout) { for (let {prevout} of tx.inputs) {
let coins = await this.getCoins(hash); let coin = await this.readCoin(prevout);
if (!coins) { if (!coin) {
coins = new Coins(); let coins = new Coins();
coins.hash = hash; view.add(prevout.hash, coins);
view.add(coins);
continue; continue;
} }
view.add(coins); view.addEntry(prevout, coin);
} }
return view; return view;
@ -944,13 +944,13 @@ ChainDB.prototype.getCoinView = async function getCoinView(tx) {
ChainDB.prototype.getSpentView = async function getSpentView(tx) { ChainDB.prototype.getSpentView = async function getSpentView(tx) {
let view = await this.getCoinView(tx); let view = await this.getCoinView(tx);
for (let coins of view.map.values()) { for (let [hash, coins] of view.map) {
let meta; let meta;
if (!coins.isEmpty()) if (!coins.isEmpty())
continue; continue;
meta = await this.getMeta(coins.hash); meta = await this.getMeta(hash);
if (!meta) if (!meta)
continue; continue;
@ -1031,19 +1031,6 @@ ChainDB.prototype.getBlockView = async function getBlockView(block) {
for (let j = tx.inputs.length - 1; j >= 0; j--) { for (let j = tx.inputs.length - 1; j >= 0; j--) {
let input = tx.inputs[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); undo.apply(view, input.prevout);
} }
} }
@ -1697,14 +1684,24 @@ ChainDB.prototype.removeBlock = async function removeBlock(entry) {
*/ */
ChainDB.prototype.saveView = function saveView(view) { ChainDB.prototype.saveView = function saveView(view) {
for (let coins of view.map.values()) { for (let [hash, coins] of view.map) {
if (coins.isEmpty()) { for (let i = 0; i < coins.outputs.length; i++) {
this.del(layout.c(coins.hash)); let coin = coins.outputs[i];
this.coinCache.unpush(coins.hash); let raw;
} else {
let raw = coins.toRaw(); if (!coin)
this.put(layout.c(coins.hash), raw); continue;
this.coinCache.push(coins.hash, raw);
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]; let tx = block.txs[i];
if (i > 0) { if (i > 0) {
await view.ensureInputs(this, tx);
for (let j = tx.inputs.length - 1; j >= 0; j--) { for (let j = tx.inputs.length - 1; j >= 0; j--) {
let input = tx.inputs[j]; let input = tx.inputs[j];
undo.apply(view, input.prevout); undo.apply(view, input.prevout);

View File

@ -36,8 +36,8 @@ const layout = {
t: function t(hash) { t: function t(hash) {
return 't' + hex(hash); return 't' + hex(hash);
}, },
c: function c(hash) { c: function c(hash, index) {
return 'c' + hex(hash); return 'c' + hex(hash) + pad32(index);
}, },
u: function u(hash) { u: function u(hash) {
return 'u' + hex(hash); return 'u' + hex(hash);

View File

@ -53,8 +53,8 @@ const layout = {
t: function t(hash) { t: function t(hash) {
return pair(0x74, hash); return pair(0x74, hash);
}, },
c: function c(hash) { c: function c(hash, index) {
return pair(0x63, hash); return bpair(0x63, hash, index);
}, },
u: function u(hash) { u: function u(hash) {
return pair(0x75, hash); return pair(0x75, hash);
@ -160,6 +160,14 @@ function ipair(prefix, num) {
return key; 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 * Expose
*/ */

280
lib/coins/coinentry.js Normal file
View File

@ -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;

View File

@ -6,27 +6,14 @@
'use strict'; 'use strict';
const util = require('../utils/util');
const assert = require('assert'); const assert = require('assert');
const Coin = require('../primitives/coin'); const CoinEntry = require('./coinentry');
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;
/** /**
* Represents the outputs for a single transaction. * Represents the outputs for a single transaction.
* @alias module:coins.Coins * @alias module:coins.Coins
* @constructor * @constructor
* @param {Object?} options - Options object.
* @property {Hash} hash - Transaction hash. * @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. * @property {CoinEntry[]} outputs - Coins.
*/ */
@ -34,69 +21,16 @@ function Coins(options) {
if (!(this instanceof Coins)) if (!(this instanceof Coins))
return new Coins(options); return new Coins(options);
this.version = 1;
this.hash = encoding.NULL_HASH;
this.height = -1;
this.coinbase = true;
this.outputs = []; 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. * Add a single entry to the collection.
* @param {Number} index * @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); assert(index >= 0);
while (this.outputs.length <= index) while (this.outputs.length <= index)
@ -104,7 +38,7 @@ Coins.prototype.add = function add(index, entry) {
assert(!this.outputs[index]); 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 * Test whether the collection has an unspent coin.
* has an unspent coin.
* @param {Number} index * @param {Number} index
* @returns {Boolean} * @returns {Boolean}
*/ */
Coins.prototype.isUnspent = function isUnspent(index) { Coins.prototype.isUnspent = function isUnspent(index) {
let output; let coin;
if (index >= this.outputs.length) if (index >= this.outputs.length)
return false; return false;
output = this.outputs[index]; coin = this.outputs[index];
if (!output || output.spent) if (!coin || coin.spent)
return false; return false;
return true; return true;
@ -182,27 +115,23 @@ Coins.prototype.get = function get(index) {
*/ */
Coins.prototype.getOutput = function getOutput(index) { Coins.prototype.getOutput = function getOutput(index) {
let entry = this.get(index); if (index >= this.outputs.length)
if (!entry)
return; return;
return entry.toOutput(); return this.outputs[index].output;
}; };
/** /**
* Get a coin. * Get a coin.
* @param {Number} index * @param {Outpoint} prevout
* @returns {Coin} * @returns {Coin}
*/ */
Coins.prototype.getCoin = function getCoin(index) { Coins.prototype.getCoin = function getCoin(prevout) {
let entry = this.get(index); if (prevout.index >= this.outputs.length)
if (!entry)
return; 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) { 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; 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) { Coins.prototype.remove = function remove(index) {
let entry = this.get(index); let coin = this.get(index);
if (!entry) if (!coin)
return false; return false;
this.outputs[index] = null; this.outputs[index] = null;
this.cleanup(); this.cleanup();
return entry; return coin;
}; };
/** /**
@ -276,284 +205,6 @@ Coins.prototype.isEmpty = function isEmpty() {
return this.length() === 0; 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. * Inject properties from tx.
* @private * @private
@ -562,21 +213,17 @@ Coins.fromRaw = function fromRaw(data, hash) {
*/ */
Coins.prototype.fromTX = function fromTX(tx, height) { Coins.prototype.fromTX = function fromTX(tx, height) {
let output;
assert(typeof height === 'number'); assert(typeof height === 'number');
this.version = tx.version; for (let i = 0; i < tx.outputs.length; i++) {
this.hash = tx.hash('hex'); let output = tx.outputs[i];
this.height = height;
this.coinbase = tx.isCoinbase();
for (output of tx.outputs) {
if (output.script.isUnspendable()) { if (output.script.isUnspendable()) {
this.outputs.push(null); this.outputs.push(null);
continue; continue;
} }
this.outputs.push(CoinEntry.fromOutput(output));
this.outputs.push(CoinEntry.fromTX(tx, i, height));
} }
this.cleanup(); this.cleanup();
@ -595,169 +242,8 @@ Coins.fromTX = function fromTX(tx, height) {
return new Coins().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 * Expose
*/ */
exports = Coins; module.exports = Coins;
exports.Coins = Coins;
exports.CoinEntry = CoinEntry;
module.exports = exports;

View File

@ -6,10 +6,9 @@
'use strict'; 'use strict';
const assert = require('assert');
const Coins = require('./coins'); const Coins = require('./coins');
const UndoCoins = require('./undocoins'); const UndoCoins = require('./undocoins');
const CoinEntry = Coins.CoinEntry; const CoinEntry = require('./coinentry');
/** /**
* Represents a coin viewpoint: * Represents a coin viewpoint:
@ -50,11 +49,12 @@ CoinView.prototype.has = function has(hash) {
/** /**
* Add coins to the collection. * Add coins to the collection.
* @param {Hash} hash
* @param {Coins} coins * @param {Coins} coins
*/ */
CoinView.prototype.add = function add(coins) { CoinView.prototype.add = function add(hash, coins) {
this.map.set(coins.hash, coins); this.map.set(hash, coins);
return coins; return coins;
}; };
@ -81,7 +81,7 @@ CoinView.prototype.remove = function remove(hash) {
CoinView.prototype.addTX = function addTX(tx, height) { CoinView.prototype.addTX = function addTX(tx, height) {
let coins = Coins.fromTX(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) { CoinView.prototype.removeTX = function removeTX(tx, height) {
let coins = Coins.fromTX(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) { if (!coins) {
coins = new Coins(); coins = new Coins();
coins.hash = coin.hash; this.add(coin.hash, coins);
coins.height = coin.height;
coins.coinbase = coin.coinbase;
this.add(coins);
} }
if (coin.script.isUnspendable()) if (coin.script.isUnspendable())
@ -117,63 +141,59 @@ CoinView.prototype.addCoin = function addCoin(coin) {
if (!coins.has(coin.index)) if (!coins.has(coin.index))
coins.addCoin(coin); coins.addCoin(coin);
return coins;
}; };
/** /**
* Add an output to the collection. * Add an output to the collection.
* @param {Hash} hash * @param {Outpoint} prevout
* @param {Number} index
* @param {Output} output * @param {Output} output
*/ */
CoinView.prototype.addOutput = function addOutput(hash, index, output) { CoinView.prototype.addOutput = function addOutput(prevout, output) {
let coins = this.get(hash); let coins = this.get(prevout.hash);
if (!coins) { if (!coins) {
coins = new Coins(); coins = new Coins();
coins.hash = hash; this.add(prevout.hash, coins);
coins.height = -1;
coins.coinbase = false;
this.add(coins);
} }
if (output.script.isUnspendable()) if (output.script.isUnspendable())
return; return;
if (!coins.has(index)) if (!coins.has(prevout.index))
coins.addOutput(index, output); coins.addOutput(prevout.index, output);
}; };
/** /**
* Spend an output. * Spend an output.
* @param {Hash} hash * @param {Outpoint} prevout
* @param {Number} index
* @returns {Boolean} * @returns {Boolean}
*/ */
CoinView.prototype.spendOutput = function spendOutput(hash, index) { CoinView.prototype.spendOutput = function spendOutput(prevout) {
let coins = this.get(hash); let coins = this.get(prevout.hash);
if (!coins) if (!coins)
return false; return false;
return this.spendFrom(coins, index); return this.spendFrom(coins, prevout.index);
}; };
/** /**
* Remove an output. * Remove an output.
* @param {Hash} hash * @param {Outpoint} prevout
* @param {Number} index
* @returns {Boolean} * @returns {Boolean}
*/ */
CoinView.prototype.removeOutput = function removeOutput(hash, index) { CoinView.prototype.removeOutput = function removeOutput(prevout) {
let coins = this.get(hash); let coins = this.get(prevout.hash);
if (!coins) if (!coins)
return false; 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) { CoinView.prototype.spendFrom = function spendFrom(coins, index) {
let entry = coins.spend(index); let coin = coins.spend(index);
let undo;
if (!entry) if (!coin)
return false; return false;
this.undo.push(entry); this.undo.push(coin);
if (coins.isEmpty()) {
undo = this.undo.top();
undo.height = coins.height;
undo.coinbase = coins.coinbase;
undo.version = coins.version;
assert(undo.height !== -1);
}
return true; 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. * Get a single entry by input.
* @param {Input} input * @param {Input} input
@ -248,6 +229,36 @@ CoinView.prototype.getEntry = function getEntry(input) {
return coins.get(input.prevout.index); 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. * Test whether the view has an entry by input.
* @param {Input} input * @param {Input} input
@ -270,12 +281,12 @@ CoinView.prototype.hasEntry = function hasEntry(input) {
*/ */
CoinView.prototype.getHeight = function getHeight(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 -1;
return coins.height; return coin.height;
}; };
/** /**
@ -285,35 +296,36 @@ CoinView.prototype.getHeight = function getHeight(input) {
*/ */
CoinView.prototype.isCoinbase = function isCoinbase(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 false;
return coins.coinbase; return coin.coinbase;
}; };
/** /**
* Retrieve coins from database. * Retrieve coins from database.
* @method * @method
* @param {ChainDB} db * @param {ChainDB} db
* @param {TX} tx * @param {Input} input
* @returns {Promise} - Returns {@link Coins}. * @returns {Promise} - Returns {@link Coins}.
*/ */
CoinView.prototype.readCoins = async function readCoins(db, hash) { CoinView.prototype.readCoin = async function readCoin(db, input) {
let coins = this.map.get(hash); let coin = this.hasEntry(input);
let prevout = input.prevout;
if (!coins) { if (!coin) {
coins = await db.getCoins(hash); coin = await db.readCoin(prevout);
if (!coins) if (!coin)
return; 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; let found = true;
for (let input of tx.inputs) { for (let input of tx.inputs) {
if (!(await this.readCoins(db, input.prevout.hash))) if (!(await this.readCoin(db, input)))
found = false; found = false;
} }
@ -345,13 +357,12 @@ CoinView.prototype.ensureInputs = async function ensureInputs(db, tx) {
CoinView.prototype.spendInputs = async function spendInputs(db, tx) { CoinView.prototype.spendInputs = async function spendInputs(db, tx) {
for (let input of tx.inputs) { for (let input of tx.inputs) {
let prevout = input.prevout; let coins = await this.readCoin(db, input);
let coins = await this.readCoins(db, prevout.hash);
if (!coins) if (!coins)
return false; return false;
if (!this.spendFrom(coins, prevout.index)) if (!this.spendFrom(coins, input.prevout.index))
return false; return false;
} }
@ -383,12 +394,12 @@ CoinView.prototype.getSize = function getSize(tx) {
size += tx.inputs.length; size += tx.inputs.length;
for (let input of tx.inputs) { for (let input of tx.inputs) {
let entry = this.getEntry(input); let coin = this.getEntry(input);
if (!entry) if (!coin)
continue; continue;
size += entry.getSize(); size += coin.getSize();
} }
return size; return size;
@ -403,24 +414,15 @@ CoinView.prototype.getSize = function getSize(tx) {
CoinView.prototype.toWriter = function toWriter(bw, tx) { CoinView.prototype.toWriter = function toWriter(bw, tx) {
for (let input of tx.inputs) { for (let input of tx.inputs) {
let prevout = input.prevout; let coin = this.getEntry(input);
let coins = this.get(prevout.hash);
let entry;
if (!coins) { if (!coin) {
bw.writeU8(0);
continue;
}
entry = coins.get(prevout.index);
if (!entry) {
bw.writeU8(0); bw.writeU8(0);
continue; continue;
} }
bw.writeU8(1); bw.writeU8(1);
entry.toWriter(bw); coin.toWriter(bw);
} }
return bw; return bw;
@ -435,24 +437,15 @@ CoinView.prototype.toWriter = function toWriter(bw, tx) {
*/ */
CoinView.prototype.fromReader = function fromReader(br, tx) { CoinView.prototype.fromReader = function fromReader(br, tx) {
for (let input of tx.inputs) { for (let {prevout} of tx.inputs) {
let prevout = input.prevout; let coin;
let coins, entry;
if (br.readU8() === 0) if (br.readU8() === 0)
continue; continue;
coins = this.get(prevout.hash); coin = CoinEntry.fromReader(br);
if (!coins) { this.addEntry(prevout, coin);
coins = new Coins();
coins.hash = prevout.hash;
coins.coinbase = false;
this.add(coins);
}
entry = CoinEntry.fromReader(br);
coins.add(prevout.index, entry);
} }
return this; return this;

View File

@ -9,12 +9,7 @@
const assert = require('assert'); const assert = require('assert');
const BufferReader = require('../utils/reader'); const BufferReader = require('../utils/reader');
const StaticWriter = require('../utils/staticwriter'); const StaticWriter = require('../utils/staticwriter');
const encoding = require('../utils/encoding'); const CoinEntry = require('../coins/coinentry');
const Output = require('../primitives/output');
const Coins = require('./coins');
const compressor = require('./compress');
const compress = compressor.compress;
const decompress = compressor.decompress;
/** /**
* UndoCoins * UndoCoins
@ -39,10 +34,8 @@ function UndoCoins() {
* @param {CoinEntry} * @param {CoinEntry}
*/ */
UndoCoins.prototype.push = function push(entry) { UndoCoins.prototype.push = function push(coin) {
let undo = new UndoCoin(); this.items.push(coin);
undo.entry = entry;
this.items.push(undo);
}; };
/** /**
@ -90,7 +83,7 @@ UndoCoins.prototype.fromRaw = function fromRaw(data) {
let count = br.readU32(); let count = br.readU32();
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)
this.items.push(UndoCoin.fromReader(br)); this.items.push(CoinEntry.fromReader(br));
return this; return this;
}; };
@ -137,200 +130,19 @@ UndoCoins.prototype.top = function top() {
/** /**
* Re-apply undo coins to a view, effectively unspending them. * Re-apply undo coins to a view, effectively unspending them.
* @param {CoinView} view * @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 undo = this.items.pop();
let hash = outpoint.hash;
let index = outpoint.index;
let coins;
assert(undo); assert(undo);
if (undo.height !== -1) { view.addEntry(prevout, undo);
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);
}; };
/* /*
* Expose * Expose
*/ */
exports = UndoCoins; module.exports = UndoCoins;
exports.UndoCoins = UndoCoins;
exports.UndoCoin = UndoCoin;
module.exports = exports;

View File

@ -1657,27 +1657,25 @@ Mempool.prototype.getSpentView = async function getSpentView(tx) {
Mempool.prototype.getCoinView = async function getCoinView(tx) { Mempool.prototype.getCoinView = async function getCoinView(tx) {
let view = new CoinView(); let view = new CoinView();
let prevout = tx.getPrevout();
for (let hash of prevout) { for (let {prevout} of tx.inputs) {
let entry = this.getEntry(hash); let entry = this.getEntry(prevout.hash);
let coins; let coin;
if (entry) { if (entry) {
view.addTX(entry.tx, -1); view.addTX(entry.tx, -1);
continue; continue;
} }
coins = await this.chain.db.getCoins(hash); coin = await this.chain.db.readCoin(prevout);
if (!coins) { if (!coin) {
coins = new Coins(); let coins = new Coins();
coins.hash = hash; view.add(prevout.hash, coins);
view.add(coins);
continue; continue;
} }
view.add(coins); view.addEntry(prevout, coin);
} }
return view; return view;

View File

@ -1731,22 +1731,19 @@ TX.prototype.checkInputs = function checkInputs(view, height) {
assert(typeof height === 'number'); assert(typeof height === 'number');
for (let input of this.inputs) { for (let input of this.inputs) {
let coins = view.get(input.prevout.hash); let coin = view.getEntry(input);
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);
if (!coin) if (!coin)
return [-1, 'bad-txns-inputs-missingorspent', 0]; 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) if (coin.value < 0 || coin.value > consensus.MAX_MONEY)
return [-1, 'bad-txns-inputvalues-outofrange', 100]; return [-1, 'bad-txns-inputvalues-outofrange', 100];

View File

@ -8,7 +8,6 @@ const Headers = require('../lib/primitives/headers');
const MerkleBlock = require('../lib/primitives/merkleblock'); const MerkleBlock = require('../lib/primitives/merkleblock');
const CoinView = require('../lib/coins/coinview'); const CoinView = require('../lib/coins/coinview');
const Coin = require('../lib/primitives/coin'); const Coin = require('../lib/primitives/coin');
const Coins = require('../lib/coins/coins');
const UndoCoins = require('../lib/coins/undocoins'); const UndoCoins = require('../lib/coins/undocoins');
const consensus = require('../lib/protocol/consensus'); const consensus = require('../lib/protocol/consensus');
const Script = require('../lib/script/script'); const Script = require('../lib/script/script');
@ -32,19 +31,6 @@ function applyUndo(block, undo) {
for (let j = tx.inputs.length - 1; j >= 0; j--) { for (let j = tx.inputs.length - 1; j >= 0; j--) {
let input = tx.inputs[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); undo.apply(view, input.prevout);
} }
} }

View File

@ -5,41 +5,26 @@ const Output = require('../lib/primitives/output');
const Input = require('../lib/primitives/input'); const Input = require('../lib/primitives/input');
const Outpoint = require('../lib/primitives/outpoint'); const Outpoint = require('../lib/primitives/outpoint');
const CoinView = require('../lib/coins/coinview'); 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 StaticWriter = require('../lib/utils/staticwriter');
const BufferReader = require('../lib/utils/reader'); const BufferReader = require('../lib/utils/reader');
const parseTX = require('./util/common').parseTX; const parseTX = require('./util/common').parseTX;
let data = parseTX('data/tx1.hex'); const data = parseTX('data/tx1.hex');
let tx1 = data.tx; const tx1 = data.tx;
function collect(coins) { function reserialize(coin) {
let outputs = []; let raw = coin.toRaw();
let i; let entry = CoinEntry.fromRaw(raw);
entry.raw = null;
for (i = 0; i < coins.outputs.length; i++) { return CoinEntry.fromRaw(entry.toRaw());
if (!coins.isUnspent(i))
continue;
outputs.push(coins.getOutput(i));
}
return outputs;
}
function reserialize(coins) {
let raw = coins.toRaw();
return Coins.fromRaw(raw);
} }
function deepCoinsEqual(a, b) { function deepCoinsEqual(a, b) {
assert(a.outputs.length > 0);
assert(b.outputs.length > 0);
assert.strictEqual(a.version, b.version); assert.strictEqual(a.version, b.version);
assert.strictEqual(a.height, b.height); assert.strictEqual(a.height, b.height);
assert.strictEqual(a.coinbase, b.coinbase); assert.strictEqual(a.coinbase, b.coinbase);
assert.strictEqual(a.length(), b.length()); assert.deepStrictEqual(a.raw, b.raw);
assert.deepStrictEqual(collect(a), collect(b));
} }
describe('Coins', function() { describe('Coins', function() {
@ -53,18 +38,15 @@ describe('Coins', function() {
view.addTX(tx1, 1); view.addTX(tx1, 1);
coins = view.get(hash); 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); assert.equal(coins.outputs.length, tx1.outputs.length);
entry = coins.get(0); entry = coins.get(0);
assert(entry); assert(entry);
assert(!entry.spent); assert(!entry.spent);
assert.equal(entry.offset, 0); assert.equal(entry.version, 1);
assert.equal(entry.size, 0); assert.equal(entry.height, 1);
assert.equal(entry.coinbase, false);
assert.equal(entry.raw, null); assert.equal(entry.raw, null);
assert(entry.output instanceof Output); assert(entry.output instanceof Output);
assert.equal(entry.spent, false); assert.equal(entry.spent, false);
@ -72,7 +54,7 @@ describe('Coins', function() {
output = view.getOutput(input); output = view.getOutput(input);
assert(output); assert(output);
deepCoinsEqual(coins, reserialize(coins)); deepCoinsEqual(entry, reserialize(entry));
}); });
it('should spend an output', () => { it('should spend an output', () => {
@ -86,7 +68,7 @@ describe('Coins', function() {
assert(coins); assert(coins);
length = coins.length(); length = coins.length();
view.spendOutput(hash, 0); view.spendOutput(new Outpoint(hash, 0));
coins = view.get(hash); coins = view.get(hash);
assert(coins); assert(coins);
@ -95,7 +77,7 @@ describe('Coins', function() {
assert(entry); assert(entry);
assert(entry.spent); assert(entry.spent);
deepCoinsEqual(coins, reserialize(coins)); deepCoinsEqual(entry, reserialize(entry));
assert.strictEqual(coins.length(), length); assert.strictEqual(coins.length(), length);
assert.equal(view.undo.items.length, 1); assert.equal(view.undo.items.length, 1);
@ -120,6 +102,8 @@ describe('Coins', function() {
prev = tx1.inputs[0].prevout; prev = tx1.inputs[0].prevout;
coins = res.get(prev.hash); 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)));
}); });
}); });

View File

@ -8,6 +8,7 @@ const consensus = require('../lib/protocol/consensus');
const TX = require('../lib/primitives/tx'); const TX = require('../lib/primitives/tx');
const Coin = require('../lib/primitives/coin'); const Coin = require('../lib/primitives/coin');
const Output = require('../lib/primitives/output'); const Output = require('../lib/primitives/output');
const Outpoint = require('../lib/primitives/outpoint');
const Script = require('../lib/script/script'); const Script = require('../lib/script/script');
const Witness = require('../lib/script/witness'); const Witness = require('../lib/script/witness');
const Input = require('../lib/primitives/input'); const Input = require('../lib/primitives/input');
@ -349,7 +350,7 @@ describe('TX', function() {
let hash = random.randomBytes(32).toString('hex'); let hash = random.randomBytes(32).toString('hex');
let output = new Output(); let output = new Output();
output.value = value; output.value = value;
view.addOutput(hash, 0, output); view.addOutput(new Outpoint(hash, 0), output);
return { return {
prevout: { prevout: {
hash: hash, hash: hash,