Source: lib/UTXOPlasmaBlockChain.js

'use strict';

const Web3 = require("web3");

const Block = require("./Block");
// const BlockHeader = require("./BlockHeader");
const Transaction = require("./Transaction");
const UTXO = require("./UTXO");
const RootChain = require("./RootChain");
const MemoryDB = require("./MemoryDB");

const BN = Web3.utils.BN;
const isBN = Web3.utils.isBN;

const utils = require("./utils");
const assert = require("assert");
/**
 * UTXOPlasmaBlockChain, a UTXO plasma chain 
 */
class UTXOPlasmaBlockChain {
  /**
   * Create UTXO plasma chain
   * @param {Object} config configuration object
   */
  constructor(config, DBClass = MemoryDB) {
    this.blockInterval = config.blockInterval || 100000;

    this.txPool = [];
    this.utxo = {};

    this.plasmaOperatorKey = config.plasmaOperatorKey;

    this.db = new DBClass(config);

    this.rootChain = new RootChain(config, this.db);
  }
  /**
   * Connect to the DB
   */
  async connect() {
    await this.db.connect();
    await this.rootChain.init();
    
    this.rootChain.startBlockSubmitter();

    // Replay UTXOs to restore the chain
    let blocks = await this.db.getBlocks();

    for (let i = 0; i < blocks.length; i++) {
      const block = blocks[i];
      await this.db.processBlock(block);

      let blockNumber = block.blockHeader.blockNumber;
      let numTxs = block.transactions.length;
      for (let j = 0; j < numTxs; j++) {
        // console.log(blockNumber, j, block.blockHeader.merkle.getProof(j))
        const txData = block.transactions[j];
        const type = block.types[j];
        const tx = Transaction.fromTxData(txData, type);
        // console.log("tx.type", type, tx.txIndex1, tx.txIndex2)
        switch (tx.type) {
          case Transaction.TxType.DEPOSIT:
            // deposit
            console.log("[BOOTSTRAP]", "blockNumber, tx.blkNum1, tx.blkNum2", blockNumber, tx.blkNum1, tx.blkNum2)
            if (this.canCreateUTXO(blockNumber, tx, j)) {
              await this.createUTXO(blockNumber, tx, j);
            }
            break;
          case Transaction.TxType.MERGE:
            // merge
            console.log("[BOOTSTRAP] this.canSpendUTXO(tx)", this.canSpendUTXO(tx))
            console.log("[BOOTSTRAP] blockNumber, tx.blkNum1, tx.blkNum2", blockNumber, tx.blkNum1, tx.blkNum2)
            if (this.canSpendUTXO(tx) && this.canCreateUTXO(blockNumber, tx, j)) {
              await this.spendUTXO(tx);
              await this.createUTXO(blockNumber, tx, j);
            }
            break;
          case Transaction.TxType.WITHDRAW:
            // withdraw
            if (this.canSpendUTXO(tx)) {
              await this.spendUTXO(tx);
            }
            break;
          case Transaction.TxType.NORMAL:
            // spend
            if (this.canSpendUTXO(tx)) {
              await this.spendUTXO(tx);
            }
            // create
            if (this.canCreateUTXO(blockNumber, tx, j)) {
              await this.createUTXO(blockNumber, tx, j);
            }
            break;
          default:
            break;
        }
        // console.log("blockNumber", blockNumber, "utxo", this.utxo);
      }
    }
  }

  /**
   * Append block to the chain, checking that it is linked correctly.
   * @param {*} newBlock 
   */
  async appendBlock(newBlock) {
    return this.db.appendBlock(newBlock);
  }
  /**
   * Set confirmation signature
   * @param {number} blkNum block number
   * @param {number} txIndex transaction index
   * @param {string} confirmSignature confirmation signature in hex string
   */
  async setConfirmSignature(blkNum, txIndex, confirmSignature) {
    await this.db.setConfirmSignature(blkNum, txIndex, confirmSignature);
  }

