fcoin/migrate/coins/coinview.js
2018-08-10 16:23:46 -07:00

478 lines
8.5 KiB
JavaScript

/*!
* coinview.js - coin viewpoint 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 {BufferMap} = require('buffer-map');
const Coins = require('./coins');
const UndoCoins = require('./undocoins');
const CoinEntry = Coins.CoinEntry;
/**
* Represents a coin viewpoint:
* a snapshot of {@link Coins} objects.
* @alias module:coins.CoinView
* @constructor
* @property {Object} map
* @property {UndoCoins} undo
*/
function CoinView() {
if (!(this instanceof CoinView))
return new CoinView();
this.map = new BufferMap();
this.undo = new UndoCoins();
}
/**
* Get coins.
* @param {Hash} hash
* @returns {Coins} coins
*/
CoinView.prototype.get = function get(hash) {
return this.map.get(hash);
};
/**
* Test whether the view has an entry.
* @param {Hash} hash
* @returns {Boolean}
*/
CoinView.prototype.has = function has(hash) {
return this.map.has(hash);
};
/**
* Add coins to the collection.
* @param {Coins} coins
*/
CoinView.prototype.add = function add(coins) {
this.map.set(coins.hash, coins);
return coins;
};
/**
* Remove coins from the collection.
* @param {Coins} coins
* @returns {Boolean}
*/
CoinView.prototype.remove = function remove(hash) {
if (!this.map.has(hash))
return false;
this.map.delete(hash);
return true;
};
/**
* Add a tx to the collection.
* @param {TX} tx
* @param {Number} height
*/
CoinView.prototype.addTX = function addTX(tx, height) {
const coins = Coins.fromTX(tx, height);
return this.add(coins);
};
/**
* Remove a tx from the collection.
* @param {TX} tx
* @param {Number} height
*/
CoinView.prototype.removeTX = function removeTX(tx, height) {
const coins = Coins.fromTX(tx, height);
coins.outputs.length = 0;
return this.add(coins);
};
/**
* Add a coin to the collection.
* @param {Coin} coin
*/
CoinView.prototype.addCoin = function addCoin(coin) {
let coins = this.get(coin.hash);
if (!coins) {
coins = new Coins();
coins.hash = coin.hash;
coins.height = coin.height;
coins.coinbase = coin.coinbase;
this.add(coins);
}
if (coin.script.isUnspendable())
return;
if (!coins.has(coin.index))
coins.addCoin(coin);
};
/**
* Add an output to the collection.
* @param {Hash} hash
* @param {Number} index
* @param {Output} output
*/
CoinView.prototype.addOutput = function addOutput(hash, index, output) {
let coins = this.get(hash);
if (!coins) {
coins = new Coins();
coins.hash = hash;
coins.height = -1;
coins.coinbase = false;
this.add(coins);
}
if (output.script.isUnspendable())
return;
if (!coins.has(index))
coins.addOutput(index, output);
};
/**
* Spend an output.
* @param {Hash} hash
* @param {Number} index
* @returns {Boolean}
*/
CoinView.prototype.spendOutput = function spendOutput(hash, index) {
const coins = this.get(hash);
if (!coins)
return false;
return this.spendFrom(coins, index);
};
/**
* Remove an output.
* @param {Hash} hash
* @param {Number} index
* @returns {Boolean}
*/
CoinView.prototype.removeOutput = function removeOutput(hash, index) {
const coins = this.get(hash);
if (!coins)
return false;
return coins.remove(index);
};
/**
* Spend a coin from coins object.
* @param {Coins} coins
* @param {Number} index
* @returns {Boolean}
*/
CoinView.prototype.spendFrom = function spendFrom(coins, index) {
const entry = coins.spend(index);
if (!entry)
return false;
this.undo.push(entry);
if (coins.isEmpty()) {
const undo = this.undo.top();
undo.height = coins.height;
undo.coinbase = coins.coinbase;
undo.version = coins.version;
assert(undo.height !== -1);
}
return true;
};
/**
* Get a single coin by input.
* @param {Input} input
* @returns {Coin}
*/
CoinView.prototype.getCoin = function getCoin(input) {
const 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) {
const coins = this.get(input.prevout.hash);
if (!coins)
return;
return coins.getOutput(input.prevout.index);
};
/**
* Get a single entry by input.
* @param {Input} input
* @returns {CoinEntry}
*/
CoinView.prototype.getEntry = function getEntry(input) {
const coins = this.get(input.prevout.hash);
if (!coins)
return;
return coins.get(input.prevout.index);
};
/**
* Test whether the view has an entry by input.
* @param {Input} input
* @returns {Boolean}
*/
CoinView.prototype.hasEntry = function hasEntry(input) {
const coins = this.get(input.prevout.hash);
if (!coins)
return false;
return coins.has(input.prevout.index);
};
/**
* Get coins height by input.
* @param {Input} input
* @returns {Number}
*/
CoinView.prototype.getHeight = function getHeight(input) {
const coins = this.get(input.prevout.hash);
if (!coins)
return -1;
return coins.height;
};
/**
* Get coins coinbase flag by input.
* @param {Input} input
* @returns {Boolean}
*/
CoinView.prototype.isCoinbase = function isCoinbase(input) {
const coins = this.get(input.prevout.hash);
if (!coins)
return false;
return coins.coinbase;
};
/**
* Retrieve coins from database.
* @method
* @param {ChainDB} db
* @param {TX} tx
* @returns {Promise} - Returns {@link Coins}.
*/
CoinView.prototype.readCoins = async function readCoins(db, hash) {
let coins = this.map.get(hash);
if (!coins) {
coins = await db.getCoins(hash);
if (!coins)
return;
this.map.set(hash, coins);
}
return coins;
};
/**
* Read all input coins into unspent map.
* @method
* @param {ChainDB} db
* @param {TX} tx
* @returns {Promise} - Returns {Boolean}.
*/
CoinView.prototype.ensureInputs = async function ensureInputs(db, tx) {
let found = true;
for (const input of tx.inputs) {
if (!await this.readCoins(db, input.prevout.hash))
found = false;
}
return found;
};
/**
* Spend coins for transaction.
* @method
* @param {ChainDB} db
* @param {TX} tx
* @returns {Promise} - Returns {Boolean}.
*/
CoinView.prototype.spendInputs = async function spendInputs(db, tx) {
for (const input of tx.inputs) {
const prevout = input.prevout;
const coins = await this.readCoins(db, prevout.hash);
if (!coins)
return false;
if (!this.spendFrom(coins, prevout.index))
return false;
}
return true;
};
/**
* Convert collection to an array.
* @returns {Coins[]}
*/
CoinView.prototype.toArray = function toArray() {
const out = [];
for (const coins of this.map.values())
out.push(coins);
return out;
};
/**
* Calculate serialization size.
* @returns {Number}
*/
CoinView.prototype.getSize = function getSize(tx) {
let size = 0;
size += tx.inputs.length;
for (const input of tx.inputs) {
const entry = this.getEntry(input);
if (!entry)
continue;
size += entry.getSize();
}
return size;
};
/**
* Write coin data to buffer writer
* as it pertains to a transaction.
* @param {BufferWriter} bw
* @param {TX} tx
*/
CoinView.prototype.toWriter = function toWriter(bw, tx) {
for (const input of tx.inputs) {
const prevout = input.prevout;
const coins = this.get(prevout.hash);
if (!coins) {
bw.writeU8(0);
continue;
}
const entry = coins.get(prevout.index);
if (!entry) {
bw.writeU8(0);
continue;
}
bw.writeU8(1);
entry.toWriter(bw);
}
return bw;
};
/**
* Read serialized view data from a buffer
* reader as it pertains to a transaction.
* @private
* @param {BufferReader} br
* @param {TX} tx
*/
CoinView.prototype.fromReader = function fromReader(br, tx) {
for (const input of tx.inputs) {
const prevout = input.prevout;
if (br.readU8() === 0)
continue;
let coins = this.get(prevout.hash);
if (!coins) {
coins = new Coins();
coins.hash = prevout.hash;
coins.coinbase = false;
this.add(coins);
}
const entry = CoinEntry.fromReader(br);
coins.add(prevout.index, entry);
}
return this;
};
/**
* Read serialized view data from a buffer
* reader as it pertains to a transaction.
* @param {BufferReader} br
* @param {TX} tx
* @returns {CoinView}
*/
CoinView.fromReader = function fromReader(br, tx) {
return new CoinView().fromReader(br, tx);
};
/*
* Expose
*/
module.exports = CoinView;