chaindb: switch to pertxout.
This commit is contained in:
parent
396ba5ee5f
commit
a1af3ab980
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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
280
lib/coins/coinentry.js
Normal 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;
|
||||||
@ -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;
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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];
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user