  /**
   * Get transaction proof in a block
   * @param {number} blockNumber block number
   * @param {number} txIndex transaction index
   */
  async getTransactionProofInBlock(blockNumber, txIndex) {
    let block = await this.db.getBlock(blockNumber);
    let tx = utils.addHexPrefix(block.transactions[txIndex]);
    let proof = block.blockHeader.merkle.getProof(txIndex);
    return {
      root: block.blockHeader.merkleRoot,
      tx: tx,
      proof: proof,
    };
  };

  /**
   * Get next block number
   */
  async getNextBlockNumber() {
    let previousBlock = await this.db.getLatestBlock();
    let blockNumber = previousBlock.blockHeader.blockNumber;
    return parseInt(blockNumber / 100000) * 100000 + this.blockInterval;
  }


  /**
   * Generate next block
   * @pre only one call at a time. 
   */
  async generateNextBlock() {
    if (this.generatingBlock) {
      return;
    }
    this.generatingBlock = true;
    let blockInterval = this.blockInterval;

    let previousBlock = await this.db.getLatestBlock();
    let previousHash = previousBlock.hash;
    let nextIndex = await this.getNextBlockNumber();

    // Query contract past event for deposits / withdrawals and collect transactions.
    let deposits = await this.rootChain.getDeposits(nextIndex - blockInterval);
    // TODO: filter out the one that already exists in the chain.
    // deposits = deposits.filter((deposit) => {
    //   return this.doesBlockExist(deposit.depositBlock)
    // })
    for (let i = deposits.length - 1; i >= 0; i--) {
      const deposit = deposits[i];
      let existed = await this.db.doesBlockExist(deposit.depositBlock)
      if (existed) {
        deposits.splice(i, 1);
      }
    }

    if (deposits.length > 0) {
      console.log(`${deposits.length} Deposit transactions found.`);

      const depositTxs = await this.createDepositTransactions(deposits);
      // console.log("depositTxs", depositTxs)
      for (let i = 0; i < depositTxs.length; i++) {
        const deposit = deposits[i];
        let dtxs = [];
        const tx = depositTxs[i];
        if (this.canCreateUTXO(deposit.depositBlock, tx, dtxs.length)) {
          await this.createUTXO(deposit.depositBlock, tx, dtxs.length);
          dtxs.push(tx);
        }

        if (!tx.newOwner1) {
          continue
        }
        const mergeTx = await this.createMergeTransaction(tx.newOwner1, tx.token);
        if (mergeTx !== null) {
          if (this.canSpendUTXO(mergeTx) && this.canCreateUTXO(deposit.depositBlock, mergeTx, dtxs.length)) {
            await this.spendUTXO(mergeTx);
            await this.createUTXO(deposit.depositBlock, mergeTx, dtxs.length);
            dtxs.push(mergeTx);
          }
        }

        let newBlock = new Block(deposit.depositBlock, previousHash, dtxs);

        let messageToSign = utils.addHexPrefix(newBlock.blockHeader.toString(false));
        let signature = this.rootChain.signBlock(messageToSign);
        newBlock.blockHeader.setSignature(signature);

        // console.log(newBlock.printBlock());
        newBlock = await this.appendBlock(newBlock).catch((e) => {
          console.log(e);
        });
        if (!newBlock) {
          continue;
        }
        console.log('New deposit block added.');

        dtxs.forEach(async (tx, i) => {
          let confirmSig = await this.generateConfirmSig(newBlock.blockHeader.blockNumber, i, this.plasmaOperatorKey);
          await this.setConfirmSignature(newBlock.blockHeader.blockNumber, i, confirmSig);
        });

        previousBlock = await this.db.getLatestBlock();

        assert(newBlock.hash == previousBlock.hash, "not equal");
        previousHash = previousBlock.hash;
      }
    }

    let withdrawals = await this.rootChain.getWithdrawals(nextIndex - blockInterval);
    let transactions = await this.collectTransactions(nextIndex, [], withdrawals);
    if (transactions.filter((tx) => tx != "").length == 0) {
      this.generatingBlock = false;
      return {};
    }

    let newBlock = new Block(nextIndex, previousHash, transactions);

    // Operator signs the new block.
    let messageToSign = utils.addHexPrefix(newBlock.blockHeader.toString(false));
    let signature = this.rootChain.signBlock(messageToSign);
    newBlock.blockHeader.setSignature(signature);

    // Submit the block header to plasma contract.
    let hexPrefixRoot = utils.addHexPrefix(newBlock.blockHeader.merkleRoot);
    this.rootChain.queueSubmitBlockHeader(nextIndex, hexPrefixRoot);

    // Add the new block to blockchain.
    // console.log(newBlock.printBlock());
    await this.appendBlock(newBlock);
    console.log('New block added.');

    // generate confirmation sigs for the tx that belongs to the operator.
    for (let i = 0; i < transactions.length; i++) {
      let tx = transactions[i];
      if (tx.type == Transaction.TxType.DEPOSIT || tx.type == Transaction.TxType.WITHDRAW || tx.type == Transaction.TxType.MERGE) {
        let confirmSig = await this.generateConfirmSig(newBlock.blockHeader.blockNumber, i, this.plasmaOperatorKey);
        await this.setConfirmSignature(newBlock.blockHeader.blockNumber, i, confirmSig);
      }
    }
    this.generatingBlock = false;
    return newBlock;
  }

