Source: mempool/mempoolentry.js

/*!
 * mempoolentry.js - mempool entry object for bcoin
 * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

const bio = require('bufio');
const policy = require('../protocol/policy');
const util = require('../utils/util');
const Script = require('../script/script');
const TX = require('../primitives/tx');

/**
 * Mempool Entry
 * Represents a mempool entry.
 * @alias module:mempool.MempoolEntry
 * @property {TX} tx
 * @property {Number} height
 * @property {Number} priority
 * @property {Number} time
 * @property {Amount} value
 */

class MempoolEntry {
  /**
   * Create a mempool entry.
   * @constructor
   * @param {Object} options
   * @param {TX} options.tx - Transaction in mempool.
   * @param {Number} options.height - Entry height.
   * @param {Number} options.priority - Entry priority.
   * @param {Number} options.time - Entry time.
   * @param {Amount} options.value - Value of on-chain coins.
   */

  constructor(options) {
    this.tx = null;
    this.height = -1;
    this.size = 0;
    this.sigops = 0;
    this.priority = 0;
    this.fee = 0;
    this.deltaFee = 0;
    this.time = 0;
    this.value = 0;
    this.coinbase = false;
    this.dependencies = false;
    this.descFee = 0;
    this.descSize = 0;

    if (options)
      this.fromOptions(options);
  }

  /**
   * Inject properties from options object.
   * @private
   * @param {Object} options
   */

  fromOptions(options) {
    this.tx = options.tx;
    this.height = options.height;
    this.size = options.size;
    this.sigops = options.sigops;
    this.priority = options.priority;
    this.fee = options.fee;
    this.deltaFee = options.deltaFee;
    this.time = options.time;
    this.value = options.value;
    this.coinbase = options.coinbase;
    this.dependencies = options.dependencies;
    this.descFee = options.descFee;
    this.descSize = options.descSize;
    return this;
  }

  /**
   * Instantiate mempool entry from options.
   * @param {Object} options
   * @returns {MempoolEntry}
   */

  static fromOptions(options) {
    return new this().fromOptions(options);
  }

  /**
   * Inject properties from transaction.
   * @private
   * @param {TX} tx
   * @param {Number} height
   */

  fromTX(tx, view, height) {
    const flags = Script.flags.STANDARD_VERIFY_FLAGS;
    const value = tx.getChainValue(view);
    const sigops = tx.getSigopsCost(view, flags);
    const size = tx.getSigopsSize(sigops);
    const priority = tx.getPriority(view, height, size);
    const fee = tx.getFee(view);

    let dependencies = false;
    let coinbase = false;

    for (const {prevout} of tx.inputs) {
      if (view.isCoinbase(prevout))
        coinbase = true;

      if (view.getHeight(prevout) === -1)
        dependencies = true;
    }

    this.tx = tx;
    this.height = height;
    this.size = size;
    this.sigops = sigops;
    this.priority = priority;
    this.fee = fee;
    this.deltaFee = fee;
    this.time = util.now();
    this.value = value;
    this.coinbase = coinbase;
    this.dependencies = dependencies;
    this.descFee = fee;
    this.descSize = size;

    return this;
  }

  /**
   * Create a mempool entry from a TX.
   * @param {TX} tx
   * @param {Number} height - Entry height.
   * @returns {MempoolEntry}
   */

  static fromTX(tx, view, height) {
    return new this().fromTX(tx, view, height);
  }

  /**
   * Calculate transaction hash.
   * @param {String?} enc
   * @returns {Hash}
   */

  hash(enc) {
    return this.tx.hash(enc);
  }

  /**
   * Calculate reverse transaction hash.
   * @returns {Hash}
   */

  txid() {
    return this.tx.txid();
  }

  /**
   * Calculate priority, taking into account
   * the entry height delta, modified size,
   * and chain value.
   * @param {Number} height
   * @returns {Number} Priority.
   */

  getPriority(height) {
    const delta = height - this.height;
    const priority = (delta * this.value) / this.size;

    let result = this.priority + Math.floor(priority);

    if (result < 0)
      result = 0;

    return result;
  }

