Source: lib/chain.js

'use strict';

const Block = require('./block');
const Ledger = require('./ledger');
const State = require('./state');
const Store = require('./store');

const MerkleTree = require('merkletreejs');

/**
 * Chain.
 * @property {String} name Current name.
 * @property {Map} indices
 * @property {Ledger} ledger
 * @property {Storage} storage
 */
class Chain extends Ledger {
  /**
   * Holds an immutable chain of events.
   * @param       {Vector} genesis Initial state for the chain of events.
   */
  constructor (origin) {
    super(origin);

    this.name = (origin) ? origin.name : 'playnet';
    this.config = Object.assign({
      name: this.name,
      type: 'sha256'
    }, origin);

    this.genesis = new State(this.config);
    this.state['@data'] = this.genesis['@data'];

    // TODO: set this up via define?
    this.indices = {
      blocks: '/blocks',
      transactions: '/transactions'
    };

    this.ledger = new Ledger();
    this.storage = new Store({
      path: './data/chain'
    });

    Object.defineProperty(this, 'ledger', {
      enumerable: false,
      writable: false
    });

    Object.defineProperty(this, 'storage', {
      enumerable: false,
      writable: false
    });

    return this;
  }

  static fromObject (data) {
    return new Chain(data);
  }

  get tip () {
    return this.ledger.tip;
  }

  get root () {
    return this.mast.getRoot();
  }

  get blocks () {
    return this.state.blocks || [];
  }

  get leaves () {
    return this.blocks.map(x => Buffer.from(x['@id'], 'hex'));
  }

  get _tree () {
    return new MerkleTree(this.leaves, this.sha256, {
      isBitcoinTree: true
    });
  }

  async start () {
    let chain = this;

    await chain.storage.open();
    await chain.ledger.start();

    // TODO: define all state transitions
    chain.state.blocks = [chain.genesis];

    // blindly bind all events
    this.trust(chain.ledger);

    // before returning, ensure a commit
    await chain.commit();

    return chain;
  }

  async stop () {
    await this.commit();
    await this.ledger.stop();
    await this.storage.close();

    return this;
  }

  async open () {
    return this.storage.open();
  }

  async close () {
    return this.storage.close();
  }

  async _load () {
    let chain = this;

    let query = await chain.storage.get('/blocks');
    let response = new State(query);

    this.log('query:', query);
    this.log('response:', response);
    this.log('response id:', response.id);

    return chain;
  }

  async append (block) {
    if (!block['@id'] || !block['@data']) {
      block = new State(block);
    }

    let self = this;
    let path = [self.indices.blocks, block.id].join('/');

    // Chains always have a genesis.
    if (self.blocks.length === 0 && !self.genesis) {
      self.genesis = block['@id'];
    }

    await self.ledger.append(block['@data']);
    await self.storage._PUT(path, block);

    self.state.blocks.push(block);

    self['@tree'] = new MerkleTree(this.leaves, this.sha256, {
      isBitcoinTree: true
    });

    self.emit('block', block['@id'], block['@data']);

    await self.commit();

    return self;
  }

  async _listBlocks () {
    let self = this;
    let blocks = await self.storage.get(self.indices.blocks);

    return blocks;
  }

  async mine () {
    let block = new State({
      parent: this.id
    });
    return block.commit();
  }

  async commit () {
    // reject invalid chains
    if (!this.ledger || !this.ledger.pages) return null;

    let input = this.ledger.pages;
    let state = new State(input);
    let commit = await state.commit();
    let script = []; // validation

    this['@data'] = input;
    this['@id'] = state.id;

    script.push(`${state.id.toString('hex')}`);
    script.push(`OP_PUSH32`);
    script.push(`OP_ALLOC`);
    script.push(`${JSON.stringify(state['@data'])}`);
    script.push(`OP_SHA256`);
    script.push(`${state.id}`);
    script.push(`OP_EQUALVERIFY`);

    if (commit['@changes']) {
      this.emit('state', state['@data']);
      // this.emit('changes', commit['@changes']);
    }

    return new Block(commit['@changes']);
  }

  async verify (level = 4, depth = 6) {
    this.log(`Verification Level ${level} running from -${depth}...`);
    console.log('root:', this.root);
    return (this['@id'] === this.root);
  }

  render () {
    console.log('[CHAIN]', '[RENDER]', this);
    return `<Chain id="${this.id}" />`;
  }
}

module.exports = Chain;