var imports = require('soop').imports(); var util = imports.util || require('./util/util'); var Debug1 = imports.Debug1 || function() {}; var Script = imports.Script || require('./Script'); var Bignum = imports.Bignum || require('bignum'); var Binary = imports.Binary || require('binary'); var Step = imports.Step || require('step'); var buffertools = imports.buffertools || require('buffertools'); var Transaction = imports.Transaction || require('./Transaction'); var TransactionIn = Transaction.In; var TransactionOut = Transaction.Out; var COINBASE_OP = Transaction.COINBASE_OP; var VerificationError = imports.VerificationError || require('./util/error').VerificationError; var BlockRules = { maxTimeOffset: 2 * 60 * 60, // How far block timestamps can be into the future largestHash: Bignum(2).pow(256) }; function Block(data) { if ("object" !== typeof data) { data = {}; } this.hash = data.hash || null; this.prev_hash = data.prev_hash || util.NULL_HASH; this.merkle_root = data.merkle_root || util.NULL_HASH; this.timestamp = data.timestamp || 0; this.bits = data.bits || 0; this.nonce = data.nonce || 0; this.version = data.version || 0; this.height = data.height || 0; this.size = data.size || 0; this.active = data.active || false; this.chainWork = data.chainWork || util.EMPTY_BUFFER; this.txs = data.txs || []; } Block.prototype.getHeader = function getHeader() { var buf = new Buffer(80); var ofs = 0; buf.writeUInt32LE(this.version, ofs); ofs += 4; this.prev_hash.copy(buf, ofs); ofs += 32; this.merkle_root.copy(buf, ofs); ofs += 32; buf.writeUInt32LE(this.timestamp, ofs); ofs += 4; buf.writeUInt32LE(this.bits, ofs); ofs += 4; buf.writeUInt32LE(this.nonce, ofs); ofs += 4; return buf; }; Block.prototype.parse = function parse(parser, headerOnly) { this.version = parser.word32le(); this.prev_hash = parser.buffer(32); this.merkle_root = parser.buffer(32); this.timestamp = parser.word32le(); this.bits = parser.word32le(); this.nonce = parser.word32le(); this.txs = []; this.size = 0; if (headerOnly) return; var txCount = parser.varInt(); for (var i = 0; i < txCount; i++) { var tx = new Transaction(); tx.parse(parser); this.txs.push(tx); } }; Block.prototype.calcHash = function calcHash() { var header = this.getHeader(); return util.twoSha256(header); }; Block.prototype.checkHash = function checkHash() { if (!this.hash || !this.hash.length) return false; return buffertools.compare(this.calcHash(), this.hash) == 0; }; Block.prototype.getHash = function getHash() { if (!this.hash || !this.hash.length) this.hash = this.calcHash(); return this.hash; }; Block.prototype.checkProofOfWork = function checkProofOfWork() { var target = util.decodeDiffBits(this.bits); // TODO: Create a compare method in node-buffertools that uses the correct // endian so we don't have to reverse both buffers before comparing. var reverseHash = buffertools.reverse(this.hash); if (buffertools.compare(reverseHash, target) > 0) { throw new VerificationError('Difficulty target not met'); } return true; }; /** * Returns the amount of work that went into this block. * * Work is defined as the average number of tries required to meet this * block's difficulty target. For example a target that is greater than 5% * of all possible hashes would mean that 20 "work" is required to meet it. */ Block.prototype.getWork = function getWork() { var target = util.decodeDiffBits(this.bits, true); return BlockRules.largestHash.div(target.add(1)); }; Block.prototype.checkTimestamp = function checkTimestamp() { var currentTime = new Date().getTime() / 1000; if (this.timestamp > currentTime + BlockRules.maxTimeOffset) { throw new VerificationError('Timestamp too far into the future'); } return true; }; Block.prototype.checkTransactions = function checkTransactions(txs) { if (!Array.isArray(txs) || txs.length <= 0) { throw new VerificationError('No transactions'); } if (!txs[0].isCoinBase()) { throw new VerificationError('First tx must be coinbase'); } for (var i = 1; i < txs.length; i++) { if (txs[i].isCoinBase()) { throw new VerificationError('Tx index '+i+' must not be coinbase'); } } return true; }; /** * Build merkle tree. * * Ported from Java. Original code: BitcoinJ by Mike Hearn * Copyright (c) 2011 Google Inc. */ Block.prototype.getMerkleTree = function getMerkleTree(txs) { // The merkle hash is based on a tree of hashes calculated from the transactions: // // merkleHash // /\ // / \ // A B // / \ / \ // tx1 tx2 tx3 tx4 // // Basically transactions are hashed, then the hashes of the transactions are hashed // again and so on upwards into the tree. The point of this scheme is to allow for // disk space savings later on. // // This function is a direct translation of CBlock::BuildMerkleTree(). if (txs.length == 0) { return [util.NULL_HASH.slice(0)]; } // Start by adding all the hashes of the transactions as leaves of the tree. var tree = txs.map(function (tx) { return tx instanceof Transaction ? tx.getHash() : tx; }); var j = 0; // Now step through each level ... for (var size = txs.length; size > 1; size = Math.floor((size + 1) / 2)) { // and for each leaf on that level .. for (var i = 0; i < size; i += 2) { var i2 = Math.min(i + 1, size - 1); var a = tree[j + i]; var b = tree[j + i2]; tree.push(util.twoSha256(Buffer.concat([a,b]))); } j += size; } return tree; }; Block.prototype.calcMerkleRoot = function calcMerkleRoot(txs) { var tree = this.getMerkleTree(txs); return tree[tree.length - 1]; }; Block.prototype.checkMerkleRoot = function checkMerkleRoot(txs) { if (!this.merkle_root || !this.merkle_root.length) { throw new VerificationError('No merkle root'); } if (buffertools.compare(this.calcMerkleRoot(txs), new Buffer(this.merkle_root)) !== 0) { throw new VerificationError('Merkle root incorrect'); } return true; }; Block.prototype.checkBlock = function checkBlock(txs) { if (!this.checkHash()) { throw new VerificationError("Block hash invalid"); } this.checkProofOfWork(); this.checkTimestamp(); if (txs) { this.checkTransactions(txs); if (!this.checkMerkleRoot(txs)) { throw new VerificationError("Merkle hash invalid"); } } return true; }; Block.getBlockValue = function getBlockValue(height) { var subsidy = Bignum(50).mul(util.COIN); subsidy = subsidy.div(Bignum(2).pow(Math.floor(height / 210000))); return subsidy; }; Block.prototype.getBlockValue = function getBlockValue() { return Block.getBlockValue(this.height); }; Block.prototype.toString = function toString() { return ""; }; Block.prototype.createCoinbaseTx = function createCoinbaseTx(beneficiary) { var tx = new Transaction(); tx.ins.push(new TransactionIn({ s: util.EMPTY_BUFFER, q: 0xffffffff, o: COINBASE_OP })); tx.outs.push(new TransactionOut({ v: util.bigIntToValue(this.getBlockValue()), s: Script.createPubKeyOut(beneficiary).getBuffer() })); return tx; }; Block.prototype.solve = function solve(miner, callback) { var header = this.getHeader(); var target = util.decodeDiffBits(this.bits); miner.solve(header, target, callback); }; /** * Returns an object with the same field names as jgarzik's getblock patch. */ Block.prototype.getStandardizedObject = function getStandardizedObject(txs) { var block = { hash: util.formatHashFull(this.getHash()), version: this.version, prev_block: util.formatHashFull(this.prev_hash), mrkl_root: util.formatHashFull(this.merkle_root), time: this.timestamp, bits: this.bits, nonce: this.nonce, height: this.height }; if (txs) { var mrkl_tree = this.getMerkleTree(txs).map(function (buffer) { return util.formatHashFull(buffer); }); block.mrkl_root = mrkl_tree[mrkl_tree.length - 1]; block.n_tx = txs.length; var totalSize = 80; // Block header totalSize += util.getVarIntSize(txs.length); // txn_count txs = txs.map(function (tx) { tx = tx.getStandardizedObject(); totalSize += tx.size; return tx; }); block.size = totalSize; block.tx = txs; block.mrkl_tree = mrkl_tree; } else { block.size = this.size; } return block; }; module.exports = require('soop')(Block);