  /**
   * Get fee.
   * @returns {Amount}
   */

  getFee() {
    return this.fee;
  }

  /**
   * Get delta fee.
   * @returns {Amount}
   */

  getDeltaFee() {
    return this.deltaFee;
  }

  /**
   * Calculate fee rate.
   * @returns {Rate}
   */

  getRate() {
    return policy.getRate(this.size, this.fee);
  }

  /**
   * Calculate delta fee rate.
   * @returns {Rate}
   */

  getDeltaRate() {
    return policy.getRate(this.size, this.deltaFee);
  }

  /**
   * Calculate fee cumulative descendant rate.
   * @returns {Rate}
   */

  getDescRate() {
    return policy.getRate(this.descSize, this.descFee);
  }

  /**
   * Calculate the memory usage of a transaction.
   * Note that this only calculates the JS heap
   * size. Sizes of buffers are ignored (the v8
   * heap is what we care most about). All numbers
   * are based on the output of v8 heap snapshots
   * of TX objects.
   * @returns {Number} Usage in bytes.
   */

  memUsage() {
    const tx = this.tx;
    let total = 0;

    total += 176; // mempool entry
    total += 48; // coinbase
    total += 48; // dependencies

    total += 208; // tx
    total += 80; // _hash
    total += 88; // _hhash
    total += 80; // _raw
    total += 80; // _whash
    total += 48; // mutable

    total += 32; // input array

    for (const input of tx.inputs) {
      total += 120; // input
      total += 104; // prevout
      total += 88; // prevout hash

      total += 40; // script
      total += 80; // script raw buffer
      total += 32; // script code array
      total += input.script.code.length * 40; // opcodes

      for (const op of input.script.code) {
        if (op.data)
          total += 80; // op buffers
      }

      total += 96; // witness
      total += 32; // witness items
      total += input.witness.items.length * 80; // witness buffers
    }

    total += 32; // output array

    for (const output of tx.outputs) {
      total += 104; // output
      total += 40; // script
      total += 80; // script raw buffer
      total += 32; // script code array
      total += output.script.code.length * 40; // opcodes

      for (const op of output.script.code) {
        if (op.data)
          total += 80; // op buffers
      }
    }

    return total;
  }

  /**
   * Test whether the entry is free with
   * the current priority (calculated by
   * current height).
   * @param {Number} height
   * @returns {Boolean}
   */

  isFree(height) {
    const priority = this.getPriority(height);
    return priority > policy.FREE_THRESHOLD;
  }

  /**
   * Get entry serialization size.
   * @returns {Number}
   */

  getSize() {
    return this.tx.getSize() + 42;
  }

  /**
   * Serialize entry to a buffer.
   * @returns {Buffer}
   */

  toRaw() {
    const bw = bio.write(this.getSize());
    bw.writeBytes(this.tx.toRaw());
    bw.writeU32(this.height);
    bw.writeU32(this.size);
    bw.writeU32(this.sigops);
    bw.writeDouble(this.priority);
    bw.writeU64(this.fee);
    bw.writeU32(this.time);
    bw.writeU64(this.value);
    bw.writeU8(this.coinbase ? 1 : 0);
    bw.writeU8(this.dependencies ? 1 : 0);
    return bw.render();
  }

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

  fromRaw(data) {
    const br = bio.read(data);
    this.tx = TX.fromReader(br);
    this.height = br.readU32();
    this.size = br.readU32();
    this.sigops = br.readU32();
    this.priority = br.readDouble();
    this.fee = br.readU64();
    this.deltaFee = this.fee;
    this.time = br.readU32();
    this.value = br.readU64();
    this.coinbase = br.readU8() === 1;
    this.dependencies = br.readU8() === 1;
    this.descFee = this.fee;
    this.descSize = this.size;
    return this;
  }

  /**
   * Instantiate entry from serialized data.
   * @param {Buffer} data
   * @returns {MempoolEntry}
   */

  static fromRaw(data) {
    return new this().fromRaw(data);
  }
}

/*
 * Expose
 */

module.exports = MempoolEntry;