330 lines
6.6 KiB
JavaScript
330 lines
6.6 KiB
JavaScript
/**
|
|
* block.js - block object for bcoin
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* https://github.com/indutny/bcoin
|
|
*/
|
|
|
|
var bcoin = require('../bcoin');
|
|
var bn = require('bn.js');
|
|
var utils = bcoin.utils;
|
|
var assert = utils.assert;
|
|
var constants = bcoin.protocol.constants;
|
|
var network = bcoin.protocol.network;
|
|
|
|
/**
|
|
* Block
|
|
*/
|
|
|
|
function Block(data) {
|
|
var self = this;
|
|
|
|
if (!(this instanceof Block))
|
|
return new Block(data);
|
|
|
|
bcoin.abstractblock.call(this, data);
|
|
|
|
this.type = 'block';
|
|
|
|
this.txs = data.txs || [];
|
|
|
|
this._cbHeight = null;
|
|
|
|
this.txs = this.txs.map(function(data) {
|
|
if (data instanceof bcoin.tx)
|
|
return data;
|
|
|
|
return bcoin.tx(data, self);
|
|
});
|
|
|
|
if (!this._raw)
|
|
this._raw = this.render();
|
|
|
|
if (!this._size)
|
|
this._size = this._raw.length;
|
|
}
|
|
|
|
utils.inherits(Block, bcoin.abstractblock);
|
|
|
|
Block.prototype.render = function render() {
|
|
if (this._raw)
|
|
return this._raw;
|
|
return bcoin.protocol.framer.block(this);
|
|
};
|
|
|
|
Block.prototype.getMerkleRoot = function getMerkleRoot() {
|
|
var hashes = [];
|
|
var i, root;
|
|
|
|
for (i = 0; i < this.txs.length; i++)
|
|
hashes.push(this.txs[i].hash());
|
|
|
|
root = utils.getMerkleRoot(hashes);
|
|
|
|
if (!root)
|
|
return utils.toHex(constants.zeroHash);
|
|
|
|
return utils.toHex(root);
|
|
};
|
|
|
|
Block.prototype._verify = function _verify() {
|
|
var uniq = {};
|
|
var i, tx, hash;
|
|
|
|
if (!this.verifyHeaders())
|
|
return false;
|
|
|
|
// Size can't be bigger than MAX_BLOCK_SIZE
|
|
if (this.txs.length > constants.block.maxSize
|
|
|| this.getSize() > constants.block.maxSize) {
|
|
utils.debug('Block is too large: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// First TX must be a coinbase
|
|
if (!this.txs.length || !this.txs[0].isCoinbase()) {
|
|
utils.debug('Block has no coinbase: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Test all txs
|
|
for (i = 0; i < this.txs.length; i++) {
|
|
tx = this.txs[i];
|
|
|
|
// The rest of the txs must not be coinbases
|
|
if (i > 0 && tx.isCoinbase()) {
|
|
utils.debug('Block more than one coinbase: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Check for duplicate txids
|
|
hash = tx.hash('hex');
|
|
if (uniq[hash]) {
|
|
utils.debug('Block has duplicate txids: %s', this.rhash);
|
|
return false;
|
|
}
|
|
uniq[hash] = true;
|
|
}
|
|
|
|
// Check merkle root
|
|
if (this.getMerkleRoot() !== this.merkleRoot) {
|
|
utils.debug('Block failed merkleroot test: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
|
|
var coinbase, s, height;
|
|
|
|
if (this.version < 2)
|
|
return -1;
|
|
|
|
if (this._cbHeight != null)
|
|
return this._cbHeight;
|
|
|
|
coinbase = this.txs[0];
|
|
|
|
if (!coinbase || coinbase.inputs.length === 0)
|
|
return -1;
|
|
|
|
s = coinbase.inputs[0].script;
|
|
|
|
if (Buffer.isBuffer(s[0]))
|
|
height = bcoin.script.num(s[0], true);
|
|
else
|
|
height = -1;
|
|
|
|
this._cbHeight = height;
|
|
|
|
return height;
|
|
};
|
|
|
|
Block.reward = function reward(height) {
|
|
var halvings = height / network.halvingInterval | 0;
|
|
var reward;
|
|
|
|
if (height < 0)
|
|
return new bn(0);
|
|
|
|
if (halvings >= 64)
|
|
return new bn(0);
|
|
|
|
reward = new bn(50).mul(constants.coin);
|
|
reward.iushrn(halvings);
|
|
|
|
return reward;
|
|
};
|
|
|
|
Block.prototype._getReward = function _getReward() {
|
|
var reward, base, fee, height;
|
|
|
|
if (this._reward)
|
|
return this._reward;
|
|
|
|
base = Block.reward(this.height);
|
|
|
|
if (this.height === -1) {
|
|
return this._reward = {
|
|
fee: new bn(0),
|
|
reward: base,
|
|
base: base
|
|
};
|
|
}
|
|
|
|
reward = this.txs[0].outputs.reduce(function(total, output) {
|
|
total.iadd(output.value);
|
|
return total;
|
|
}, new bn(0));
|
|
|
|
fee = reward.sub(base);
|
|
|
|
return this._reward = {
|
|
fee: fee,
|
|
reward: reward,
|
|
base: base
|
|
};
|
|
};
|
|
|
|
Block.prototype.getBaseReward = function getBaseReward() {
|
|
return this._getReward().base;
|
|
};
|
|
|
|
Block.prototype.getReward = function getReward() {
|
|
return this._getReward().reward;
|
|
};
|
|
|
|
Block.prototype.getFee = function getFee() {
|
|
return this._getReward().fee;
|
|
};
|
|
|
|
Block.prototype.getCoinbase = function getCoinbase() {
|
|
var tx;
|
|
|
|
tx = this.txs[0];
|
|
if (!tx || !tx.isCoinbase())
|
|
return;
|
|
|
|
return tx;
|
|
};
|
|
|
|
Block.prototype.inspect = function inspect() {
|
|
var copy = bcoin.block(this);
|
|
copy.__proto__ = null;
|
|
delete copy._raw;
|
|
delete copy._chain;
|
|
copy.hash = this.hash('hex');
|
|
copy.rhash = this.rhash;
|
|
copy.reward = utils.btc(this.getReward());
|
|
copy.fee = utils.btc(this.getFee());
|
|
copy.date = new Date((copy.ts || 0) * 1000).toISOString();
|
|
return copy;
|
|
};
|
|
|
|
Block.prototype.toJSON = function toJSON() {
|
|
return {
|
|
type: this.type,
|
|
height: this.height,
|
|
relayedBy: this.relayedBy,
|
|
hash: utils.revHex(this.hash('hex')),
|
|
version: this.version,
|
|
prevBlock: utils.revHex(this.prevBlock),
|
|
merkleRoot: utils.revHex(this.merkleRoot),
|
|
ts: this.ts,
|
|
bits: this.bits,
|
|
nonce: this.nonce,
|
|
totalTX: this.totalTX,
|
|
txs: this.txs.map(function(tx) {
|
|
return tx.toJSON();
|
|
})
|
|
};
|
|
};
|
|
|
|
Block._fromJSON = function _fromJSON(json) {
|
|
json.prevBlock = utils.revHex(json.prevBlock);
|
|
json.merkleRoot = utils.revHex(json.merkleRoot);
|
|
json.txs = json.txs.map(function(tx) {
|
|
return bcoin.tx._fromJSON(tx);
|
|
});
|
|
return json;
|
|
};
|
|
|
|
Block.fromJSON = function fromJSON(json) {
|
|
return new Block(Block._fromJSON(json));
|
|
};
|
|
|
|
Block.prototype.toCompact = function toCompact() {
|
|
return {
|
|
type: this.type,
|
|
hash: this.hash('hex'),
|
|
prevBlock: this.prevBlock,
|
|
ts: this.ts,
|
|
height: this.height,
|
|
relayedBy: this.relayedBy,
|
|
block: utils.toHex(this.render())
|
|
};
|
|
};
|
|
|
|
Block._fromCompact = function _fromCompact(json) {
|
|
var raw, parser, data;
|
|
|
|
assert.equal(json.type, 'block');
|
|
|
|
raw = new Buffer(json.block, 'hex');
|
|
|
|
parser = new bcoin.protocol.parser();
|
|
|
|
data = parser.parseBlock(raw);
|
|
|
|
data.height = json.height;
|
|
data.relayedBy = json.relayedBy;
|
|
|
|
return data;
|
|
};
|
|
|
|
Block.fromCompact = function fromCompact(json) {
|
|
return new Block(Block._fromCompact(json));
|
|
};
|
|
|
|
Block.prototype.toRaw = function toRaw(enc) {
|
|
var data;
|
|
|
|
data = this.render();
|
|
|
|
if (enc === 'hex')
|
|
data = utils.toHex(data);
|
|
|
|
return data;
|
|
};
|
|
|
|
Block._fromRaw = function _fromRaw(data, enc, type) {
|
|
var parser = new bcoin.protocol.parser();
|
|
|
|
if (enc === 'hex')
|
|
data = new Buffer(data, 'hex');
|
|
|
|
if (type === 'merkleblock')
|
|
return bcoin.merkleblock._fromRaw(data);
|
|
|
|
if (type === 'headers')
|
|
return bcoin.headers._fromRaw(data);
|
|
|
|
return parser.parseBlock(data);
|
|
};
|
|
|
|
Block.fromRaw = function fromRaw(data, enc, type) {
|
|
if (type === 'merkleblock')
|
|
return bcoin.merkleblock.fromRaw(data);
|
|
|
|
if (type === 'headers')
|
|
return bcoin.headers.fromRaw(data);
|
|
|
|
return new Block(Block._fromRaw(data, enc));
|
|
};
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = Block;
|