339 lines
6.3 KiB
JavaScript
339 lines
6.3 KiB
JavaScript
/*!
|
|
* undocoins.js - undocoins object for bcoin
|
|
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
/* eslint-disable */
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const BufferReader = require('../../lib/utils/reader');
|
|
const StaticWriter = require('../../lib/utils/staticwriter');
|
|
const encoding = require('../../lib/utils/encoding');
|
|
const Output = require('../../lib/primitives/output');
|
|
const Coins = require('./coins');
|
|
const compressor = require('./compress');
|
|
const compress = compressor.compress;
|
|
const 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) {
|
|
const undo = new UndoCoin();
|
|
undo.entry = entry;
|
|
this.items.push(undo);
|
|
};
|
|
|
|
/**
|
|
* Calculate undo coins size.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
UndoCoins.prototype.getSize = function getSize() {
|
|
let size = 0;
|
|
|
|
size += 4;
|
|
|
|
for (const coin of this.items)
|
|
size += coin.getSize();
|
|
|
|
return size;
|
|
};
|
|
|
|
/**
|
|
* Serialize all undo coins.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
UndoCoins.prototype.toRaw = function toRaw() {
|
|
const size = this.getSize();
|
|
const bw = new StaticWriter(size);
|
|
|
|
bw.writeU32(this.items.length);
|
|
|
|
for (const coin of this.items)
|
|
coin.toWriter(bw);
|
|
|
|
return bw.render();
|
|
};
|
|
|
|
/**
|
|
* Inject properties from serialized data.
|
|
* @private
|
|
* @param {Buffer} data
|
|
* @returns {UndoCoins}
|
|
*/
|
|
|
|
UndoCoins.prototype.fromRaw = function fromRaw(data) {
|
|
const br = new BufferReader(data);
|
|
const count = br.readU32();
|
|
|
|
for (let 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() {
|
|
const 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) {
|
|
const undo = this.items.pop();
|
|
const hash = outpoint.hash;
|
|
const index = outpoint.index;
|
|
let coins;
|
|
|
|
assert(undo);
|
|
|
|
if (undo.height !== -1) {
|
|
coins = new Coins();
|
|
|
|
assert(!view.map.has(hash));
|
|
view.map.set(hash, coins);
|
|
|
|
coins.hash = hash;
|
|
coins.coinbase = undo.coinbase;
|
|
coins.height = undo.height;
|
|
coins.version = undo.version;
|
|
} else {
|
|
coins = view.map.get(hash);
|
|
assert(coins);
|
|
}
|
|
|
|
coins.addOutput(index, undo.toOutput());
|
|
|
|
assert(coins.has(index));
|
|
};
|
|
|
|
/**
|
|
* UndoCoin
|
|
* @alias module:coins.UndoCoin
|
|
* @constructor
|
|
* @property {CoinEntry|null} entry
|
|
* @property {Output|null} output
|
|
* @property {Number} version
|
|
* @property {Number} height
|
|
* @property {Boolean} coinbase
|
|
*/
|
|
|
|
function UndoCoin() {
|
|
this.entry = null;
|
|
this.output = null;
|
|
this.version = -1;
|
|
this.height = -1;
|
|
this.coinbase = false;
|
|
}
|
|
|
|
/**
|
|
* Convert undo coin to an output.
|
|
* @returns {Output}
|
|
*/
|
|
|
|
UndoCoin.prototype.toOutput = function toOutput() {
|
|
if (!this.output) {
|
|
assert(this.entry);
|
|
return this.entry.toOutput();
|
|
}
|
|
return this.output;
|
|
};
|
|
|
|
/**
|
|
* Calculate undo coin size.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
UndoCoin.prototype.getSize = function getSize() {
|
|
let height = this.height;
|
|
let size = 0;
|
|
|
|
if (height === -1)
|
|
height = 0;
|
|
|
|
size += encoding.sizeVarint(height * 2 + (this.coinbase ? 1 : 0));
|
|
|
|
if (this.height !== -1)
|
|
size += encoding.sizeVarint(this.version);
|
|
|
|
if (this.entry) {
|
|
// Cached from spend.
|
|
size += this.entry.getSize();
|
|
} else {
|
|
size += compress.size(this.output);
|
|
}
|
|
|
|
return size;
|
|
};
|
|
|
|
/**
|
|
* Write the undo coin to a buffer writer.
|
|
* @param {BufferWriter} bw
|
|
*/
|
|
|
|
UndoCoin.prototype.toWriter = function toWriter(bw) {
|
|
let height = this.height;
|
|
|
|
assert(height !== 0);
|
|
|
|
if (height === -1)
|
|
height = 0;
|
|
|
|
bw.writeVarint(height * 2 + (this.coinbase ? 1 : 0));
|
|
|
|
if (this.height !== -1) {
|
|
assert(this.version !== -1);
|
|
bw.writeVarint(this.version);
|
|
}
|
|
|
|
if (this.entry) {
|
|
// Cached from spend.
|
|
this.entry.toWriter(bw);
|
|
} else {
|
|
compress.output(this.output, bw);
|
|
}
|
|
|
|
return bw;
|
|
};
|
|
|
|
/**
|
|
* Serialize the undo coin.
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
UndoCoin.prototype.toRaw = function toRaw() {
|
|
const 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) {
|
|
const 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;
|