689 lines
17 KiB
JavaScript
689 lines
17 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 constants = bcoin.protocol.constants;
|
|
var network = bcoin.protocol.network;
|
|
|
|
/**
|
|
* Block
|
|
*/
|
|
|
|
function Block(data, subtype) {
|
|
var self = this;
|
|
var tx, height;
|
|
|
|
if (!(this instanceof Block))
|
|
return new Block(data, subtype);
|
|
|
|
this.type = 'block';
|
|
this.subtype = subtype;
|
|
this.version = data.version;
|
|
this.prevBlock = utils.toHex(data.prevBlock);
|
|
this.merkleRoot = utils.toHex(data.merkleRoot);
|
|
this.ts = data.ts;
|
|
this.bits = data.bits;
|
|
this.nonce = data.nonce;
|
|
this.totalTX = data.totalTX;
|
|
this.hashes = (data.hashes || []).map(function(hash) {
|
|
return utils.toHex(hash);
|
|
});
|
|
this.flags = data.flags || [];
|
|
this.txs = data.txs || [];
|
|
|
|
this._raw = data._raw || null;
|
|
this._size = data._size || 0;
|
|
|
|
this.network = data.network || false;
|
|
this.relayedBy = data.relayedBy || '0.0.0.0';
|
|
|
|
this._chain = data.chain;
|
|
|
|
this.valid = null;
|
|
this._hash = null;
|
|
|
|
// https://gist.github.com/sipa/bf69659f43e763540550
|
|
// http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-August/010396.html
|
|
this.versionBits = (this.version >>> 29) & 7;
|
|
this.realVersion = this.version & 0x1fffffff;
|
|
this.highVersion = this.version & 0x1ffffff8;
|
|
this.lowVersion = this.version & 7;
|
|
|
|
// List of matched TXs
|
|
this.tx = [];
|
|
|
|
if (!this.subtype) {
|
|
if (this.hashes.length)
|
|
this.subtype = 'merkleblock';
|
|
else if (this.txs.length)
|
|
this.subtype = 'block';
|
|
else
|
|
this.subtype = 'header';
|
|
}
|
|
|
|
if (this.subtype === 'block') {
|
|
this.txs = this.txs.map(function(tx) {
|
|
tx.network = self.network;
|
|
tx.relayedBy = self.relayedBy;
|
|
tx = bcoin.tx(tx);
|
|
tx.block = self.hash('hex');
|
|
tx.ts = tx.ts || self.ts;
|
|
return tx;
|
|
});
|
|
}
|
|
|
|
this.verify();
|
|
|
|
if (this.subtype === 'block' && !this.valid) {
|
|
this.txs = this.txs.map(function(tx) {
|
|
tx.block = null;
|
|
if (tx.ts === self.ts)
|
|
tx.ts = 0;
|
|
return tx;
|
|
});
|
|
}
|
|
}
|
|
|
|
Block.prototype.hash = function hash(enc) {
|
|
// Hash it
|
|
if (!this._hash)
|
|
this._hash = utils.toHex(utils.dsha256(this.abbr()));
|
|
return enc === 'hex' ? this._hash : utils.toArray(this._hash, 'hex');
|
|
};
|
|
|
|
Block.prototype.abbr = function abbr() {
|
|
if (this.network && this._raw)
|
|
return utils.toArray(this._raw.slice(0, 80));
|
|
|
|
var res = new Array(80);
|
|
utils.write32(res, this.version, 0);
|
|
utils.copy(utils.toArray(this.prevBlock, 'hex'), res, 4);
|
|
utils.copy(utils.toArray(this.merkleRoot, 'hex'), res, 36);
|
|
utils.writeU32(res, this.ts, 68);
|
|
utils.writeU32(res, this.bits, 72);
|
|
utils.writeU32(res, this.nonce, 76);
|
|
|
|
return res;
|
|
};
|
|
|
|
Block.prototype.verify = function verify() {
|
|
if (this.valid == null)
|
|
this.valid = this._verify();
|
|
return this.valid;
|
|
};
|
|
|
|
Block.verify = function verify(data, subtype) {
|
|
var block = new Block(data, subtype);
|
|
return block.verify();
|
|
};
|
|
|
|
Block.prototype.render = function render() {
|
|
if (this.network && this._raw && this._raw.length > 80)
|
|
return utils.toArray(this._raw);
|
|
return bcoin.protocol.framer.block(this, this.subtype);
|
|
};
|
|
|
|
Block.prototype.getSize = function getSize() {
|
|
return this._size || this.render().length;
|
|
};
|
|
|
|
// Legacy
|
|
Block.prototype.size = Block.prototype.getSize;
|
|
|
|
Block.prototype.hasTX = function hasTX(hash) {
|
|
return this.tx.indexOf(hash) !== -1;
|
|
};
|
|
|
|
Block.prototype._verifyPartial = function _verifyPartial() {
|
|
var height = 0;
|
|
var tx = [];
|
|
var i = 0;
|
|
var j = 0;
|
|
var hashes = this.hashes;
|
|
var flags = this.flags;
|
|
var i, root;
|
|
|
|
if (this.subtype !== 'merkleblock')
|
|
return;
|
|
|
|
// Count leaves
|
|
for (i = this.totalTX; i > 0; i >>= 1)
|
|
height++;
|
|
|
|
if (this.totalTX > (1 << (height - 1)))
|
|
height++;
|
|
|
|
function visit(depth) {
|
|
var flag, left, right;
|
|
|
|
if (i === flags.length * 8 || j === hashes.length)
|
|
return null;
|
|
|
|
flag = (flags[i >> 3] >>> (i & 7)) & 1;
|
|
i++;
|
|
|
|
if (flag === 0 || depth === height) {
|
|
if (depth === height)
|
|
tx.push(hashes[j]);
|
|
return hashes[j++];
|
|
}
|
|
|
|
// Go deeper
|
|
left = visit(depth + 1);
|
|
if (!left)
|
|
return null;
|
|
|
|
right = visit(depth + 1);
|
|
if (right === left)
|
|
return null;
|
|
|
|
if (!right)
|
|
right = left;
|
|
|
|
return utils.toHex(utils.dsha256(left + right, 'hex'));
|
|
}
|
|
|
|
root = visit(1);
|
|
|
|
if (!root || root !== this.merkleRoot)
|
|
return false;
|
|
|
|
this.tx = tx;
|
|
|
|
return true;
|
|
};
|
|
|
|
Block.prototype.getMerkleRoot = function getMerkleRoot() {
|
|
var merkleTree = [];
|
|
var i, j, size, i2, hash;
|
|
|
|
for (i = 0; i < this.txs.length; i++) {
|
|
merkleTree.push(this.txs[i].hash('hex'));
|
|
}
|
|
|
|
j = 0;
|
|
size = this.txs.length;
|
|
|
|
for (; size > 1; size = ((size + 1) / 2) | 0) {
|
|
for (i = 0; i < size; i += 2) {
|
|
i2 = Math.min(i + 1, size - 1);
|
|
if (i2 === i + 1 && i2 + 1 === size
|
|
&& merkleTree[j + i] === merkleTree[j + i2]) {
|
|
return utils.toHex(constants.zeroHash);
|
|
}
|
|
hash = utils.dsha256(merkleTree[j + i] + merkleTree[j + i2], 'hex');
|
|
merkleTree.push(utils.toHex(hash));
|
|
}
|
|
j += size;
|
|
}
|
|
|
|
if (!merkleTree.length)
|
|
return utils.toHex(constants.zeroHash);
|
|
|
|
return merkleTree[merkleTree.length - 1];
|
|
};
|
|
|
|
Block.prototype._verify = function _verify() {
|
|
var uniq = {};
|
|
var i, tx, hash;
|
|
|
|
// Check proof of work
|
|
if (!utils.testTarget(this.bits, this.hash())) {
|
|
utils.debug('Block failed POW test: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Check timestamp against now + 2 hours
|
|
if (this.ts > utils.now() + 2 * 60 * 60) {
|
|
utils.debug('Block timestamp is too high: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Verify the partial merkle tree if we are a merkleblock.
|
|
if (this.subtype === 'merkleblock') {
|
|
if (!this._verifyPartial()) {
|
|
utils.debug('Block failed merkle test: %s', this.rhash);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Merkleblock and headers cannot do anymore tests.
|
|
if (this.subtype !== 'block')
|
|
return true;
|
|
|
|
// 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.verifyContext = function verifyContext() {
|
|
var flags = {};
|
|
var sigops = 0;
|
|
var prev, height, ts, i, j, tx, cb, input;
|
|
|
|
if (this.subtype !== 'block')
|
|
return true;
|
|
|
|
if (this.isGenesis())
|
|
return true;
|
|
|
|
if (!this.chain)
|
|
return true;
|
|
|
|
prev = this.chain.getBlock(this.prevBlock);
|
|
|
|
// Ensure it's not an orphan
|
|
if (!prev) {
|
|
utils.debug('Block has no previous entry: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
height = prev.height + 1;
|
|
|
|
// Ensure the timestamp is correct
|
|
if (this.ts <= prev.getMedianTime()) {
|
|
utils.debug('Block time is lower than median: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Ensure the miner's target is equal to what we expect
|
|
if (this.bits !== this.chain.target(prev, this)) {
|
|
utils.debug('Block is using wrong target: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Only allow version 2 blocks (coinbase height)
|
|
// once the majority of blocks are using it.
|
|
if (this.version < 2 && prev.isOutdated(2)) {
|
|
utils.debug('Block is outdated (v2): %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Only allow version 3 blocks (sig validation)
|
|
// once the majority of blocks are using it.
|
|
if (this.version < 3 && prev.isOutdated(3)) {
|
|
utils.debug('Block is outdated (v3): %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Only allow version 4 blocks (checklocktimeverify)
|
|
// once the majority of blocks are using it.
|
|
if (this.version < 4 && prev.isOutdated(4)) {
|
|
utils.debug('Block is outdated (v4): %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Only allow version 8 blocks (locktime median past)
|
|
// once the majority of blocks are using it.
|
|
// if (this.version < 8 && prev.isOutdated(8)) {
|
|
// utils.debug('Block is outdated (v8): %s', this.rhash);
|
|
// return false;
|
|
// }
|
|
|
|
// Make sure the height contained in the coinbase is correct.
|
|
if (this.version >= 2 && prev.isUpgraded(2)) {
|
|
cb = bcoin.script.getCoinbaseData(this.txs[0].inputs[0].script);
|
|
|
|
// Make sure the coinbase is parseable.
|
|
if (!cb) {
|
|
utils.debug('Block has malformed coinbase: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Make sure coinbase height is equal to the actual height.
|
|
if (cb.height !== height) {
|
|
utils.debug('Block has bad coinbase height: %s', this.rhash);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Signature validation is now enforced (bip66)
|
|
if (!(this.version >= 3 && prev.isUpgraded(3)))
|
|
flags.dersig = false;
|
|
|
|
// CHECKLOCKTIMEVERIFY is now usable (bip65)
|
|
if (!(this.version >= 4 && prev.isUpgraded(4)))
|
|
flags.checklocktimeverify = false;
|
|
|
|
// Use nLockTime median past (bip113)
|
|
// https://github.com/btcdrak/bips/blob/d4c9a236ecb947866c61aefb868b284498489c2b/bip-0113.mediawiki
|
|
// Support version bits:
|
|
// https://gist.github.com/sipa/bf69659f43e763540550
|
|
// http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-August/010396.html
|
|
// if (this.version >= 8 && prev.isUpgraded(8))
|
|
// flags.locktimeMedian = true;
|
|
|
|
// If we are an ancestor of a checkpoint, we can
|
|
// skip the input verification.
|
|
if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
|
|
flags.scriptChecks = false;
|
|
|
|
// Get timestamp for tx.isFinal().
|
|
ts = flags.locktimeMedian
|
|
? prev.getMedianTime()
|
|
: this.ts;
|
|
|
|
// Check all transactions
|
|
for (i = 0; i < this.txs.length; i++) {
|
|
tx = this.txs[i];
|
|
|
|
// Transactions must be finalized with
|
|
// regards to nSequence and nLockTime.
|
|
if (!tx.isFinal(height, ts)) {
|
|
utils.debug('TX is not final: %s (%s)', this.rhash, i);
|
|
return false;
|
|
}
|
|
|
|
// Check for tx sigops limits
|
|
// Bitcoind does not check for this when accepting
|
|
// a block even though it probably should.
|
|
// if (tx.getSigops(true) > constants.script.maxTxSigops) {
|
|
// // Block 71036 abused checksig to
|
|
// // include a huge number of sigops.
|
|
// utils.debug('Block TX has too many sigops: %s', this.rhash);
|
|
// if (!(network.type === 'main' && height === 71036))
|
|
// return false;
|
|
// }
|
|
|
|
// Check for block sigops limits
|
|
// Start counting P2SH sigops once block
|
|
// timestamps reach March 31st, 2012.
|
|
if (this.ts >= constants.block.bip16time)
|
|
sigops += tx.getSigops(true);
|
|
else
|
|
sigops += tx.getSigops();
|
|
|
|
if (sigops > constants.script.maxBlockSigops) {
|
|
utils.debug('Block has too many sigops: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// BIP30 - Ensure there are no duplicate txids
|
|
if (this.chain[tx.hash('hex')]) {
|
|
// Blocks 91842 and 91880 created duplicate
|
|
// txids by using the same exact output script
|
|
// and extraNonce.
|
|
utils.debug('Block is overwriting txids: %s', this.rhash);
|
|
if (!(network.type === 'main' && (height === 91842 || height === 91880)))
|
|
return false;
|
|
}
|
|
|
|
// Verify the inputs of every tx (CheckInputs)
|
|
if (flags.scriptChecks !== false) {
|
|
if (tx.isCoinbase())
|
|
continue;
|
|
|
|
for (j = 0; j < tx.inputs.length; j++) {
|
|
input = tx.inputs[j];
|
|
|
|
// We need the previous output in order
|
|
// to verify the script.
|
|
if (!input.prevout.tx)
|
|
continue;
|
|
|
|
assert(input.prevout.tx);
|
|
|
|
// Verify the script
|
|
if (!tx.verify(j, true, flags)) {
|
|
utils.debug('Block has invalid inputs: %s', this.rhash);
|
|
return false;
|
|
}
|
|
|
|
// Ensure tx is not double spending an output
|
|
// if (this.chain.isSpent(input.prevout.hash, input.prevout.index)) {
|
|
// utils.debug('Block is using spent inputs: %s', this.rhash);
|
|
// return false;
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
Block.prototype.isGenesis = function isGenesis() {
|
|
return this.hash('hex') === network.genesis.hash;
|
|
};
|
|
|
|
Block.prototype.getHeight = function getHeight() {
|
|
if (!this.chain)
|
|
return -1;
|
|
return this.chain.getHeight(this.hash('hex'));
|
|
};
|
|
|
|
Block.prototype.getNextBlock = function getNextBlock() {
|
|
var next;
|
|
|
|
if (!this.chain)
|
|
return utils.toHex(constants.zeroHash);
|
|
|
|
next = this.chain.getNextBlock(this.hash('hex'));
|
|
|
|
if (!next)
|
|
return utils.toHex(constants.zeroHash);
|
|
|
|
return next;
|
|
};
|
|
|
|
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 = utils.satoshi('50.0');
|
|
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.getHeight());
|
|
|
|
if (this.txs.length === 0 || !this.txs[0].isCoinbase()) {
|
|
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));
|
|
|
|
// If height is not accurate, artificially increase
|
|
// the reward era until base is smaller than the reward.
|
|
height = 0;
|
|
while (base.cmp(reward) > 0) {
|
|
base = Block.reward(height);
|
|
height += constants.halvingInterval;
|
|
}
|
|
|
|
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.getEntry = function getEntry() {
|
|
if (!this.chain)
|
|
return;
|
|
return this.chain.getBlock(this);
|
|
};
|
|
|
|
Block.prototype.isOrphan = function isOrphan() {
|
|
if (!this.chain)
|
|
return true;
|
|
return !this.chain.hasBlock(this.prevBlock);
|
|
};
|
|
|
|
Block.prototype.getCoinbase = function getCoinbase() {
|
|
var tx = this.txs[0];
|
|
if (!tx || !tx.isCoinbase())
|
|
return;
|
|
return tx;
|
|
};
|
|
|
|
Block.prototype.__defineGetter__('chain', function() {
|
|
return this._chain || bcoin.chain.global;
|
|
});
|
|
|
|
Block.prototype.__defineGetter__('rhash', function() {
|
|
return utils.revHex(this.hash('hex'));
|
|
});
|
|
|
|
Block.prototype.__defineGetter__('height', function() {
|
|
return this.getHeight();
|
|
});
|
|
|
|
Block.prototype.__defineGetter__('nextBlock', function() {
|
|
return this.getNextBlock();
|
|
});
|
|
|
|
Block.prototype.__defineGetter__('reward', function() {
|
|
return this.getReward();
|
|
});
|
|
|
|
Block.prototype.__defineGetter__('fee', function() {
|
|
return this.getFee();
|
|
});
|
|
|
|
Block.prototype.__defineGetter__('coinbase', function() {
|
|
return this.getCoinbase();
|
|
});
|
|
|
|
Block.prototype.__defineGetter__('entry', function() {
|
|
return this.getEntry();
|
|
});
|
|
|
|
Block.prototype.__defineGetter__('orphan', function() {
|
|
return this.isOrphan();
|
|
});
|
|
|
|
Block.prototype.inspect = function inspect() {
|
|
var copy = bcoin.block(this, this.subtype);
|
|
copy.__proto__ = null;
|
|
delete copy._raw;
|
|
delete copy._chain;
|
|
copy.hash = this.hash('hex');
|
|
copy.rhash = this.rhash;
|
|
copy.height = this.getHeight();
|
|
copy.nextBlock = this.getNextBlock();
|
|
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 {
|
|
v: 1,
|
|
type: 'block',
|
|
subtype: this.subtype,
|
|
hash: this.hash('hex'),
|
|
prevBlock: this.prevBlock,
|
|
ts: this.ts,
|
|
network: this.network,
|
|
relayedBy: this.relayedBy,
|
|
block: utils.toHex(this.render())
|
|
};
|
|
};
|
|
|
|
Block.fromJSON = function fromJSON(json) {
|
|
var raw, parser, data, block;
|
|
|
|
utils.assert.equal(json.v, 1);
|
|
utils.assert.equal(json.type, 'block');
|
|
|
|
raw = utils.toArray(json.block, 'hex');
|
|
|
|
parser = new bcoin.protocol.parser();
|
|
|
|
if (json.subtype === 'merkleblock')
|
|
data = parser.parseMerkleBlock(raw);
|
|
else if (json.subtype === 'block' || json.subtype === 'header')
|
|
data = parser.parseBlock(raw);
|
|
|
|
data.network = json.network;
|
|
data.relayedBy = json.relayedBy;
|
|
|
|
block = new Block(data, json.subtype);
|
|
|
|
block._hash = json.hash;
|
|
|
|
return block;
|
|
};
|
|
|
|
/**
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = Block;
|