Source: primitives/txmeta.js

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

'use strict';

const assert = require('bsert');
const bio = require('bufio');
const util = require('../utils/util');
const TX = require('./tx');
const {inspectSymbol} = require('../utils');

/**
 * TXMeta
 * An extended transaction object.
 * @alias module:primitives.TXMeta
 */

class TXMeta {
  /**
   * Create an extended transaction.
   * @constructor
   * @param {Object?} options
   */

  constructor(options) {
    this.tx = new TX();
    this.mtime = util.now();
    this.height = -1;
    this.block = null;
    this.time = 0;
    this.index = -1;

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

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

  fromOptions(options) {
    if (options.tx) {
      assert(options.tx instanceof TX);
      this.tx = options.tx;
    }

    if (options.mtime != null) {
      assert((options.mtime >>> 0) === options.mtime);
      this.mtime = options.mtime;
    }

    if (options.height != null) {
      assert(Number.isSafeInteger(options.height));
      this.height = options.height;
    }

    if (options.block !== undefined) {
      assert(options.block == null || Buffer.isBuffer(options.block));
      this.block = options.block;
    }

    if (options.time != null) {
      assert((options.time >>> 0) === options.time);
      this.time = options.time;
    }

    if (options.index != null) {
      assert(Number.isSafeInteger(options.index));
      this.index = options.index;
    }

    return this;
  }

  /**
   * Instantiate TXMeta from options.
   * @param {Object} options
   * @returns {TXMeta}
   */

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

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

  fromTX(tx, entry, index) {
    this.tx = tx;
    if (entry) {
      this.height = entry.height;
      this.block = entry.hash;
      this.time = entry.time;
      this.index = index;
    }
    return this;
  }

  /**
   * Instantiate TXMeta from options.
   * @param {Object} options
   * @returns {TXMeta}
   */

  static fromTX(tx, entry, index) {
    return new this().fromTX(tx, entry, index);
  }

  /**
   * Inspect the transaction.
   * @returns {Object}
   */

  [inspectSymbol]() {
    return this.format();
  }

  /**
   * Inspect the transaction.
   * @returns {Object}
   */

  format(view) {
    const data = this.tx.format(view, null, this.index);
    data.mtime = this.mtime;
    data.height = this.height;
    data.block = this.block ? util.revHex(this.block) : null;
    data.time = this.time;
    return data;
  }

  /**
   * Convert transaction to JSON.
   * @returns {Object}
   */

  toJSON() {
    return this.getJSON();
  }

  /**
   * Convert the transaction to an object suitable
   * for JSON serialization.
   * @param {Network} network
   * @param {CoinView} view
   * @returns {Object}
   */

  getJSON(network, view, chainHeight) {
    const json = this.tx.getJSON(network, view, null, this.index);
    json.mtime = this.mtime;
    json.height = this.height;
    json.block = this.block ? util.revHex(this.block) : null;
    json.time = this.time;
    json.confirmations = 0;

    if (chainHeight != null && this.height !== -1)
      json.confirmations = chainHeight - this.height + 1;

    return json;
  }

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

  fromJSON(json) {
    this.tx.fromJSON(json);

    assert((json.mtime >>> 0) === json.mtime);
    assert(Number.isSafeInteger(json.height));
    assert(!json.block || typeof json.block === 'string');
    assert((json.time >>> 0) === json.time);
    assert(Number.isSafeInteger(json.index));

    this.mtime = json.mtime;
    this.height = json.height;
    this.block = util.fromRev(json.block);
    this.index = json.index;

    return this;
  }

  /**
   * Instantiate a transaction from a
   * jsonified transaction object.
   * @param {Object} json - The jsonified transaction object.
   * @returns {TX}
   */

  static fromJSON(json) {
    return new this().fromJSON(json);
  }

  /**
   * Calculate serialization size.
   * @returns {Number}
   */

  getSize() {
    let size = 0;

    size += this.tx.getSize();
    size += 4;

    if (this.block) {
      size += 1;
      size += 32;
      size += 4 * 3;
    } else {
      size += 1;
    }

    return size;
  }

  /**
   * Serialize a transaction to "extended format".
   * This is the serialization format bcoin uses internally
   * to store transactions in the database. The extended
   * serialization includes the height, block hash, index,
   * timestamp, and pending-since time.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    const bw = bio.write(size);

    this.tx.toWriter(bw);

    bw.writeU32(this.mtime);

    if (this.block) {
      bw.writeU8(1);
      bw.writeHash(this.block);
      bw.writeU32(this.height);
      bw.writeU32(this.time);
      bw.writeU32(this.index);
    } else {
      bw.writeU8(0);
    }

    return bw.render();
  }

  /**
   * Inject properties from "extended" serialization format.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    const br = bio.read(data);

    this.tx.fromReader(br);

    this.mtime = br.readU32();

    if (br.readU8() === 1) {
      this.block = br.readHash();
      this.height = br.readU32();
      this.time = br.readU32();
      this.index = br.readU32();
      if (this.index === 0x7fffffff)
        this.index = -1;
    }

    return this;
  }

  /**
   * Instantiate a transaction from a Buffer
   * in "extended" serialization format.
   * @param {Buffer} data
   * @param {String?} enc - One of `"hex"` or `null`.
   * @returns {TX}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }

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

  static isTXMeta(obj) {
    return obj instanceof TXMeta;
  }
}

/*
 * Expose
 */

module.exports = TXMeta;