Source: coins/coinentry.js

/*!
 * coinentry.js - coin entry 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 Coin = require('../primitives/coin');
const Output = require('../primitives/output');
const compress = require('./compress');
const {encoding} = bio;

/*
 * Constants
 */

const NUM_FLAGS = 1;
const MAX_HEIGHT = ((1 << (32 - NUM_FLAGS)) >>> 0) - 1;

/**
 * Coin Entry
 * Represents an unspent output.
 * @alias module:coins.CoinEntry
 * @property {Number} version - Transaction version.
 * @property {Number} height - Transaction height (-1 if unconfirmed).
 * @property {Boolean} coinbase - Whether the containing
 * transaction is a coinbase.
 * @property {Output} output
 * @property {Boolean} spent
 * @property {Buffer} raw
 */

class CoinEntry extends bio.Struct {
  /**
   * Create a coin entry.
   * @constructor
   */

  constructor() {
    super();
    this.version = 1;
    this.height = -1;
    this.coinbase = false;
    this.output = new Output();
    this.spent = false;
    this.raw = null;
  }

  /**
   * Convert coin entry to an output.
   * @returns {Output}
   */

  toOutput() {
    return this.output;
  }

  /**
   * Convert coin entry to a coin.
   * @param {Outpoint} prevout
   * @returns {Coin}
   */

  toCoin(prevout) {
    const coin = new Coin();
    coin.version = this.version;
    coin.height = this.height;
    coin.coinbase = this.coinbase;
    coin.value = this.output.value;
    coin.address = this.output.address;
    coin.covenant = this.output.covenant;
    coin.hash = prevout.hash;
    coin.index = prevout.index;
    return coin;
  }

  /**
   * Inject properties from TX.
   * @param {TX} tx
   * @param {Number} index
   */

  fromOutput(output) {
    this.output = output;
    return this;
  }

  /**
   * Instantiate a coin from a TX
   * @param {TX} tx
   * @param {Number} index - Output index.
   * @returns {CoinEntry}
   */

  static fromOutput(output) {
    return new this().fromOutput(output);
  }

  /**
   * Inject properties from TX.
   * @param {TX} tx
   * @param {Number} index
   */

  fromCoin(coin) {
    this.version = coin.version;
    this.height = coin.height;
    this.coinbase = coin.coinbase;
    this.output.value = coin.value;
    this.output.address = coin.address;
    this.output.covenant = coin.covenant;
    return this;
  }

  /**
   * Instantiate a coin from a TX
   * @param {TX} tx
   * @returns {CoinEntry}
   */

  static fromCoin(coin) {
    return new this().fromCoin(coin);
  }

  /**
   * Inject properties from TX.
   * @param {TX} tx
   * @param {Number} index
   */

  fromTX(tx, index, height) {
    assert(typeof index === 'number');
    assert(typeof height === 'number');
    assert(index >= 0 && index < tx.outputs.length);
    this.version = tx.version;
    this.height = height;
    this.coinbase = tx.isCoinbase();
    this.output = tx.outputs[index];
    return this;
  }

  /**
   * Instantiate a coin from a TX
   * @param {TX} tx
   * @param {Number} index - Output index.
   * @returns {CoinEntry}
   */

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

  /**
   * Calculate size of coin.
   * @returns {Number}
   */

  getSize() {
    if (this.raw)
      return this.raw.length;

    let size = 0;
    size += encoding.sizeVarint(this.version);
    size += 4;
    size += compress.size(this.output);

    return size;
  }

  /**
   * Write the coin to a buffer writer.
   * @param {BufferWriter} bw
   */

  write(bw) {
    if (this.raw) {
      bw.writeBytes(this.raw);
      return bw;
    }

    let height = this.height;
    let field = 0;

    if (this.coinbase)
      field |= 1;

    if (height === -1)
      height = MAX_HEIGHT;

    field |= height << NUM_FLAGS;

    bw.writeVarint(this.version);
    bw.writeU32(field);
    compress.pack(this.output, bw);

    return bw;
  }

  /**
   * Serialize the coin.
   * @returns {Buffer}
   */

  encode() {
    if (!this.raw)
      this.raw = super.encode();

    return this.raw;
  }

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

  read(br) {
    const version = br.readVarint();
    const field = br.readU32();

    let height = field >>> NUM_FLAGS;

    if (height === MAX_HEIGHT)
      height = -1;

    this.version = version;
    this.coinbase = (field & 1) !== 0;
    this.height = height;

    compress.unpack(this.output, br);

    return this;
  }

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

  decode(data) {
    super.decode(data);
    this.raw = data;
    return this;
  }
}

/*
 * Expose
 */

module.exports = CoinEntry;