  // UTXO and TxPool
  /**
   * Get pending transaction pool.
   */
  getTXPool() {
    return this.txPool;
  }
  /**
   * Get UTXOs
   */
  getUTXO() {
    return Object.values(this.utxo);
  }
  /**
   * Spend UTXO
   * @param {Transaction} tx transaction 
   */
  async spendUTXO(tx) {
    if (tx.blkNum1 !== 0) {
      const utxo = this.getUTXOByIndex(tx.blkNum1, tx.txIndex1, tx.oIndex1, tx.token);
      if (utxo) {
        delete this.utxo[this.getUTXOKey(tx.blkNum1, tx.txIndex1, tx.oIndex1, tx.token)];
        await this.db.spendUTXO(utxo);
      }
    }
    if (tx.blkNum2 !== 0) {
      const utxo = this.getUTXOByIndex(tx.blkNum2, tx.txIndex2, tx.oIndex2, tx.token);
      if (utxo) {
        delete this.utxo[this.getUTXOKey(tx.blkNum2, tx.txIndex2, tx.oIndex2, tx.token)];
        await this.db.spendUTXO(utxo);
      }
    }
  }
  /**
   * Check whether the transaction can be spent.
   * @param {Transaction} tx transaction
   */
  canSpendUTXO(tx) {
    let result1 = false;
    // console.log("tx.blkNum1", tx.blkNum1, typeof (tx.blkNum1))
    // console.log("tx.txIndex1", tx.txIndex1, typeof (tx.txIndex1))
    // console.log("tx.oIndex1", tx.oIndex1, typeof (tx.oIndex1))
    // console.log("tx.blkNum2", tx.blkNum2, typeof (tx.blkNum2))
    // console.log("tx.txIndex2", tx.txIndex2, typeof (tx.txIndex2))
    // console.log("tx.oIndex2", tx.oIndex2, typeof (tx.oIndex2))
    // console.log("tx.token", tx.token, typeof (tx.token))
    if (tx.blkNum1 !== 0) {
      const utxo = this.getUTXOByIndex(tx.blkNum1, tx.txIndex1, tx.oIndex1, tx.token);
      if (utxo) {
        result1 = true;
      } else {
        result1 = false;
      }
    } else {
      result1 = true;
    }
    let result2 = false;
    if (tx.blkNum2 !== 0) {
      const utxo = this.getUTXOByIndex(tx.blkNum2, tx.txIndex2, tx.oIndex2, tx.token);
      if (utxo) {
        result2 = true;
      } else {
        result2 = false;
      }
    } else {
      result2 = true;
    }
    console.log("result1, result2", result1, result2)
    return result1 && result2;
  }
  /**
   * Create UTXO
   * @param {number} blockNumber block number
   * @param {Transaction} tx a transaction object
   * @param {number} txIndex transaction index
   */
  async createUTXO(blockNumber, tx, txIndex) {
    // console.log(tx.newOwner2, tx.denom2)
    if (tx.newOwner1 !== 0 && !tx.denom1.isZero()) {
      let utxo = new UTXO(blockNumber, txIndex, 0, tx.newOwner1, tx.denom1, tx.token)
      this.utxo[this.getUTXOKey(blockNumber, txIndex, 0, tx.token)] = utxo;
      await this.db.createUTXO(utxo);
    }
    // console.log(tx.newOwner2, tx.denom2)
    if (tx.newOwner2 !== 0 && !tx.denom2.isZero()) {
      let utxo = new UTXO(blockNumber, txIndex, 1, tx.newOwner2, tx.denom2, tx.token)
      this.utxo[this.getUTXOKey(blockNumber, txIndex, 1, tx.token)] = utxo;
      await this.db.createUTXO(utxo);
    }
  }
  /**
   * Check whether this transaction can create a UTXO
   * @param {number} blockNumber block number
   * @param {Transaction} tx a transaction object
   * @param {number} txIndex transaction index
   */
  canCreateUTXO(blockNumber, tx, txIndex) {
    if (tx.newOwner1 !== 0 && !tx.denom1.isZero()) {
      return true;
    }
    if (tx.newOwner2 !== 0 && !tx.denom2.isZero()) {
      return true;
    }
    return false;
  }
  /**
   * Get UTXO by owner address
   * @param {string} owner hex string of owner address
   * @param {string} token hex string of token address
   * @param {number} [start=0] starting UTXO index to find
   */
  getUTXOByAddress(owner, token, start = 0) {
    if (!owner) {
      return;
    }
    if (!token || token == "0x0") {
      token = "0x0000000000000000000000000000000000000000"
    }
    let keys = Object.keys(this.utxo);
    for (let i = start; i < keys.length; i++) {
      let key = keys[i];
      if (this.utxo[key].owner.toLowerCase() === owner.toLowerCase() &&
        this.utxo[key].token.toLowerCase() === token.toLowerCase()) {
        return key;
      }
    }
    return;
  };

