Source: blockchain/records.js

/*!
 * records.js - chaindb records
 * Copyright (c) 2024 The Handshake Developers (MIT License).
 * https://github.com/handshake-org/hsd
 */

'use strict';

const assert = require('bsert');
const bio = require('bufio');
const {BufferMap} = require('buffer-map');
const consensus = require('../protocol/consensus');
const Network = require('../protocol/network');

/**
 * ChainFlags
 */

class ChainFlags extends bio.Struct {
  /**
   * Create chain flags.
   * @alias module:blockchain.ChainFlags
   * @constructor
   */

  constructor(options) {
    super();

    this.network = Network.primary;
    this.spv = false;
    this.prune = false;
    this.indexTX = false;
    this.indexAddress = false;

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

  fromOptions(options) {
    this.network = Network.get(options.network);

    if (options.spv != null) {
      assert(typeof options.spv === 'boolean');
      this.spv = options.spv;
    }

    if (options.prune != null) {
      assert(typeof options.prune === 'boolean');
      this.prune = options.prune;
    }

    if (options.indexTX != null) {
      assert(typeof options.indexTX === 'boolean');
      this.indexTX = options.indexTX;
    }

    if (options.indexAddress != null) {
      assert(typeof options.indexAddress === 'boolean');
      this.indexAddress = options.indexAddress;
    }

    return this;
  }

  getSize() {
    return 12;
  }

  write(bw) {
    let flags = 0;

    if (this.spv)
      flags |= 1 << 0;

    if (this.prune)
      flags |= 1 << 1;

    if (this.indexTX)
      flags |= 1 << 2;

    if (this.indexAddress)
      flags |= 1 << 3;

    bw.writeU32(this.network.magic);
    bw.writeU32(flags);
    bw.writeU32(0);

    return bw;
  }

  read(br) {
    this.network = Network.fromMagic(br.readU32());

    const flags = br.readU32();

    this.spv = (flags & 1) !== 0;
    this.prune = (flags & 2) !== 0;
    this.indexTX = (flags & 4) !== 0;
    this.indexAddress = (flags & 8) !== 0;

    return this;
  }
}

/**
 * Chain State
 */

class ChainState extends bio.Struct {
  /**
   * Create chain state.
   * @alias module:blockchain.ChainState
   * @constructor
   */

  constructor() {
    super();
    this.tip = consensus.ZERO_HASH;
    this.tx = 0;
    this.coin = 0;
    this.value = 0;
    this.burned = 0;
    this.committed = false;
  }

  inject(state) {
    this.tip = state.tip;
    this.tx = state.tx;
    this.coin = state.coin;
    this.value = state.value;
    this.burned = state.burned;
    return this;
  }

  connect(block) {
    this.tx += block.txs.length;
  }

  disconnect(block) {
    this.tx -= block.txs.length;
  }

  add(coin) {
    this.coin += 1;
    this.value += coin.value;
  }

  spend(coin) {
    this.coin -= 1;
    this.value -= coin.value;
  }

  burn(coin) {
    this.coin += 1;
    this.burned += coin.value;
  }

  unburn(coin) {
    this.coin -= 1;
    this.burned -= coin.value;
  }

  commit(hash) {
    assert(Buffer.isBuffer(hash));
    this.tip = hash;
    this.committed = true;
    return this.encode();
  }

  getSize() {
    return 64;
  }

  write(bw) {
    bw.writeHash(this.tip);
    bw.writeU64(this.tx);
    bw.writeU64(this.coin);
    bw.writeU64(this.value);
    bw.writeU64(this.burned);
    return bw;
  }

  read(br) {
    this.tip = br.readHash();
    this.tx = br.readU64();
    this.coin = br.readU64();
    this.value = br.readU64();
    this.burned = br.readU64();
    return this;
  }
}

/**
 * State Cache
 */

class StateCache {
  /**
   * Create state cache.
   * @alias module:blockchain.StateCache
   * @constructor
   */

  constructor(network) {
    this.network = network;
    this.bits = [];
    this.updates = [];
    this.init();
  }

  init() {
    for (let i = 0; i < 32; i++)
      this.bits.push(null);

    for (const {bit} of this.network.deploys) {
      assert(!this.bits[bit]);
      this.bits[bit] = new BufferMap();
    }
  }

  set(bit, entry, state) {
    const cache = this.bits[bit];

    assert(cache);

    if (cache.get(entry.hash) !== state) {
      cache.set(entry.hash, state);
      this.updates.push(new CacheUpdate(bit, entry.hash, state));
    }
  }

  get(bit, entry) {
    const cache = this.bits[bit];

    assert(cache);

    const state = cache.get(entry.hash);

    if (state == null)
      return -1;

    return state;
  }

  commit() {
    this.updates.length = 0;
  }

  drop() {
    for (const {bit, hash} of this.updates) {
      const cache = this.bits[bit];
      assert(cache);
      cache.delete(hash);
    }

    this.updates.length = 0;
  }

  insert(bit, hash, state) {
    const cache = this.bits[bit];
    assert(cache);
    cache.set(hash, state);
  }
}

/**
 * Cache Update
 */

class CacheUpdate {
  /**
   * Create cache update.
   * @constructor
   * @ignore
   */

  constructor(bit, hash, state) {
    this.bit = bit;
    this.hash = hash;
    this.state = state;
  }

  encode() {
    const data = Buffer.allocUnsafe(1);
    data[0] = this.state;
    return data;
  }
}

/**
 * Tree related state.
 */

class TreeState extends bio.Struct {
  /**
   * Create tree state.
   * @constructor
   * @ignore
   */

  constructor() {
    super();
    this.treeRoot = consensus.ZERO_HASH;
    this.commitHeight = 0;
    this.compactionRoot = consensus.ZERO_HASH;
    this.compactionHeight = 0;

    this.committed = false;
  }

  inject(state) {
    this.treeRoot = state.treeRoot;
    this.commitHeight = state.treeHeight;
    this.compactionHeight = state.compactionHeight;
    this.compactionRoot = state.compactionRoot;

    return this;
  }

  compact(hash, height) {
    assert(Buffer.isBuffer(hash));
    assert((height >>> 0) === height);

    this.compactionRoot = hash;
    this.compactionHeight = height;
  };

  commit(hash, height) {
    assert(Buffer.isBuffer(hash));
    assert((height >>> 0) === height);

    this.treeRoot = hash;
    this.commitHeight = height;
    this.committed = true;
    return this.encode();
  }

  getSize() {
    return 72;
  }

  write(bw) {
    bw.writeHash(this.treeRoot);
    bw.writeU32(this.commitHeight);
    bw.writeHash(this.compactionRoot);
    bw.writeU32(this.compactionHeight);

    return bw;
  }

  read(br) {
    this.treeRoot = br.readHash();
    this.commitHeight = br.readU32();
    this.compactionRoot = br.readHash();
    this.compactionHeight = br.readU32();

    return this;
  }
}

/*
 * Expose
 */

exports.ChainFlags = ChainFlags;
exports.ChainState = ChainState;
exports.StateCache = StateCache;
exports.TreeState = TreeState;
exports.CacheUpdate = CacheUpdate;