fcoin/lib/mining/template.js
2017-06-22 03:59:49 -07:00

800 lines
17 KiB
JavaScript

/*!
* template.js - block template object for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var util = require('../utils/util');
var crypto = require('../crypto/crypto');
var BN = require('../crypto/bn');
var StaticWriter = require('../utils/staticwriter');
var Address = require('../primitives/address');
var TX = require('../primitives/tx');
var Block = require('../primitives/block');
var Input = require('../primitives/input');
var Output = require('../primitives/output');
var consensus = require('../protocol/consensus');
var policy = require('../protocol/policy');
var encoding = require('../utils/encoding');
var CoinView = require('../coins/coinview');
var Script = require('../script/script');
var common = require('./common');
var DUMMY = Buffer.alloc(0);
/**
* Block Template
* @alias module:mining.BlockTemplate
* @constructor
* @param {Object} options
*/
function BlockTemplate(options) {
if (!(this instanceof BlockTemplate))
return new BlockTemplate(options);
this.prevBlock = encoding.NULL_HASH;
this.version = 1;
this.height = 0;
this.ts = 0;
this.bits = 0;
this.target = encoding.ZERO_HASH;
this.locktime = 0;
this.mtp = 0;
this.flags = 0;
this.coinbaseFlags = DUMMY;
this.witness = false;
this.address = new Address();
this.sigops = 400;
this.weight = 4000;
this.interval = 210000;
this.fees = 0;
this.tree = new MerkleTree();
this.commitment = encoding.ZERO_HASH;
this.left = DUMMY;
this.right = DUMMY;
this.items = [];
if (options)
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
* @returns {BlockTemplate}
*/
BlockTemplate.prototype.fromOptions = function fromOptions(options) {
assert(options);
if (options.prevBlock != null) {
assert(typeof options.prevBlock === 'string');
this.prevBlock = options.prevBlock;
}
if (options.version != null) {
assert(typeof options.version === 'number');
this.version = options.version;
}
if (options.height != null) {
assert(typeof options.height === 'number');
this.height = options.height;
}
if (options.ts != null) {
assert(typeof options.ts === 'number');
this.ts = options.ts;
}
if (options.bits != null)
this.setBits(options.bits);
if (options.target != null)
this.setTarget(options.target);
if (options.locktime != null) {
assert(typeof options.locktime === 'number');
this.locktime = options.locktime;
}
if (options.mtp != null) {
assert(typeof options.mtp === 'number');
this.mtp = options.mtp;
}
if (options.flags != null) {
assert(typeof options.flags === 'number');
this.flags = options.flags;
}
if (options.coinbaseFlags != null) {
assert(Buffer.isBuffer(options.coinbaseFlags));
this.coinbaseFlags = options.coinbaseFlags;
}
if (options.witness != null) {
assert(typeof options.witness === 'boolean');
this.witness = options.witness;
}
if (options.address != null)
this.address.fromOptions(options.address);
if (options.sigops != null) {
assert(typeof options.sigops === 'number');
this.sigops = options.sigops;
}
if (options.weight != null) {
assert(typeof options.weight === 'number');
this.weight = options.weight;
}
if (options.interval != null) {
assert(typeof options.interval === 'number');
this.interval = options.interval;
}
if (options.fees != null) {
assert(typeof options.fees === 'number');
this.fees = options.fees;
}
if (options.items != null) {
assert(Array.isArray(options.items));
this.items = options.items;
}
return this;
};
/**
* Instantiate block template from options.
* @param {Object} options
* @returns {BlockTemplate}
*/
BlockTemplate.fromOptions = function fromOptions(options) {
return new BlockTemplate().fromOptions(options);
};
/**
* Create witness commitment hash.
* @returns {Buffer}
*/
BlockTemplate.prototype.getWitnessHash = function getWitnessHash() {
var nonce = encoding.ZERO_HASH;
var leaves = [];
var i, item, root, data;
leaves.push(encoding.ZERO_HASH);
for (i = 0; i < this.items.length; i++) {
item = this.items[i];
leaves.push(item.tx.witnessHash());
}
root = crypto.createMerkleRoot(leaves);
assert(!root.malleated);
data = util.concat(root.hash, nonce);
return crypto.hash256(data);
};
/**
* Create witness commitment script.
* @returns {Script}
*/
BlockTemplate.prototype.getWitnessScript = function getWitnessScript() {
return Script.fromCommitment(this.commitment);
};
/**
* Set the target (bits).
* @param {Number} bits
*/
BlockTemplate.prototype.setBits = function setBits(bits) {
assert(typeof bits === 'number');
this.bits = bits;
this.target = common.getTarget(bits);
};
/**
* Set the target (uint256le).
* @param {Buffer} target
*/
BlockTemplate.prototype.setTarget = function setTarget(target) {
assert(Buffer.isBuffer(target));
this.bits = common.getBits(target);
this.target = target;
};
/**
* Calculate the block reward.
* @returns {Amount}
*/
BlockTemplate.prototype.getReward = function getReward() {
var reward = consensus.getReward(this.height, this.interval);
return reward + this.fees;
};
/**
* Initialize the default coinbase.
* @param {Buffer} hash - Witness commitment hash.
* @returns {TX}
*/
BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) {
var scale = consensus.WITNESS_SCALE_FACTOR;
var cb = new TX();
var padding = 0;
var input, output, commit;
// Coinbase input.
input = new Input();
// Height (required in v2+ blocks)
input.script.push(new BN(this.height));
// Coinbase flags.
input.script.push(encoding.ZERO_HASH160);
// Smaller nonce for good measure.
input.script.push(util.nonce(4));
// Extra nonce: incremented when
// the nonce overflows.
input.script.push(encoding.ZERO_U64);
input.script.compile();
// Set up the witness nonce.
if (this.witness) {
input.witness.set(0, encoding.ZERO_HASH);
input.witness.compile();
}
cb.inputs.push(input);
// Reward output.
output = new Output();
output.script.fromPubkeyhash(encoding.ZERO_HASH160);
output.value = this.getReward();
cb.outputs.push(output);
// If we're using segwit, we
// need to set up the commitment.
if (this.witness) {
// Commitment output.
commit = new Output();
commit.script.fromCommitment(hash);
cb.outputs.push(commit);
}
// Padding for the CB height (constant size).
padding = 5 - input.script.code[0].getSize();
assert(padding >= 0);
// Reserved size.
// Without segwit:
// CB weight = 500
// CB stripped size = 125
// CB size = 125
// Sigops cost = 4
// With segwit:
// CB weight = 724
// CB stripped size = 172
// CB size = 208
// Sigops cost = 4
if (!this.witness) {
assert.equal(cb.getWeight() + padding * scale, 500);
assert.equal(cb.getBaseSize() + padding, 125);
assert.equal(cb.getSize() + padding, 125);
} else {
assert.equal(cb.getWeight() + padding * scale, 724);
assert.equal(cb.getBaseSize() + padding, 172);
assert.equal(cb.getSize() + padding, 208);
}
// Setup coinbase flags (variable size).
input.script.set(1, this.coinbaseFlags);
input.script.compile();
// Setup output script (variable size).
output.script.clear();
output.script.fromAddress(this.address);
cb.refresh();
assert(input.script.getSize() <= 100,
'Coinbase input script is too large!');
return cb;
};
/**
* Refresh the coinbase and merkle tree.
*/
BlockTemplate.prototype.refresh = function refresh() {
var hash = this.getWitnessHash();
var cb = this.createCoinbase(hash);
var raw = cb.toNormal();
var size = 0;
var left, right;
size += 4; // version
size += 1; // varint inputs length
size += cb.inputs[0].getSize(); // input size
size -= 4 + 4 + 4; // -(nonce1 + nonce2 + sequence)
// Cut off right after the nonce
// push and before the sequence.
left = raw.slice(0, size);
// Include the sequence.
size += 4 + 4; // nonce1 + nonce2
right = raw.slice(size);
this.commitment = hash;
this.left = left;
this.right = right;
this.tree = MerkleTree.fromItems(this.items);
};
/**
* Get raw coinbase with desired nonces.
* @param {Number} nonce1
* @param {Number} nonce2
* @returns {Buffer}
*/
BlockTemplate.prototype.getRawCoinbase = function getRawCoinbase(nonce1, nonce2) {
var size = 0;
var bw;
size += this.left.length;
size += 4 + 4;
size += this.right.length;
bw = new StaticWriter(size);
bw.writeBytes(this.left);
bw.writeU32BE(nonce1);
bw.writeU32BE(nonce2);
bw.writeBytes(this.right);
return bw.render();
};
/**
* Calculate the merkle root with given nonces.
* @param {Number} nonce1
* @param {Number} nonce2
* @returns {Buffer}
*/
BlockTemplate.prototype.getRoot = function getRoot(nonce1, nonce2) {
var raw = this.getRawCoinbase(nonce1, nonce2);
var hash = crypto.hash256(raw);
return this.tree.withFirst(hash);
};
/**
* Create raw block header with given parameters.
* @param {Buffer} root
* @param {Number} ts
* @param {Number} nonce
* @returns {Buffer}
*/
BlockTemplate.prototype.getHeader = function getHeader(root, ts, nonce) {
var bw = new StaticWriter(80);
bw.writeU32(this.version);
bw.writeHash(this.prevBlock);
bw.writeHash(root);
bw.writeU32(ts);
bw.writeU32(this.bits);
bw.writeU32(nonce);
return bw.render();
};
/**
* Calculate proof with given parameters.
* @param {Number} nonce1
* @param {Number} nonce2
* @param {Number} ts
* @param {Number} nonce
* @returns {BlockProof}
*/
BlockTemplate.prototype.getProof = function getProof(nonce1, nonce2, ts, nonce) {
var root = this.getRoot(nonce1, nonce2);
var data = this.getHeader(root, ts, nonce);
var hash = crypto.hash256(data);
return new BlockProof(hash, root, nonce1, nonce2, ts, nonce);
};
/**
* Create coinbase from given parameters.
* @param {Number} nonce1
* @param {Number} nonce2
* @returns {TX}
*/
BlockTemplate.prototype.getCoinbase = function getCoinbase(nonce1, nonce2) {
var raw = this.getRawCoinbase(nonce1, nonce2);
var tx = TX.fromRaw(raw);
var input;
if (this.witness) {
input = tx.inputs[0];
input.witness.push(encoding.ZERO_HASH);
input.witness.compile();
tx.refresh();
}
return tx;
};
/**
* Create block from calculated proof.
* @param {BlockProof} proof
* @returns {Block}
*/
BlockTemplate.prototype.commit = function commit(proof) {
var root = proof.root;
var n1 = proof.nonce1;
var n2 = proof.nonce2;
var ts = proof.ts;
var nonce = proof.nonce;
var block = new Block();
var i, tx, item;
block.version = this.version;
block.prevBlock = this.prevBlock;
block.merkleRoot = root.toString('hex');
block.ts = ts;
block.bits = this.bits;
block.nonce = nonce;
tx = this.getCoinbase(n1, n2);
block.txs.push(tx);
for (i = 0; i < this.items.length; i++) {
item = this.items[i];
block.txs.push(item.tx);
}
return block;
};
/**
* Quick and dirty way to
* get a coinbase tx object.
* @returns {TX}
*/
BlockTemplate.prototype.toCoinbase = function toCoinbase() {
return this.getCoinbase(0, 0);
};
/**
* Quick and dirty way to get a block
* object (most likely to be an invalid one).
* @returns {Block}
*/
BlockTemplate.prototype.toBlock = function toBlock() {
var proof = this.getProof(0, 0, this.ts, 0);
return this.commit(proof);
};
/**
* Calculate the target difficulty.
* @returns {Number}
*/
BlockTemplate.prototype.getDifficulty = function getDifficulty() {
return common.getDifficulty(this.target);
};
/**
* Set the reward output
* address and refresh.
* @param {Address} address
*/
BlockTemplate.prototype.setAddress = function setAddress(address) {
this.address = Address(address);
this.refresh();
};
/**
* Add a transaction to the template.
* @param {TX} tx
* @param {CoinView} view
*/
BlockTemplate.prototype.addTX = function addTX(tx, view) {
var item, weight, sigops;
assert(!tx.mutable, 'Cannot add mutable TX to block.');
item = BlockEntry.fromTX(tx, view, this);
weight = item.tx.getWeight();
sigops = item.sigops;
if (!tx.isFinal(this.height, this.locktime))
return false;
if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT)
return false;
if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST)
return false;
if (!this.witness && tx.hasWitness())
return false;
this.weight += weight;
this.sigops += sigops;
this.fees += item.fee;
// Add the tx to our block
this.items.push(item);
return true;
};
/**
* Add a transaction to the template
* (less verification than addTX).
* @param {TX} tx
* @param {CoinView?} view
*/
BlockTemplate.prototype.pushTX = function pushTX(tx, view) {
var item, weight, sigops;
assert(!tx.mutable, 'Cannot add mutable TX to block.');
if (!view)
view = new CoinView();
item = BlockEntry.fromTX(tx, view, this);
weight = item.tx.getWeight();
sigops = item.sigops;
this.weight += weight;
this.sigops += sigops;
this.fees += item.fee;
// Add the tx to our block
this.items.push(item);
return true;
};
/**
* BlockEntry
* @alias module:mining.BlockEntry
* @constructor
* @param {TX} tx
* @property {TX} tx
* @property {Hash} hash
* @property {Amount} fee
* @property {Rate} rate
* @property {Number} priority
* @property {Boolean} free
* @property {Sigops} sigops
* @property {Number} depCount
*/
function BlockEntry(tx) {
this.tx = tx;
this.hash = tx.hash('hex');
this.fee = 0;
this.rate = 0;
this.priority = 0;
this.free = false;
this.sigops = 0;
this.descRate = 0;
this.depCount = 0;
}
/**
* Instantiate block entry from transaction.
* @param {TX} tx
* @param {CoinView} view
* @param {BlockTemplate} attempt
* @returns {BlockEntry}
*/
BlockEntry.fromTX = function fromTX(tx, view, attempt) {
var item = new BlockEntry(tx);
item.fee = tx.getFee(view);
item.rate = tx.getRate(view);
item.priority = tx.getPriority(view, attempt.height);
item.free = false;
item.sigops = tx.getSigopsCost(view, attempt.flags);
item.descRate = item.rate;
return item;
};
/**
* Instantiate block entry from mempool entry.
* @param {MempoolEntry} entry
* @param {BlockTemplate} attempt
* @returns {BlockEntry}
*/
BlockEntry.fromEntry = function fromEntry(entry, attempt) {
var item = new BlockEntry(entry.tx);
item.fee = entry.getFee();
item.rate = entry.getDeltaRate();
item.priority = entry.getPriority(attempt.height);
item.free = entry.getDeltaFee() < policy.getMinFee(entry.size);
item.sigops = entry.sigops;
item.descRate = entry.getDescRate();
return item;
};
/*
* BlockProof
* @constructor
* @param {Hash} hash
* @param {Hash} root
* @param {Number} nonce1
* @param {Number} nonce2
* @param {Number} ts
* @param {Number} nonce
*/
function BlockProof(hash, root, nonce1, nonce2, ts, nonce) {
this.hash = hash;
this.root = root;
this.nonce1 = nonce1;
this.nonce2 = nonce2;
this.ts = ts;
this.nonce = nonce;
}
BlockProof.prototype.rhash = function rhash() {
return util.revHex(this.hash.toString('hex'));
};
BlockProof.prototype.verify = function verify(target) {
return common.rcmp(this.hash, target) <= 0;
};
BlockProof.prototype.getDifficulty = function getDifficulty() {
return common.getDifficulty(this.hash);
};
/*
* MerkleTree
* @constructor
* @property {Hash[]} steps
*/
function MerkleTree() {
this.steps = [];
}
MerkleTree.prototype.withFirst = function withFirst(hash) {
var i, step, data;
for (i = 0; i < this.steps.length; i++) {
step = this.steps[i];
data = util.concat(hash, step);
hash = crypto.hash256(data);
}
return hash;
};
MerkleTree.prototype.toJSON = function toJSON() {
var steps = [];
var i, step;
for (i = 0; i < this.steps.length; i++) {
step = this.steps[i];
steps.push(step.toString('hex'));
}
return steps;
};
MerkleTree.prototype.fromItems = function fromItems(items) {
var leaves = [];
var i, item;
leaves.push(encoding.ZERO_HASH);
for (i = 0; i < items.length; i++) {
item = items[i];
leaves.push(item.tx.hash());
}
return this.fromLeaves(leaves);
};
MerkleTree.fromItems = function fromItems(items) {
return new MerkleTree().fromItems(items);
};
MerkleTree.prototype.fromBlock = function fromBlock(txs) {
var leaves = [];
var i, tx;
leaves.push(encoding.ZERO_HASH);
for (i = 1; i < txs.length; i++) {
tx = txs[i];
leaves.push(tx.hash());
}
return this.fromLeaves(leaves);
};
MerkleTree.fromBlock = function fromBlock(txs) {
return new MerkleTree().fromBlock(txs);
};
MerkleTree.prototype.fromLeaves = function fromLeaves(leaves) {
var len = leaves.length;
var i, hashes, data, hash;
while (len > 1) {
this.steps.push(leaves[1]);
if (len % 2)
leaves.push(leaves[len - 1]);
hashes = [null];
for (i = 2; i < len; i += 2) {
data = util.concat(leaves[i], leaves[i + 1]);
hash = crypto.hash256(data);
hashes.push(hash);
}
leaves = hashes;
len = leaves.length;
}
return this;
};
MerkleTree.fromLeaves = function fromLeaves(leaves) {
return new MerkleTree().fromLeaves(leaves);
};
/*
* Expose
*/
exports = BlockTemplate;
exports.BlockTemplate = BlockTemplate;
exports.BlockEntry = BlockEntry;
module.exports = exports;