/*! * undocoins.js - undocoins object for bcoin * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; var assert = require('assert'); var BufferReader = require('../utils/reader'); var StaticWriter = require('../utils/staticwriter'); var encoding = require('../utils/encoding'); var Output = require('../primitives/output'); var Coins = require('./coins'); var compressor = require('./compress'); var compress = compressor.compress; var decompress = compressor.decompress; /** * UndoCoins * Coins need to be resurrected from somewhere * during a reorg. The undo coins store all * spent coins in a single record per block * (in a compressed format). * @alias module:coins.UndoCoins * @constructor * @property {UndoCoin[]} items */ function UndoCoins() { if (!(this instanceof UndoCoins)) return new UndoCoins(); this.items = []; } /** * Push coin entry onto undo coin array. * @param {CoinEntry} */ UndoCoins.prototype.push = function push(entry) { var undo = new UndoCoin(); undo.entry = entry; this.items.push(undo); }; /** * Calculate undo coins size. * @returns {Number} */ UndoCoins.prototype.getSize = function getSize() { var size = 0; var i, coin; size += 4; for (i = 0; i < this.items.length; i++) { coin = this.items[i]; size += coin.getSize(); } return size; }; /** * Serialize all undo coins. * @returns {Buffer} */ UndoCoins.prototype.toRaw = function toRaw() { var size = this.getSize(); var bw = new StaticWriter(size); var i, coin; bw.writeU32(this.items.length); for (i = 0; i < this.items.length; i++) { coin = this.items[i]; coin.toWriter(bw); } return bw.render(); }; /** * Inject properties from serialized data. * @private * @param {Buffer} data * @returns {UndoCoins} */ UndoCoins.prototype.fromRaw = function fromRaw(data) { var br = new BufferReader(data); var count = br.readU32(); var i; for (i = 0; i < count; i++) this.items.push(UndoCoin.fromReader(br)); return this; }; /** * Instantiate undo coins from serialized data. * @param {Buffer} data * @returns {UndoCoins} */ UndoCoins.fromRaw = function fromRaw(data) { return new UndoCoins().fromRaw(data); }; /** * Test whether the undo coins have any members. * @returns {Boolean} */ UndoCoins.prototype.isEmpty = function isEmpty() { return this.items.length === 0; }; /** * Render the undo coins. * @returns {Buffer} */ UndoCoins.prototype.commit = function commit() { var raw = this.toRaw(); this.items.length = 0; return raw; }; /** * Retrieve the last undo coin. * @returns {UndoCoin} */ UndoCoins.prototype.top = function top() { return this.items[this.items.length - 1]; }; /** * Re-apply undo coins to a view, effectively unspending them. * @param {CoinView} view * @param {Outpoint} outpoint */ UndoCoins.prototype.apply = function apply(view, outpoint) { var undo = this.items.pop(); var hash = outpoint.hash; var index = outpoint.index; var coins; assert(undo); if (undo.height !== -1) { coins = new Coins(); assert(!view.map[hash]); view.map[hash] = coins; coins.hash = hash; coins.coinbase = undo.coinbase; coins.height = undo.height; coins.version = undo.version; } else { coins = view.map[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() { var height = this.height; var 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) { var 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() { var 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) { var 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 */ exports = UndoCoins; exports.UndoCoins = UndoCoins; exports.UndoCoin = UndoCoin; module.exports = exports;