  /**
   * Get two UTXOs by address
   * @param {string} owner hex string of owner address
   * @param {string} token hex string of token address
   */
  getTwoUTXOsByAddress(owner, token) {
    if (!owner) {
      return [null, null];
    }
    if (!token || token == "0x0") {
      token = "0x0000000000000000000000000000000000000000"
    }
    let indexes = [];
    let keys = Object.keys(this.utxo);
    for (let i = 0; i < keys.length; i++) {
      let key = keys[i];
      if (this.utxo[key].owner.toLowerCase() === owner.toLowerCase() &&
        this.utxo[key].token.toLowerCase() === token.toLowerCase()) {
        indexes.push(key);
        if (indexes.length == 2) {
          return indexes;
        }
      }
    }
    return [null, null];
  };

  getUTXOKey(blkNum, txIndex, oIndex, token) {
    return `${blkNum}:${txIndex}:${oIndex}:${token.toLowerCase()}`;
  }
  /**
   * Get UTXO by index
   * @param {number} blkNum block number
   * @param {number} txIndex transaction index
   * @param {number} oIndex output index
   * @param {string} token hex string of token address
   */
  getUTXOByIndex(blkNum, txIndex, oIndex, token) {
    return this.utxo[this.getUTXOKey(blkNum, txIndex, oIndex, token)];
    // for (let i = 0; i < this.utxo.length; i++) {
    //   if (this.utxo[i].blkNum === blkNum &&
    //     this.utxo[i].txIndex === txIndex &&
    //     this.utxo[i].oIndex === oIndex &&
    //     this.utxo[i].token.toLowerCase() === token.toLowerCase()) {
    //     return i;
    //   }
    // }
    // return -1;
  };

