616 lines
11 KiB
JavaScript
616 lines
11 KiB
JavaScript
/*!
|
|
* coinview.js - coin viewpoint object for bcoin
|
|
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var co = require('../utils/co');
|
|
var Coins = require('./coins');
|
|
var UndoCoins = require('./undocoins');
|
|
var BufferReader = require('../utils/reader');
|
|
var BufferWriter = require('../utils/writer');
|
|
var 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 = {};
|
|
this.undo = new UndoCoins();
|
|
}
|
|
|
|
/**
|
|
* Get coins.
|
|
* @param {Hash} hash
|
|
* @returns {Coins} coins
|
|
*/
|
|
|
|
CoinView.prototype.get = function get(hash) {
|
|
return this.map[hash];
|
|
};
|
|
|
|
/**
|
|
* Test whether the view has an entry.
|
|
* @param {Hash} hash
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
CoinView.prototype.has = function has(hash) {
|
|
return this.map[hash] != null;
|
|
};
|
|
|
|
/**
|
|
* Add coins to the collection.
|
|
* @param {Coins} coins
|
|
*/
|
|
|
|
CoinView.prototype.add = function add(coins) {
|
|
this.map[coins.hash] = coins;
|
|
return coins;
|
|
};
|
|
|
|
/**
|
|
* Remove coins from the collection.
|
|
* @param {Coins} coins
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
CoinView.prototype.remove = function remove(hash) {
|
|
if (!this.map[hash])
|
|
return false;
|
|
|
|
delete this.map[hash];
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Add a tx to the collection.
|
|
* @param {TX} tx
|
|
* @param {Number} height
|
|
*/
|
|
|
|
CoinView.prototype.addTX = function addTX(tx, height) {
|
|
var 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) {
|
|
var 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) {
|
|
var 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) {
|
|
var 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) {
|
|
var 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) {
|
|
var 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) {
|
|
var entry = coins.spend(index);
|
|
var undo;
|
|
|
|
if (!entry)
|
|
return false;
|
|
|
|
this.undo.push(entry);
|
|
|
|
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;
|
|
};
|
|
|
|
/**
|
|
* Get a single coin by input.
|
|
* @param {Input} input
|
|
* @returns {Coin}
|
|
*/
|
|
|
|
CoinView.prototype.getCoin = function getCoin(input) {
|
|
var 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) {
|
|
var 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) {
|
|
var 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) {
|
|
var 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) {
|
|
var 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) {
|
|
var 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 = co(function* readCoins(db, hash) {
|
|
var coins = this.map[hash];
|
|
|
|
if (!coins) {
|
|
coins = yield db.getCoins(hash);
|
|
|
|
if (!coins)
|
|
return;
|
|
|
|
this.map[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 = co(function* ensureInputs(db, tx) {
|
|
var found = true;
|
|
var i, input;
|
|
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
if (!(yield 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 = co(function* spendInputs(db, tx) {
|
|
var i, input, prevout, coins;
|
|
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
prevout = input.prevout;
|
|
coins = yield 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() {
|
|
var keys = Object.keys(this.map);
|
|
var out = [];
|
|
var i, hash;
|
|
|
|
for (i = 0; i < keys.length; i++) {
|
|
hash = keys[i];
|
|
out.push(this.map[hash]);
|
|
}
|
|
|
|
return out;
|
|
};
|
|
|
|
/**
|
|
* Calculate serialization size.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
CoinView.prototype.getFastSize = function getFastSize(tx) {
|
|
var size = 0;
|
|
var i, input, entry;
|
|
|
|
size += tx.inputs.length;
|
|
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
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.toFast = function toFast(bw, tx) {
|
|
var i, input, prevout, coins, entry;
|
|
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
prevout = input.prevout;
|
|
coins = this.get(prevout.hash);
|
|
|
|
if (!coins) {
|
|
bw.writeU8(0);
|
|
continue;
|
|
}
|
|
|
|
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.fromFast = function fromFast(br, tx) {
|
|
var i, input, prevout, coins, entry;
|
|
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
prevout = input.prevout;
|
|
|
|
if (br.readU8() === 0)
|
|
continue;
|
|
|
|
coins = this.get(prevout.hash);
|
|
|
|
if (!coins) {
|
|
coins = new Coins();
|
|
coins.hash = prevout.hash;
|
|
coins.coinbase = false;
|
|
this.add(coins);
|
|
}
|
|
|
|
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.fromFast = function fromFast(br, tx) {
|
|
return new CoinView().fromFast(br, tx);
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
var map = {};
|
|
var i, input, prevout, coins, entry, height;
|
|
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
prevout = input.prevout;
|
|
coins = this.get(prevout.hash);
|
|
|
|
if (!coins) {
|
|
bw.writeU8(0);
|
|
continue;
|
|
}
|
|
|
|
entry = coins.get(prevout.index);
|
|
|
|
if (!entry) {
|
|
bw.writeU8(0);
|
|
continue;
|
|
}
|
|
|
|
bw.writeU8(1);
|
|
|
|
if (!map[prevout.hash]) {
|
|
height = coins.height;
|
|
if (height === -1)
|
|
height = 0;
|
|
bw.writeVarint(height * 2 + (coins.coinbase ? 1 : 0));
|
|
bw.writeVarint(coins.version);
|
|
map[prevout.hash] = true;
|
|
}
|
|
|
|
entry.toWriter(bw);
|
|
}
|
|
|
|
return bw;
|
|
};
|
|
|
|
/**
|
|
* Serialize coin data to as it
|
|
* pertains to a transaction.
|
|
* @param {TX} tx
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
CoinView.prototype.toRaw = function toRaw(tx) {
|
|
return this.toWriter(new BufferWriter()).render();
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
var i, input, prevout, coins, entry, height;
|
|
|
|
for (i = 0; i < tx.inputs.length; i++) {
|
|
input = tx.inputs[i];
|
|
prevout = input.prevout;
|
|
|
|
if (br.readU8() === 0)
|
|
continue;
|
|
|
|
coins = this.get(prevout.hash);
|
|
|
|
if (!coins) {
|
|
coins = new Coins();
|
|
coins.hash = prevout.hash;
|
|
height = br.readVarint();
|
|
coins.coinbase = (height & 1) !== 0;
|
|
height = height / 2 | 0;
|
|
if (height === 0)
|
|
height = -1;
|
|
coins.height = height;
|
|
coins.version = br.readVarint();
|
|
this.add(coins);
|
|
}
|
|
|
|
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);
|
|
};
|
|
|
|
/**
|
|
* Read serialized view data from a buffer
|
|
* as it pertains to a transaction.
|
|
* @param {Buffer} data
|
|
* @param {TX} tx
|
|
* @returns {CoinView}
|
|
*/
|
|
|
|
CoinView.fromRaw = function fromRaw(data, tx) {
|
|
return new CoinView().fromReader(new BufferReader(data), tx);
|
|
};
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = CoinView;
|