Source: primitives/memblock.js

/*!
 * memblock.js - memblock block 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 AbstractBlock = require('./abstractblock');
const Block = require('./block');
const Headers = require('./headers');
const Input = require('./input');
const Output = require('./output');
const consensus = require('../protocol/consensus');
const DUMMY = Buffer.alloc(0);

/**
 * Mem Block
 * A block object which is essentially a "placeholder"
 * for a full {@link Block} object. The v8 garbage
 * collector's head will explode if there is too much
 * data on the javascript heap. Blocks can currently
 * be up to 1mb in size. In the future, they may be
 * 2mb, 8mb, or maybe 20mb, who knows? A MemBlock
 * is an optimization which defers parsing of
 * the serialized transactions (the block Buffer) until
 * the block has passed through the chain queue and
 * is about to enter the chain. This keeps a lot data
 * off of the javascript heap for most of the time a
 * block even exists in memory, and manages to keep a
 * lot of strain off of the garbage collector. Having
 * 500mb of blocks on the js heap would not be a good
 * thing.
 * @alias module:primitives.MemBlock
 * @extends AbstractBlock
 */

class MemBlock extends AbstractBlock {
  /**
   * Create a mem block.
   * @constructor
   */

  constructor() {
    super();

    this._raw = DUMMY;
  }

  /**
   * Test whether the block is a memblock.
   * @returns {Boolean}
   */

  isMemory() {
    return true;
  }

  /**
   * Retrieve deterministically random padding.
   * @param {Number} size
   * @returns {Buffer}
   */

  padding(size) {
    assert((size >>> 0) === size);

    const pad = Buffer.alloc(size);
    const prevBlock = this._raw.slice(12, 12 + 32);
    const treeRoot = this._raw.slice(12 + 32, 12 + 32 + 32);

    for (let i = 0; i < size; i++)
      pad[i] = prevBlock[i % 32] ^ treeRoot[i % 32];

    return pad;
  }

  /**
   * Serialize the block headers.
   * @returns {Buffer}
   */

  toPrehead() {
    return Headers.decode(this._raw).toPrehead();
  }

  /**
   * Calculate PoW hash.
   * @returns {Buffer}
   */

  powHash() {
    const hash = this.shareHash();
    const mask = this._raw.slice(consensus.HEADER_SIZE - 32,
                                 consensus.HEADER_SIZE);

    for (let i = 0; i < 32; i++)
      hash[i] ^= mask[i];

    return hash;
  }

  /**
   * Serialize the block headers.
   * @returns {Buffer}
   */

  toHead() {
    return this._raw.slice(0, consensus.HEADER_SIZE);
  }

  /**
   * Get the full block size.
   * @returns {Number}
   */

  getSize() {
    return this._raw.length;
  }

  /**
   * Verify the block.
   * @returns {Boolean}
   */

  verifyBody() {
    return true;
  }

  /**
   * Retrieve the coinbase height
   * from the coinbase input script.
   * @returns {Number} height (-1 if not present).
   */

  getCoinbaseHeight() {
    try {
      return this.parseCoinbaseHeight();
    } catch (e) {
      return -1;
    }
  }

  /**
   * Parse the coinbase height
   * from the coinbase input script.
   * @private
   * @returns {Number} height (-1 if not present).
   */

  parseCoinbaseHeight() {
    const br = bio.read(this._raw, true);

    br.seek(consensus.HEADER_SIZE);

    const txCount = br.readVarint();

    if (txCount === 0)
      return -1;

    br.seek(4);

    const inCount = br.readVarint();

    for (let i = 0; i < inCount; i++)
      Input.read(br);

    const outCount = br.readVarint();

    for (let i = 0; i < outCount; i++)
      Output.read(br);

    return br.readU32();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  read(br) {
    assert(br.offset === 0);

    this.readHead(br);

    this._raw = br.data;

    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  decode(data) {
    const br = bio.read(data, true);
    return this.read(br);
  }

  /**
   * Return serialized block data.
   * @returns {Buffer}
   */

  write(bw) {
    bw.writeBytes(this._raw);
    return bw;
  }

  /**
   * Return serialized block data.
   * @returns {Buffer}
   */

  encode() {
    return this._raw;
  }

  /**
   * Parse the serialized block data
   * and create an actual {@link Block}.
   * @returns {Block}
   * @throws Parse error
   */

  toBlock() {
    const block = Block.decode(this._raw);

    block._hash = this._hash;

    return block;
  }

  /**
   * Convert the block to a headers object.
   * @returns {Headers}
   */

  toHeaders() {
    return Headers.fromBlock(this);
  }

  /**
   * Test whether an object is a MemBlock.
   * @param {Object} obj
   * @returns {Boolean}
   */

  static isMemBlock(obj) {
    return obj instanceof MemBlock;
  }
}

/*
 * Expose
 */

module.exports = MemBlock;