  /**
   * Create a merge transaction to combine UTXO of the same owner and token
   * @param {string} owner hex string of owner address
   * @param {string} token hex string of token address
   */
  createMergeTransaction(owner, token) {
    const indexes = this.getTwoUTXOsByAddress(owner, token);

    if (indexes[0] && indexes[1]) {
      const utxoA = this.utxo[indexes[0]];
      const utxoB = this.utxo[indexes[1]];
      let tx = new Transaction(utxoA.blkNum, utxoA.txIndex, utxoA.oIndex, 0,
        utxoB.blkNum, utxoB.txIndex, utxoB.oIndex, 0,
        owner, utxoA.denom.add(utxoB.denom), 0, 0, 0,
        Transaction.TxType.MERGE, token);
      let signature = this.rootChain.signTransactionKey(tx.hash(), this.plasmaOperatorKey)
      tx.setSignature(signature);
      return tx;
    } else {
      return null;
    }
  };

  /**
   * Check whether the transaction is valid
   * @param {Transaction} tx transaction object
   */
  isValidTransaction(tx) {
    if (+tx.type !== Transaction.TxType.NORMAL) {
      return true;
    }

    let denom = new BN(0);
    if (tx.blkNum1 !== 0) {
      let message = tx.hash();
      let utxo = this.getUTXOByIndex(tx.blkNum1, tx.txIndex1, tx.oIndex1, tx.token);
      console.log(utxo, this.rootChain.isValidSignature(message, tx.sig1, utxo.owner));
      if (utxo &&
        this.rootChain.isValidSignature(message, tx.sig1, utxo.owner)) {
        denom = denom.add(utxo.denom);
      } else {
        return false;
      }
    }
    if (tx.blkNum2 !== 0) {
      let message = tx.hash();
      let utxo = this.getUTXOByIndex(tx.blkNum2, tx.txIndex2, tx.oIndex2, tx.token);
      console.log(utxo);
      if (utxo &&
        this.rootChain.isValidSignature(message, tx.sig2, utxo.owner)) {
        denom = denom.add(utxo.denom);
      } else {
        return false;
      }
    }
    console.log("denom", denom.toString());
    console.log("denom1", tx.denom1.toString());
    console.log("denom2", tx.denom2.toString());
    console.log("fee", tx.fee.toString());
    return denom.eq(tx.denom1.add(tx.denom2).add(tx.fee));
  }

  /**
   * Get transaction hash, its merkle root and signature
   * @param {number} blkNum block number
   * @param {number} txIndex transaction index
   */
  async getTxHashRoot(blkNum, txIndex) {
    let block = await this.db.getBlock(blkNum);
    let data = block.transactions[txIndex];
    // let rawData = utils.removeHexPrefix(data);
    // let txData = utils.addHexPrefix(rawData.slice(0, rawData.length - 130 - 130));
    // let sig1 = utils.addHexPrefix(rawData.slice(rawData.length - 130 - 130, rawData.length - 130));
    let {
      txBytes,
      sig1,
      sig2
    } = Block.splitTxData(data);
    console.log("txBytes", txBytes);
    let txHash = Web3.utils.sha3(txBytes);
    let root = utils.addHexPrefix(block.blockHeader.merkleRoot);
    return {
      txHash,
      root,
      sig1,
      sig2
    }
  }

  /**
   * Generate confirmation signature
   * @param {number} blkNum block number
   * @param {number} txIndex transaction index
   * @param {string} key hex string of the private key
   */
  async generateConfirmSig(blkNum, txIndex, key) {
    let data = await this.getTxHashRoot(blkNum, txIndex);
    let txHash = data.txHash;
    let root = data.root;
    let sig1 = data.sig1;
    let sig2 = data.sig2;
    let confirmSig1 = this.rootChain.confirmSig(txHash, root, key);
    let confirmSig2 = this.rootChain.confirmSig(txHash, root, key);
    if (this.rootChain.isValidConfirmSig(txHash, root, sig1, confirmSig1, sig2, confirmSig2)) {
      return utils.concatHex(confirmSig1, confirmSig2);
    } else {
      return "";
    }
  }

