911 lines
20 KiB
JavaScript
911 lines
20 KiB
JavaScript
/*!
|
|
* template.js - block template object for hsd
|
|
* Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
|
|
* https://github.com/handshake-org/hsd
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const bio = require('bufio');
|
|
const BLAKE2b = require('bcrypto/lib/blake2b');
|
|
const random = require('bcrypto/lib/random');
|
|
const merkle = require('bcrypto/lib/mrkl');
|
|
const Address = require('../primitives/address');
|
|
const TX = require('../primitives/tx');
|
|
const Block = require('../primitives/block');
|
|
const Headers = require('../primitives/headers');
|
|
const Input = require('../primitives/input');
|
|
const Output = require('../primitives/output');
|
|
const consensus = require('../protocol/consensus');
|
|
const policy = require('../protocol/policy');
|
|
const CoinView = require('../coins/coinview');
|
|
const rules = require('../covenants/rules');
|
|
const common = require('./common');
|
|
|
|
/** @typedef {import('../types').Amount} AmountValue */
|
|
/** @typedef {import('../types').Hash} Hash */
|
|
/** @typedef {import('../primitives/claim')} Claim */
|
|
/** @typedef {import('../primitives/airdropproof')} AirdropProof */
|
|
/** @typedef {import('../mempool/airdropentry')} AirdropEntry */
|
|
/** @typedef {import('../mempool/claimentry')} ClaimEntry */
|
|
/** @typedef {import('../mempool/mempoolentry')} MempoolEntry */
|
|
|
|
/*
|
|
* Constants
|
|
*/
|
|
|
|
const DUMMY = Buffer.alloc(0);
|
|
|
|
/**
|
|
* Block Template
|
|
* @alias module:mining.BlockTemplate
|
|
*/
|
|
|
|
class BlockTemplate {
|
|
/**
|
|
* Create a block template.
|
|
* @constructor
|
|
* @param {Object} [options]
|
|
*/
|
|
|
|
constructor(options) {
|
|
this.prevBlock = consensus.ZERO_HASH;
|
|
this.version = 0;
|
|
this.height = 0;
|
|
this.time = 0;
|
|
this.bits = 0;
|
|
this.target = consensus.ZERO_HASH;
|
|
this.mtp = 0;
|
|
this.flags = 0;
|
|
this.coinbaseFlags = DUMMY;
|
|
this.address = new Address();
|
|
this.sigops = 400;
|
|
this.weight = 4000;
|
|
this.opens = 0;
|
|
this.updates = 0;
|
|
this.renewals = 0;
|
|
this.interval = 170000;
|
|
this.fees = 0;
|
|
this.merkleRoot = consensus.ZERO_HASH;
|
|
this.witnessRoot = consensus.ZERO_HASH;
|
|
this.treeRoot = consensus.ZERO_HASH;
|
|
this.reservedRoot = consensus.ZERO_HASH;
|
|
this.coinbase = new TX();
|
|
this.items = [];
|
|
this.claims = [];
|
|
this.airdrops = [];
|
|
|
|
if (options)
|
|
this.fromOptions(options);
|
|
}
|
|
|
|
/**
|
|
* Inject properties from options.
|
|
* @param {Object} options
|
|
* @returns {BlockTemplate}
|
|
*/
|
|
|
|
fromOptions(options) {
|
|
assert(options);
|
|
|
|
if (options.prevBlock != null) {
|
|
assert(Buffer.isBuffer(options.prevBlock));
|
|
this.prevBlock = options.prevBlock;
|
|
}
|
|
|
|
if (options.merkleRoot != null) {
|
|
assert(Buffer.isBuffer(options.merkleRoot));
|
|
this.merkleRoot = options.merkleRoot;
|
|
}
|
|
|
|
if (options.witnessRoot != null) {
|
|
assert(Buffer.isBuffer(options.witnessRoot));
|
|
this.witnessRoot = options.witnessRoot;
|
|
}
|
|
|
|
if (options.treeRoot != null) {
|
|
assert(Buffer.isBuffer(options.treeRoot));
|
|
this.treeRoot = options.treeRoot;
|
|
}
|
|
|
|
if (options.reservedRoot != null) {
|
|
assert(Buffer.isBuffer(options.reservedRoot));
|
|
this.reservedRoot = options.reservedRoot;
|
|
}
|
|
|
|
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.time != null) {
|
|
assert(typeof options.time === 'number');
|
|
this.time = options.time;
|
|
}
|
|
|
|
if (options.bits != null)
|
|
this.setBits(options.bits);
|
|
|
|
if (options.target != null)
|
|
this.setTarget(options.target);
|
|
|
|
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.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.opens != null) {
|
|
assert(typeof options.opens === 'number');
|
|
this.opens = options.opens;
|
|
}
|
|
|
|
if (options.updates != null) {
|
|
assert(typeof options.updates === 'number');
|
|
this.updates = options.updates;
|
|
}
|
|
|
|
if (options.renewals != null) {
|
|
assert(typeof options.renewals === 'number');
|
|
this.renewals = options.renewals;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (options.claims != null) {
|
|
assert(Array.isArray(options.claims));
|
|
this.claims = options.claims;
|
|
}
|
|
|
|
if (options.airdrops != null) {
|
|
assert(Array.isArray(options.airdrops));
|
|
this.airdrops = options.airdrops;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Instantiate block template from options.
|
|
* @param {Object} options
|
|
* @returns {BlockTemplate}
|
|
*/
|
|
|
|
static fromOptions(options) {
|
|
return new this().fromOptions(options);
|
|
}
|
|
|
|
/**
|
|
* Set the target (bits).
|
|
* @param {Number} bits
|
|
*/
|
|
|
|
setBits(bits) {
|
|
assert(typeof bits === 'number');
|
|
this.bits = bits;
|
|
this.target = common.getTarget(bits);
|
|
}
|
|
|
|
/**
|
|
* Set the target (uint256le).
|
|
* @param {Buffer} target
|
|
*/
|
|
|
|
setTarget(target) {
|
|
assert(Buffer.isBuffer(target));
|
|
this.bits = common.getBits(target);
|
|
this.target = target;
|
|
}
|
|
|
|
/**
|
|
* Calculate the block reward.
|
|
* @returns {AmountValue}
|
|
*/
|
|
|
|
getReward() {
|
|
const reward = consensus.getReward(this.height, this.interval);
|
|
return reward + this.fees;
|
|
}
|
|
|
|
/**
|
|
* Initialize the default coinbase.
|
|
* @returns {TX}
|
|
*/
|
|
|
|
createCoinbase() {
|
|
const cb = new TX();
|
|
|
|
// Commit to height.
|
|
cb.locktime = this.height;
|
|
|
|
// Coinbase input.
|
|
const input = new Input();
|
|
input.sequence = random.randomInt();
|
|
input.witness.pushData(Buffer.alloc(20, 0x00));
|
|
input.witness.pushData(Buffer.alloc(8, 0x00));
|
|
input.witness.pushData(Buffer.alloc(8, 0x00));
|
|
input.witness.compile();
|
|
|
|
cb.inputs.push(input);
|
|
|
|
// Reward output.
|
|
const output = new Output();
|
|
output.address.fromPubkeyhash(Buffer.alloc(20, 0x00));
|
|
output.value = this.getReward();
|
|
|
|
cb.outputs.push(output);
|
|
|
|
// Setup coinbase flags (variable size).
|
|
input.witness.setData(0, this.coinbaseFlags);
|
|
input.witness.setData(1, random.randomBytes(8));
|
|
input.witness.compile();
|
|
|
|
// Setup output address (variable size).
|
|
output.address = this.address;
|
|
|
|
// Add any claims.
|
|
for (const claim of this.claims) {
|
|
const input = new Input();
|
|
|
|
input.witness.items.push(claim.blob);
|
|
|
|
cb.inputs.push(input);
|
|
|
|
let flags = 0;
|
|
|
|
if (claim.weak)
|
|
flags |= 1;
|
|
|
|
const output = new Output();
|
|
|
|
output.value = claim.value - claim.fee;
|
|
output.address = claim.address;
|
|
output.covenant.setClaim(
|
|
claim.nameHash,
|
|
this.height,
|
|
claim.name,
|
|
flags,
|
|
claim.commitHash,
|
|
claim.commitHeight
|
|
);
|
|
|
|
cb.outputs.push(output);
|
|
}
|
|
|
|
// Add any airdrop proofs.
|
|
for (const proof of this.airdrops) {
|
|
const input = new Input();
|
|
|
|
input.witness.items.push(proof.blob);
|
|
|
|
cb.inputs.push(input);
|
|
|
|
const output = new Output();
|
|
|
|
output.value = proof.value - proof.fee;
|
|
output.address = proof.address;
|
|
|
|
cb.outputs.push(output);
|
|
}
|
|
|
|
cb.refresh();
|
|
|
|
assert(input.witness.getSize() <= 1000,
|
|
'Coinbase witness is too large!');
|
|
|
|
return cb;
|
|
}
|
|
|
|
/**
|
|
* Refresh the coinbase and merkle tree.
|
|
*/
|
|
|
|
refresh() {
|
|
const cb = this.createCoinbase();
|
|
|
|
{
|
|
const leaves = [];
|
|
|
|
leaves.push(cb.hash());
|
|
|
|
for (const {tx} of this.items)
|
|
leaves.push(tx.hash());
|
|
|
|
this.merkleRoot = merkle.createRoot(BLAKE2b, leaves);
|
|
}
|
|
|
|
{
|
|
const leaves = [];
|
|
|
|
leaves.push(cb.witnessHash());
|
|
|
|
for (const {tx} of this.items)
|
|
leaves.push(tx.witnessHash());
|
|
|
|
this.witnessRoot = merkle.createRoot(BLAKE2b, leaves);
|
|
}
|
|
|
|
this.coinbase = cb;
|
|
}
|
|
|
|
/**
|
|
* Create raw block header with given parameters.
|
|
* @param {Number} nonce
|
|
* @param {Number} time
|
|
* @param {Buffer} extraNonce
|
|
* @param {Buffer} mask
|
|
* @returns {Buffer}
|
|
*/
|
|
|
|
getHeader(nonce, time, extraNonce, mask) {
|
|
const hdr = new Headers();
|
|
|
|
hdr.version = this.version;
|
|
hdr.prevBlock = this.prevBlock;
|
|
hdr.merkleRoot = this.merkleRoot;
|
|
hdr.witnessRoot = this.witnessRoot;
|
|
hdr.treeRoot = this.treeRoot;
|
|
hdr.reservedRoot = this.reservedRoot;
|
|
hdr.time = time;
|
|
hdr.bits = this.bits;
|
|
hdr.nonce = nonce;
|
|
hdr.extraNonce = extraNonce;
|
|
hdr.mask = mask;
|
|
|
|
return hdr.toMiner();
|
|
}
|
|
|
|
/**
|
|
* Calculate proof with given parameters.
|
|
* @param {Number} nonce
|
|
* @param {Number} time
|
|
* @param {Buffer} extraNonce
|
|
* @param {Buffer} mask
|
|
* @returns {BlockProof}
|
|
*/
|
|
|
|
getProof(nonce, time, extraNonce, mask) {
|
|
const hdr = this.getHeader(nonce, time, extraNonce, mask);
|
|
const proof = new BlockProof();
|
|
|
|
proof.hdr = hdr;
|
|
proof.time = time;
|
|
proof.nonce = nonce;
|
|
proof.extraNonce = extraNonce;
|
|
proof.mask = mask;
|
|
|
|
return proof;
|
|
}
|
|
|
|
/**
|
|
* Create block from calculated proof.
|
|
* @param {BlockProof} proof
|
|
* @returns {Block}
|
|
*/
|
|
|
|
commit(proof) {
|
|
const block = new Block();
|
|
|
|
block.version = this.version;
|
|
block.prevBlock = this.prevBlock;
|
|
block.merkleRoot = this.merkleRoot;
|
|
block.witnessRoot = this.witnessRoot;
|
|
block.treeRoot = this.treeRoot;
|
|
block.reservedRoot = this.reservedRoot;
|
|
block.time = proof.time;
|
|
block.bits = this.bits;
|
|
block.nonce = proof.nonce;
|
|
block.extraNonce = proof.extraNonce;
|
|
block.mask = proof.mask;
|
|
|
|
block.txs.push(this.coinbase);
|
|
|
|
for (const item of this.items)
|
|
block.txs.push(item.tx);
|
|
|
|
return block;
|
|
}
|
|
|
|
/**
|
|
* Quick and dirty way to
|
|
* get a coinbase tx object.
|
|
* @returns {TX}
|
|
*/
|
|
|
|
toCoinbase() {
|
|
return this.coinbase.clone();
|
|
}
|
|
|
|
/**
|
|
* Quick and dirty way to get a block
|
|
* object (most likely to be an invalid one).
|
|
* @returns {Block}
|
|
*/
|
|
|
|
toBlock() {
|
|
const extraNonce = consensus.ZERO_NONCE;
|
|
const mask = consensus.ZERO_HASH;
|
|
const proof = this.getProof(0, this.time, extraNonce, mask);
|
|
return this.commit(proof);
|
|
}
|
|
|
|
/**
|
|
* Calculate the target difficulty.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
getDifficulty() {
|
|
return common.getDifficulty(this.target);
|
|
}
|
|
|
|
/**
|
|
* Set the reward output
|
|
* address and refresh.
|
|
* @param {Address} address
|
|
*/
|
|
|
|
setAddress(address) {
|
|
this.address = new Address(address);
|
|
this.refresh();
|
|
}
|
|
|
|
/**
|
|
* Add a transaction to the template.
|
|
* @param {TX} tx
|
|
* @param {CoinView} view
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
addTX(tx, view) {
|
|
assert(!tx.mutable, 'Cannot add mutable TX to block.');
|
|
|
|
const item = BlockEntry.fromTX(tx, view, this);
|
|
const weight = item.tx.getWeight();
|
|
const sigops = item.sigops;
|
|
const opens = rules.countOpens(tx);
|
|
const updates = rules.countUpdates(tx);
|
|
const renewals = rules.countRenewals(tx);
|
|
|
|
if (!tx.isFinal(this.height, this.mtp))
|
|
return false;
|
|
|
|
if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT)
|
|
return false;
|
|
|
|
if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS)
|
|
return false;
|
|
|
|
if (this.opens + opens > consensus.MAX_BLOCK_OPENS)
|
|
return false;
|
|
|
|
if (this.updates + updates > consensus.MAX_BLOCK_UPDATES)
|
|
return false;
|
|
|
|
if (this.renewals + renewals > consensus.MAX_BLOCK_RENEWALS)
|
|
return false;
|
|
|
|
this.weight += weight;
|
|
this.sigops += sigops;
|
|
this.opens += opens;
|
|
this.updates += updates;
|
|
this.renewals += renewals;
|
|
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]
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
pushTX(tx, view) {
|
|
assert(!tx.mutable, 'Cannot add mutable TX to block.');
|
|
|
|
if (!view)
|
|
view = new CoinView();
|
|
|
|
const item = BlockEntry.fromTX(tx, view, this);
|
|
const weight = item.tx.getWeight();
|
|
const sigops = item.sigops;
|
|
const opens = rules.countOpens(tx);
|
|
const updates = rules.countUpdates(tx);
|
|
const renewals = rules.countRenewals(tx);
|
|
|
|
this.weight += weight;
|
|
this.sigops += sigops;
|
|
this.opens += opens;
|
|
this.updates += updates;
|
|
this.renewals += renewals;
|
|
this.fees += item.fee;
|
|
|
|
// Add the tx to our block
|
|
this.items.push(item);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add a claim to the template.
|
|
* @param {Claim} claim
|
|
* @param {Object} data
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
addClaim(claim, data) {
|
|
const entry = BlockClaim.fromClaim(claim, data);
|
|
|
|
if (entry.commitHeight === 1)
|
|
this.fees += entry.fee;
|
|
|
|
this.claims.push(entry);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add a claim to the template.
|
|
* @param {AirdropProof} proof
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
addAirdrop(proof) {
|
|
const entry = BlockAirdrop.fromAirdrop(proof);
|
|
this.fees += entry.fee;
|
|
this.airdrops.push(entry);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Block Entry
|
|
* @alias module:mining.BlockEntry
|
|
* @property {TX} tx
|
|
* @property {Hash} hash
|
|
* @property {Amount} fee
|
|
* @property {Rate} rate
|
|
* @property {Number} priority
|
|
* @property {Boolean} free
|
|
* @property {Sigops} sigops
|
|
* @property {Number} depCount
|
|
*/
|
|
|
|
class BlockEntry {
|
|
/**
|
|
* Create a block entry.
|
|
* @constructor
|
|
* @param {TX} tx
|
|
*/
|
|
|
|
constructor(tx) {
|
|
this.tx = tx;
|
|
this.hash = tx.hash();
|
|
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}
|
|
*/
|
|
|
|
static fromTX(tx, view, attempt) {
|
|
const item = new this(tx);
|
|
item.fee = tx.getFee(view);
|
|
item.rate = tx.getRate(view);
|
|
item.priority = tx.getPriority(view, attempt.height);
|
|
item.free = false;
|
|
item.sigops = tx.getSigops(view);
|
|
item.descRate = item.rate;
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Instantiate block entry from mempool entry.
|
|
* @param {MempoolEntry} entry
|
|
* @param {BlockTemplate} attempt
|
|
* @returns {BlockEntry}
|
|
*/
|
|
|
|
static fromEntry(entry, attempt) {
|
|
const item = new this(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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Block Claim
|
|
* @alias module:mining.BlockClaim
|
|
*/
|
|
|
|
class BlockClaim {
|
|
/**
|
|
* Create a block entry.
|
|
* @constructor
|
|
*/
|
|
|
|
constructor() {
|
|
this.blob = DUMMY;
|
|
this.nameHash = consensus.ZERO_HASH;
|
|
this.name = DUMMY;
|
|
this.address = new Address();
|
|
this.value = 0;
|
|
this.fee = 0;
|
|
this.rate = 0;
|
|
this.weak = false;
|
|
this.commitHash = consensus.ZERO_HASH;
|
|
this.commitHeight = 0;
|
|
}
|
|
|
|
/**
|
|
* Calculate weight.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
getWeight() {
|
|
const size = 1 + 8 + this.address.getSize() + (90 + this.name.length);
|
|
const weight = size * consensus.WITNESS_SCALE_FACTOR;
|
|
|
|
return 1 + bio.sizeVarBytes(this.blob) + weight;
|
|
}
|
|
|
|
/**
|
|
* Instantiate block entry from transaction.
|
|
* @param {Claim} claim
|
|
* @param {Object} data
|
|
* @returns {BlockClaim}
|
|
*/
|
|
|
|
static fromClaim(claim, data) {
|
|
const size = claim.getVirtualSize();
|
|
const name = Buffer.from(data.name, 'binary');
|
|
const item = new this();
|
|
|
|
item.blob = claim.blob;
|
|
item.nameHash = rules.hashName(name);
|
|
item.name = name;
|
|
item.address = Address.fromHash(data.hash, data.version);
|
|
item.value = data.value;
|
|
item.fee = data.fee;
|
|
item.rate = policy.getRate(size, item.fee);
|
|
item.weak = data.weak;
|
|
item.commitHash = data.commitHash;
|
|
item.commitHeight = data.commitHeight;
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Instantiate block entry from mempool entry.
|
|
* @param {ClaimEntry} entry
|
|
* @returns {BlockClaim}
|
|
*/
|
|
|
|
static fromEntry(entry) {
|
|
const item = new this();
|
|
item.blob = entry.blob;
|
|
item.nameHash = entry.nameHash;
|
|
item.name = entry.name;
|
|
item.address = entry.address;
|
|
item.value = entry.value;
|
|
item.fee = entry.fee;
|
|
item.rate = entry.rate;
|
|
item.weak = entry.weak;
|
|
item.commitHash = entry.commitHash;
|
|
item.commitHeight = entry.commitHeight;
|
|
return item;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Block Airdrop
|
|
* @alias module:mining.BlockAirdrop
|
|
*/
|
|
|
|
class BlockAirdrop {
|
|
/**
|
|
* Create a block entry.
|
|
* @constructor
|
|
*/
|
|
|
|
constructor() {
|
|
this.blob = DUMMY;
|
|
this.position = 0;
|
|
this.address = new Address();
|
|
this.value = 0;
|
|
this.fee = 0;
|
|
this.rate = 0;
|
|
this.weak = false;
|
|
}
|
|
|
|
/**
|
|
* Calculate weight.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
getWeight() {
|
|
const size = 1 + 8 + this.address.getSize() + 5;
|
|
const weight = size * consensus.WITNESS_SCALE_FACTOR;
|
|
|
|
return 1 + bio.sizeVarBytes(this.blob) + weight;
|
|
}
|
|
|
|
/**
|
|
* Instantiate block entry from transaction.
|
|
* @param {AirdropProof} proof
|
|
* @returns {BlockAirdrop}
|
|
*/
|
|
|
|
static fromAirdrop(proof) {
|
|
const size = proof.getVirtualSize();
|
|
const item = new this();
|
|
|
|
item.blob = proof.encode();
|
|
item.position = proof.position();
|
|
item.address = Address.fromHash(proof.address, proof.version);
|
|
item.value = proof.getValue();
|
|
item.fee = proof.fee;
|
|
item.rate = policy.getRate(size, proof.fee);
|
|
item.weak = proof.isWeak();
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Instantiate block airdrop from mempool airdropentry.
|
|
* @param {AirdropEntry} entry
|
|
* @returns {BlockAirdrop}
|
|
*/
|
|
|
|
static fromEntry(entry) {
|
|
const item = new this();
|
|
item.blob = entry.blob;
|
|
item.position = entry.position;
|
|
item.address = entry.address;
|
|
item.value = entry.value;
|
|
item.fee = entry.fee;
|
|
item.rate = entry.rate;
|
|
item.weak = entry.weak;
|
|
return item;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Block Proof
|
|
*/
|
|
|
|
class BlockProof {
|
|
/**
|
|
* Create a block proof.
|
|
* @constructor
|
|
*/
|
|
|
|
constructor() {
|
|
this.hdr = consensus.ZERO_HEADER;
|
|
this.time = 0;
|
|
this.nonce = 0;
|
|
this.extraNonce = consensus.ZERO_NONCE;
|
|
this.mask = consensus.ZERO_HASH;
|
|
}
|
|
|
|
/**
|
|
* @returns {Hash}
|
|
*/
|
|
|
|
hash() {
|
|
return this.powHash();
|
|
}
|
|
|
|
/**
|
|
* @returns {Hash}
|
|
*/
|
|
|
|
shareHash() {
|
|
const hdr = Headers.fromMiner(this.hdr);
|
|
return hdr.shareHash();
|
|
}
|
|
|
|
/**
|
|
* @returns {Hash}
|
|
*/
|
|
|
|
powHash() {
|
|
const hash = this.shareHash();
|
|
|
|
for (let i = 0; i < 32; i++)
|
|
hash[i] ^= this.mask[i];
|
|
|
|
return hash;
|
|
}
|
|
|
|
/**
|
|
* @param {Buffer} target
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
verify(target) {
|
|
return this.powHash().compare(target) <= 0;
|
|
}
|
|
|
|
/**
|
|
* Calculate the target difficulty.
|
|
* @returns {Number}
|
|
*/
|
|
|
|
getDifficulty() {
|
|
return common.getDifficulty(this.powHash());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
exports = BlockTemplate;
|
|
exports.BlockTemplate = BlockTemplate;
|
|
exports.BlockEntry = BlockEntry;
|
|
exports.BlockClaim = BlockClaim;
|
|
exports.BlockAirdrop = BlockAirdrop;
|
|
|
|
module.exports = exports;
|