  /**
   * Create deposit transactions from deposit objects collected from deposit events
   * @param {Object[]} deposits deposit objects collected from deposit events
   */
  createDepositTransactions(deposits) {
    return deposits.map(deposit => {
      //console.log("deposit", deposit);
      const depositBlock = deposit.depositBlock;
      const owner = deposit.depositor;
      const amount = deposit.amount;
      const token = deposit.token;
      let tx = new Transaction(depositBlock, 0, 0, 0, 0, 0, 0, 0,
        owner, amount, 0, 0, 0, Transaction.TxType.DEPOSIT, token);
      // console.log("tx.toString(false)", tx.toString(false));
      let signature = this.rootChain.signTransactionKey(tx.hash(), this.plasmaOperatorKey)
      tx.setSignature(signature);
      return tx;
    });
  };

  /**
   * Create withdrawal transactions from withdrawal objects collected from withdrawal events
   * @param {Object[]} withdrawals withdrawal objects collected from withdrawal events
   */
  createWithdrawalTransactions(withdrawals) {
    return withdrawals.map(withdrawal => {
      const utxoPos = withdrawal.utxoPos;
      const blkNum = parseInt(utxoPos / 1000000000);
      const txIndex = parseInt((utxoPos % 1000000000) / 10000);
      const oIndex = parseInt(utxoPos - blkNum * 1000000000 - txIndex * 10000);
      const token = withdrawal.token;
      let tx = new Transaction(blkNum, txIndex, oIndex, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Transaction.TxType.WITHDRAW, token);
      let signature = this.rootChain.signTransactionKey(tx.hash(), this.plasmaOperatorKey)
      tx.setSignature(signature);
      return tx;
    });
  };

  /**
   * Collect all transactions
   * @param {number} blockNumber block number
   * @param {Transaction[]} deposits deposit transactions
   * @param {Transaction[]} withdrawals withdrawal transactions
   */
  async collectTransactions(blockNumber, deposits, withdrawals) {
    const txs = [];

    // if (deposits.length > 0) {
    //   console.log('Deposit transactions found.');
    //   console.log(deposits);
    //   const depositTxs = await this.createDepositTransactions(deposits);
    //   // console.log("depositTxs", depositTxs)
    //   for (let i = 0; i < depositTxs.length; i++) {
    //     const tx = depositTxs[i];
    //     this.createUTXO(blockNumber, tx, txs.length);
    //     txs.push(tx);

    //     if (!tx.newOwner1) {
    //       continue
    //     }
    //     const mergeTx = await this.createMergeTransaction(tx.newOwner1, tx.token);
    //     if (mergeTx !== null) {
    //       this.spendUTXO(mergeTx);
    //       this.createUTXO(blockNumber, mergeTx, txs.length);
    //       txs.push(mergeTx);
    //     }
    //   }
    // }

    if (withdrawals.length > 0) {
      console.log('Withdrawals detected.');
      console.log(withdrawals);
      const withdrawalTxs = await this.createWithdrawalTransactions(withdrawals);
      for (let i = 0; i < withdrawalTxs.length; i++) {
        const tx = withdrawalTxs[i];
        if (this.canSpendUTXO(tx)) {
          await this.spendUTXO(tx);
          txs.push(tx);
        }
      }
    }

    while (this.txPool.length > 0) {
      const tx = this.txPool[0];
      if (this.canCreateUTXO(blockNumber, tx, txs.length)) {
        await this.createUTXO(blockNumber, tx, txs.length);
        txs.push(tx);
        this.txPool.shift();
      }

      const mergeTx1 = await this.createMergeTransaction(tx.newOwner1, tx.token);
      if (mergeTx1 !== null) {
        if (this.canSpendUTXO(mergeTx1) && this.canCreateUTXO(blockNumber, mergeTx1, txs.length)) {
          await this.spendUTXO(mergeTx1);
          await this.createUTXO(blockNumber, mergeTx1, txs.length);
          txs.push(mergeTx1);
        }
      }
      const mergeTx2 = await this.createMergeTransaction(tx.newOwner2, tx.token);
      if (mergeTx2 !== null) {
        if (this.canSpendUTXO(mergeTx2) && this.canCreateUTXO(blockNumber, mergeTx2, txs.length)) {
          await this.spendUTXO(mergeTx2);
          await this.createUTXO(blockNumber, mergeTx2, txs.length);
          txs.push(mergeTx2);
        }
      }

      // Limit transactions per block to power of 2 on purpose for the
      // convenience of building Merkle tree.
      if (txs.length >= 256) {
        break;
      }
    }

    // Fill empty string if transactions are less than 256.
    const len = txs.length;
    for (let i = len; i < 256; i++) {
      txs.push("");
    }
    return txs;
  }

  /**
   * Create an unsigned transaction
   * @param {string} token hex string of token address
   * @param {string} from hex string of from address
   * @param {string} to hex string of to address
   * @param {string} amount the amount to transfer in ether unit
   * @param {string} [confirmSig] the confirmation signature
   */
  async createUnsignedTransaction(token, from, to, amount, confirmSig) {
    let index = this.getUTXOByAddress(from, token);
    if (!index) {
      throw 'No asset found';
    }
    let utxo = this.utxo[index];
    let blkNum1 = utxo.blkNum;
    let txIndex1 = utxo.txIndex;
    let oIndex1 = utxo.oIndex;

    let block = await this.db.getBlock(blkNum1)
    let txHashRoot = await this.getTxHashRoot(blkNum1, txIndex1);
    let txHash = txHashRoot.txHash;
    let root = txHashRoot.root;
    let sig1 = txHashRoot.sig1;
    let sig2 = txHashRoot.sig2;
    confirmSig = confirmSig || block.confirmations[txIndex1] || "";
    console.log("confirmSig", confirmSig);
    // try generating confirmation signature
    let {
      confirmSig1,
      confirmSig2
    } = utils.splitConfirmSig(confirmSig);
    if (!confirmSig || !(this.rootChain.isValidConfirmSig(txHash, root, sig1, confirmSig1, sig2, confirmSig2))) {
      throw 'Invalid confirmation signature';
    }
    this.setConfirmSignature(blkNum1, txIndex1, confirmSig);

    let newOwner1 = to;
    let denom1 = new BN(Web3.utils.toWei("" + amount, "ether"));
    let fee = new BN(Web3.utils.toWei("" + 0.00, "ether")); // hard-coded fee to 0
    if (utxo.denom.lt(denom1.add(fee))) {
      throw 'Insufficient funds';
    }
    let remain = utxo.denom.sub(denom1).sub(fee);
    let newOwner2 = (remain.gt(0)) ? from : 0;
    let denom2 = remain;

    let tx = new Transaction(
      blkNum1, txIndex1, oIndex1, 0, 0, 0, 0, 0,
      newOwner1, denom1, newOwner2, denom2, fee, Transaction.TxType.NORMAL, token);

    return tx;
  }

  /**
   * Create a transaction
   * @param {string} token hex string of token address
   * @param {string} from hex string of from address
   * @param {string} to hex string of to address
   * @param {string} amount the amount to transfer in ether unit
   * @param {string} [confirmSig] the confirmation signature
   * @param {string} key the hex string of the private key
   */
  async createTransaction(token, from, to, amount, confirmSig, key) {
    let index = this.getUTXOByAddress(from, token);
    if (!index) {
      throw 'No asset found';
    }
    let utxo = this.utxo[index];
    let blkNum1 = utxo.blkNum;
    let txIndex1 = utxo.txIndex;
    let oIndex1 = utxo.oIndex;

    let block = await this.db.getBlock(blkNum1)
    let txHashRoot = await this.getTxHashRoot(blkNum1, txIndex1);
    let txHash = txHashRoot.txHash;
    let root = txHashRoot.root;
    let sig1 = txHashRoot.sig1;
    let sig2 = txHashRoot.sig2;
    confirmSig = confirmSig || block.confirmations[txIndex1] || "";
    // try generating confirmation signature
    if (!confirmSig) {
      confirmSig = await this.generateConfirmSig(blkNum1, txIndex1, key)
    }
    let {
      confirmSig1,
      confirmSig2
    } = utils.splitConfirmSig(confirmSig);
    if (!confirmSig || !(this.rootChain.isValidConfirmSig(txHash, root, sig1, confirmSig1, sig2, confirmSig2))) {
      throw 'Invalid confirmation signature';
    }
    this.setConfirmSignature(blkNum1, txIndex1, confirmSig);

    let newOwner1 = to;
    let denom1 = new BN(Web3.utils.toWei("" + amount, "ether"));
    let fee = new BN(Web3.utils.toWei("" + 0.00, "ether")); // hard-coded fee to 0
    if (utxo.denom.lt(denom1.add(fee))) {
      throw 'Insufficient funds';
    }
    let remain = utxo.denom.sub(denom1).sub(fee);
    let newOwner2 = (remain.gt(0)) ? from : 0;
    let denom2 = remain;

    let tx = new Transaction(
      blkNum1, txIndex1, oIndex1, 0, 0, 0, 0, 0,
      newOwner1, denom1, newOwner2, denom2, fee, Transaction.TxType.NORMAL, token);
    let signature = this.rootChain.signTransactionKey(tx.hash(), key);
    tx.setSignature(signature);

    return this.submitTransaction(tx);
  }

  /**
   * Submit Transaction
   * @param {Transaction} tx a transaction object
   * @param {string} confirmSig a confirmation signature
   */
  async submitTransaction(tx, confirmSig) {
    let blkNum1 = tx.blkNum1;
    let txIndex1 = tx.txIndex1;
    let block = await this.db.getBlock(blkNum1)
    let txHashRoot = await this.getTxHashRoot(blkNum1, txIndex1);
    let txHash = txHashRoot.txHash;
    let root = txHashRoot.root;
    let sig1 = txHashRoot.sig1;
    let sig2 = txHashRoot.sig2;
    confirmSig = confirmSig || block.confirmations[txIndex1] || "";

    // TODO: check pending transactions < 256 txs
    let nextBlockNumber = await this.getNextBlockNumber();
    // console.log("confirmSig, nextBlockNumber", confirmSig, nextBlockNumber)
    if (blkNum1 >= nextBlockNumber) {
      console.log("Block has yet been included");
      throw 'Block has yet been included';
    }
    // try generating confirmation signature
    // if (!confirmSig) {
    //   confirmSig = await this.generateConfirmSig(blkNum1, txIndex1, key)
    // }
    // if (!confirmSig || !(this.rootChain.isValidConfirmSig(txHash, root, sig1, confirmSig))) {
    //   throw 'Invalid confirmation signature';
    // }
    let {
      confirmSig1,
      confirmSig2
    } = utils.splitConfirmSig(confirmSig);
    if (!confirmSig || !(this.rootChain.isValidConfirmSig(txHash, root, sig1, confirmSig1, sig2, confirmSig2))) {
      throw 'Invalid confirmation signature';
    }
    this.setConfirmSignature(blkNum1, txIndex1, confirmSig);

    console.log("this.isValidTransaction(tx)", this.isValidTransaction(tx))
    console.log("this.canSpendUTXO(tx)", this.canSpendUTXO(tx))
    if (this.isValidTransaction(tx) && this.canSpendUTXO(tx)) {
      await this.spendUTXO(tx);
      this.txPool.push(tx);
    } else {
      console.log("Invalid transaction");

      throw 'Invalid transaction';
    }
    return tx;
  }
}

module.exports = UTXOPlasmaBlockChain;