<commit_before>/*!
 * txdb.js - persistent transaction pool
 * Copyright (c) 2014-2015, <NAME> (MIT License)
 * Copyright (c) 2014-2016, <NAME> (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

var util = require('../utils/util');
var LRU = require('../utils/lru');
var co = require('../utils/co');
var assert = require('assert');
var BufferReader = require('../utils/reader');
var StaticWriter = require('../utils/staticwriter');
var Amount = require('../btc/amount');
var CoinView = require('../coins/coinview');
var Coin = require('../primitives/coin');
var Outpoint = require('../primitives/outpoint');
var records = require('./records');
var layout = require('./layout').txdb;
var encoding = require('../utils/encoding');
var policy = require('../protocol/policy');
var Script = require('../script/script');
var BlockMapRecord = records.BlockMapRecord;
var OutpointMapRecord = records.OutpointMapRecord;
var TXRecord = records.TXRecord;
var DUMMY = new Buffer([0]);

/**
 * TXDB
 * @exports TXDB
 * @constructor
 * @param {Wallet} wallet
 */

function TXDB(wallet) {
  if (!(this instanceof TXDB))
    return new TXDB(wallet);

  this.wallet = wallet;
  this.walletdb = wallet.db;
  this.db = wallet.db.db;
  this.logger = wallet.db.logger;
  this.network = wallet.db.network;
  this.options = wallet.db.options;
  this.coinCache = new LRU(10000);

  this.locked = {};
  this.state = null;
  this.pending = null;
  this.events = [];
}

/**
 * Database layout.
 * @type {Object}
 */

TXDB.layout = layout;

/**
 * Open TXDB.
 * @returns {Promise}
 */

TXDB.prototype.open = co(function* open() {
  var state = yield this.getState();

  if (state) {
    this.state = state;
    this.logger.info('TXDB loaded for %s.', this.wallet.id);
  } else {
    this.state = new TXDBState(this.wallet.wid, this.wallet.id);
    this.logger.info('TXDB created for %s.', this.wallet.id);
  }

  this.logger.info('TXDB State: tx=%d coin=%s.',
    this.state.tx, this.state.coin);

  this.logger.info(
    'Balance: unconfirmed=%s confirmed=%s.',
    Amount.btc(this.state.unconfirmed),
    Amount.btc(this.state.confirmed));
});

/**
 * Start batch.
 * @private
 */

TXDB.prototype.start = function start() {
  this.pending = this.state.clone();
  this.coinCache.start();
  return this.wallet.start();
};

/**
 * Drop batch.
 * @private
 */

TXDB.prototype.drop = function drop() {
  this.pending = null;
  this.events.length = 0;
  this.coinCache.drop();
  return this.wallet.drop();
};

/**
 * Clear batch.
 * @private
 */

TXDB.prototype.clear = function clear() {
  this.pending = this.state.clone();
  this.events.length = 0;
  this.coinCache.clear();
  return this.wallet.clear();
};

/**
 * Save batch.
 * @returns {Promise}
 */

TXDB.prototype.commit = co(function* commit() {
  var i, item;

  try {
    yield this.wallet.commit();
  } catch (e) {
    this.pending = null;
    this.events.length = 0;
    this.coinCache.drop();
    throw e;
  }

  // Overwrite the entire state
  // with our new committed state.
  if (this.pending.committed) {
    this.state = this.pending;

    // Emit buffered events now that
    // we know everything is written.
    for (i = 0; i < this.events.length; i++) {
      item = this.events[i];
      this.walletdb.emit(item[0], this.wallet.id, item[1], item[2]);
      this.wallet.emit(item[0], item[1], item[2]);
    }
  }

  this.pending = null;
  this.events.length = 0;
  this.coinCache.commit();
});

/**
 * Emit transaction event.
 * @private
 * @param {String} event
 * @param {Object} data
 * @param {Details} details
 */

TXDB.prototype.emit = function emit(event, data, details) {
  this.events.push([event, data, details]);
};

/**
 * Prefix a key.
 * @param {Buffer} key
 * @returns {Buffer} Prefixed key.
 */

TXDB.prototype.prefix = function prefix(key) {
  assert(this.wallet.wid);
  return layout.prefix(this.wallet.wid, key);
};

/**
 * Put key and value to current batch.
 * @param {String} key
 * @param {Buffer} value
 */

TXDB.prototype.put = function put(key, value) {
  assert(this.wallet.current);
  this.wallet.current.put(this.prefix(key), value);
};

/**
 * Delete key from current batch.
 * @param {String} key
 */

TXDB.prototype.del = function del(key) {
  assert(this.wallet.current);
  this.wallet.current.del(this.prefix(key));
};

/**
 * Get.
 * @param {String} key
 */

TXDB.prototype.get = function get(key) {
  return this.db.get(this.prefix(key));
};

/**
 * Has.
 * @param {String} key
 */

TXDB.prototype.has = function has(key) {
  return this.db.has(this.prefix(key));
};

/**
 * Iterate.
 * @param {Object} options
 * @returns {Promise}
 */

TXDB.prototype.range = function range(options) {
  if (options.gte)
    options.gte = this.prefix(options.gte);
  if (options.lte)
    options.lte = this.prefix(options.lte);
  return this.db.range(options);
};

/**
 * Iterate.
 * @param {Object} options
 * @returns {Promise}
 */

TXDB.prototype.keys = function keys(options) {
  if (options.gte)
    options.gte = this.prefix(options.gte);
  if (options.lte)
    options.lte = this.prefix(options.lte);
  return this.db.keys(options);
};

/**
 * Iterate.
 * @param {Object} options
 * @returns {Promise}
 */

TXDB.prototype.values = function values(options) {
  if (options.gte)
    options.gte = this.prefix(options.gte);
  if (options.lte)
    options.lte = this.prefix(options.lte);
  return this.db.values(options);
};

/**
 * Get wallet path for output.
 * @param {Output} output
 * @returns {Promise} - Returns {@link Path}.
 */

TXDB.prototype.getPath = function getPath(output) {
  if (!output)
    return Promise.resolve();
  return this.wallet.getPath(output.getAddress());
};

/**
 * Test whether path exists for output.
 * @param {Output} output
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.hasPath = function hasPath(output) {
  if (!output)
    return Promise.resolve();
  return this.wallet.hasPath(output.getAddress());
};

/**
 * Save credit.
 * @param {Credit} credit
 * @param {Path} path
 */

TXDB.prototype.saveCredit = co(function* saveCredit(credit, path) {
  var coin = credit.coin;
  var key = coin.toKey();
  var raw = credit.toRaw();

  yield this.addOutpointMap(coin.hash, coin.index);

  this.put(layout.c(coin.hash, coin.index), raw);
  this.put(layout.C(path.account, coin.hash, coin.index), DUMMY);

  this.coinCache.push(key, raw);
});

/**
 * Remove credit.
 * @param {Credit} credit
 * @param {Path} path
 */

TXDB.prototype.removeCredit = co(function* removeCredit(credit, path) {
  var coin = credit.coin;
  var key = coin.toKey();

  yield this.removeOutpointMap(coin.hash, coin.index);

  this.del(layout.c(coin.hash, coin.index));
  this.del(layout.C(path.account, coin.hash, coin.index));

  this.coinCache.unpush(key);
});

/**
 * Spend credit.
 * @param {Credit} credit
 * @param {TX} tx
 * @param {Number} index
 */

TXDB.prototype.spendCredit = function spendCredit(credit, tx, index) {
  var prevout = tx.inputs[index].prevout;
  var spender = Outpoint.fromTX(tx, index);
  this.put(layout.s(prevout.hash, prevout.index), spender.toRaw());
  this.put(layout.d(spender.hash, spender.index), credit.coin.toRaw());
};

/**
 * Unspend credit.
 * @param {TX} tx
 * @param {Number} index
 */

TXDB.prototype.unspendCredit = function unspendCredit(tx, index) {
  var prevout = tx.inputs[index].prevout;
  var spender = Outpoint.fromTX(tx, index);
  this.del(layout.s(prevout.hash, prevout.index));
  this.del(layout.d(spender.hash, spender.index));
};

/**
 * Write input record.
 * @param {TX} tx
 * @param {Number} index
 */

TXDB.prototype.writeInput = function writeInput(tx, index) {
  var prevout = tx.inputs[index].prevout;
  var spender = Outpoint.fromTX(tx, index);
  this.put(layout.s(prevout.hash, prevout.index), spender.toRaw());
};

/**
 * Remove input record.
 * @param {TX} tx
 * @param {Number} index
 */

TXDB.prototype.removeInput = function removeInput(tx, index) {
  var prevout = tx.inputs[index].prevout;
  this.del(layout.s(prevout.hash, prevout.index));
};

/**
 * Resolve orphan input.
 * @param {TX} tx
 * @param {Number} index
 * @param {Number} height
 * @param {Path} path
 * @returns {Boolean}
 */

TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, height, path) {
  var hash = tx.hash('hex');
  var spent = yield this.getSpent(hash, index);
  var stx, credit;

  if (!spent)
    return false;

  // If we have an undo coin, we
  // already knew about this input.
  if (yield this.hasSpentCoin(spent))
    return false;

  // Get the spending transaction so
  // we can properly add the undo coin.
  stx = yield this.getTX(spent.hash);
  assert(stx);

  // Crete the credit and add the undo coin.
  credit = Credit.fromTX(tx, index, height);

  this.spendCredit(credit, stx.tx, spent.index);

  // If the spender is unconfirmed, save
  // the credit as well, and mark it as
  // unspent in the mempool. This is the
  // same behavior `insert` would have
  // done for inputs. We're just doing
  // it retroactively.
  if (stx.height === -1) {
    credit.spent = true;
    yield this.saveCredit(credit, path);
    if (height !== -1)
      this.pending.confirmed += credit.coin.value;
  }

  return true;
});

/**
 * Test an entire transaction to see
 * if any of its outpoints are a double-spend.
 * @param {TX} tx
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.isDoubleSpend = co(function* isDoubleSpend(tx) {
  var i, input, prevout, spent;

  for (i = 0; i < tx.inputs.length; i++) {
    input = tx.inputs[i];
    prevout = input.prevout;
    spent = yield this.isSpent(prevout.hash, prevout.index);
    if (spent)
      return true;
  }

  return false;
});

/**
 * Test an entire transaction to see
 * if any of its outpoints are replace by fee.
 * @param {TX} tx
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.isRBF = co(function* isRBF(tx) {
  var i, input, prevout;

  if (tx.isRBF())
    return true;

  for (i = 0; i < tx.inputs.length; i++) {
    input = tx.inputs[i];
    prevout = input.prevout;
    if (yield this.has(layout.r(prevout.hash)))
      return true;
  }

  return false;
});

/**
 * Test a whether a coin has been spent.
 * @param {Hash} hash
 * @param {Number} index
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.getSpent = co(function* getSpent(hash, index) {
  var data = yield this.get(layout.s(hash, index));

  if (!data)
    return;

  return Outpoint.fromRaw(data);
});

/**
 * Test a whether a coin has been spent.
 * @param {Hash} hash
 * @param {Number} index
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.isSpent = function isSpent(hash, index) {
  return this.has(layout.s(hash, index));
};

/**
 * Append to the global unspent record.
 * @param {Hash} hash
 * @param {Number} index
 * @returns {Promise}
 */

TXDB.prototype.addOutpointMap = co(function* addOutpointMap(hash, i) {
  var map = yield this.walletdb.getOutpointMap(hash, i);

  if (!map)
    map = new OutpointMapRecord(hash, i);

  if (!map.add(this.wallet.wid))
    return;

  this.walletdb.writeOutpointMap(this.wallet, hash, i, map);
});

/**
 * Remove from the global unspent record.
 * @param {Hash} hash
 * @param {Number} index
 * @returns {Promise}
 */

TXDB.prototype.removeOutpointMap = co(function* removeOutpointMap(hash, i) {
  var map = yield this.walletdb.getOutpointMap(hash, i);

  if (!map)
    return;

  if (!map.remove(this.wallet.wid))
    return;

  if (map.wids.length === 0) {
    this.walletdb.unwriteOutpointMap(this.wallet, hash, i);
    return;
  }

  this.walletdb.writeOutpointMap(this.wallet, hash, i, map);
});

/**
 * Append to the global block record.
 * @param {Hash} hash
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.addBlockMap = co(function* addBlockMap(hash, height) {
  var block = yield this.walletdb.getBlockMap(height);

  if (!block)
    block = new BlockMapRecord(height);

  if (!block.add(hash, this.wallet.wid))
    return;

  this.walletdb.writeBlockMap(this.wallet, height, block);
});

/**
 * Remove from the global block record.
 * @param {Hash} hash
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.removeBlockMap = co(function* removeBlockMap(hash, height) {
  var block = yield this.walletdb.getBlockMap(height);

  if (!block)
    return;

  if (!block.remove(hash, this.wallet.wid))
    return;

  if (block.txs.length === 0) {
    this.walletdb.unwriteBlockMap(this.wallet, height);
    return;
  }

  this.walletdb.writeBlockMap(this.wallet, height, block);
});

/**
 * List block records.
 * @returns {Promise}
 */

TXDB.prototype.getBlocks = function getBlocks() {
  return this.keys({
    gte: layout.b(0),
    lte: layout.b(0xffffffff),
    parse: function(key) {
      return layout.bb(key);
    }
  });
};

/**
 * Get block record.
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.getBlock = co(function* getBlock(height) {
  var data = yield this.get(layout.b(height));

  if (!data)
    return;

  return BlockRecord.fromRaw(data);
});

/**
 * Append to the global block record.
 * @param {Hash} hash
 * @param {BlockMeta} meta
 * @returns {Promise}
 */

TXDB.prototype.addBlock = co(function* addBlock(hash, meta) {
  var key = layout.b(meta.height);
  var data = yield this.get(key);
  var block, size;

  if (!data) {
    block = BlockRecord.fromMeta(meta);
    data = block.toRaw();
  }

  block = new Buffer(data.length + 32);
  data.copy(block, 0);

  size = block.readUInt32LE(40, true);
  block.writeUInt32LE(size + 1, 40, true);
  hash.copy(block, data.length);

  this.put(key, block);
});

/**
 * Remove from the global block record.
 * @param {Hash} hash
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.removeBlock = co(function* removeBlock(hash, height) {
  var key = layout.b(height);
  var data = yield this.get(key);
  var block, size;

  if (!data)
    return;

  size = data.readUInt32LE(40, true);

  assert(size > 0);
  assert(util.equal(data.slice(-32), hash));

  if (size === 1) {
    this.del(key);
    return;
  }

  block = data.slice(0, -32);
  block.writeUInt32LE(size - 1, 40, true);

  this.put(key, block);
});

/**
 * Append to the global block record.
 * @param {Hash} hash
 * @param {BlockMeta} meta
 * @returns {Promise}
 */

TXDB.prototype.addBlockSlow = co(function* addBlockSlow(hash, meta) {
  var block = yield this.getBlock(meta.height);

  if (!block)
    block = BlockRecord.fromMeta(meta);

  if (!block.add(hash))
    return;

  this.put(layout.b(meta.height), block.toRaw());
});

/**
 * Remove from the global block record.
 * @param {Hash} hash
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.removeBlockSlow = co(function* removeBlockSlow(hash, height) {
  var block = yield this.getBlock(height);

  if (!block)
    return;

  if (!block.remove(hash))
    return;

  if (block.hashes.length === 0) {
    this.del(layout.b(height));
    return;
  }

  this.put(layout.b(height), block.toRaw());
});

/**
 * Add transaction, potentially runs
 * `confirm()` and `removeConflicts()`.
 * @param {TX} tx
 * @param {BlockMeta} block
 * @returns {Promise}
 */

TXDB.prototype.add = co(function* add(tx, block) {
  var result;

  this.start();

  try {
    result = yield this._add(tx, block);
  } catch (e) {
    this.drop();
    throw e;
  }

  yield this.commit();

  return result;
});

/**
 * Add transaction without a batch.
 * @private
 * @param {TX} tx
 * @returns {Promise}
 */

TXDB.prototype._add = co(function* add(tx, block) {
  var hash = tx.hash('hex');
  var existing = yield this.getTX(hash);
  var wtx;

  assert(!tx.mutable, 'Cannot add mutable TX to wallet.');

  if (existing) {
    // Existing tx is already confirmed. Ignore.
    if (existing.height !== -1)
      return;

    // The incoming tx won't confirm the
    // existing one anyway. Ignore.
    if (!block)
      return;

    // Confirm transaction.
    return yield this._confirm(existing, block);
  }

  wtx = TXRecord.fromTX(tx, block);

  if (!block) {
    // We ignore any unconfirmed txs
    // that are replace-by-fee.
    if (yield this.isRBF(tx)) {
      // We need to index every spender
      // hash to detect "passive"
      // replace-by-fee.
      this.put(layout.r(hash), DUMMY);
      return;
    }

    // Potentially remove double-spenders.
    // Only remove if they're not confirmed.
    if (!(yield this.removeConflicts(tx, true)))
      return;
  } else {
    // Potentially remove double-spenders.
    yield this.removeConflicts(tx, false);

    // Delete the replace-by-fee record.
    this.del(layout.r(hash));
  }

  // Finally we can do a regular insertion.
  return yield this.insert(wtx, block);
});

/**
 * Insert transaction.
 * @private
 * @param {TXRecord} wtx
 * @param {BlockMeta} block
 * @returns {Promise}
 */

TXDB.prototype.insert = co(function* insert(wtx, block) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var height = block ? block.height : -1;
  var details = new Details(this, wtx, block);
  var updated = false;
  var i, input, output, coin;
  var prevout, credit, path, account;

  if (!tx.isCoinbase()) {
    // We need to potentially spend some coins here.
    for (i = 0; i < tx.inputs.length; i++) {
      input = tx.inputs[i];
      prevout = input.prevout;
      credit = yield this.getCredit(prevout.hash, prevout.index);

      if (!credit) {
        // Maintain an stxo list for every
        // spent input (even ones we don't
        // recognize). This is used for
        // detecting double-spends (as best
        // we can), as well as resolving
        // inputs we didn't know were ours
        // at the time. This built-in error
        // correction is not technically
        // necessary assuming no messages
        // are ever missed from the mempool,
        // but shit happens.
        this.writeInput(tx, i);
        continue;
      }

      coin = credit.coin;

      // Do some verification.
      if (!block) {
        if (!(yield this.verifyInput(tx, i, coin))) {
          this.clear();
          return;
        }
      }

      path = yield this.getPath(coin);
      assert(path);

      // Build the tx details object
      // as we go, for speed.
      details.setInput(i, path, coin);

      // Write an undo coin for the credit
      // and add it to the stxo set.
      this.spendCredit(credit, tx, i);

      // Unconfirmed balance should always
      // be updated as it reflects the on-chain
      // balance _and_ mempool balance assuming
      // everything in the mempool were to confirm.
      this.pending.coin--;
      this.pending.unconfirmed -= coin.value;

      if (!block) {
        // If the tx is not mined, we do not
        // disconnect the coin, we simply mark
        // a `spent` flag on the credit. This
        // effectively prevents the mempool
        // from altering our utxo state
        // permanently. It also makes it
        // possible to compare the on-chain
        // state vs. the mempool state.
        credit.spent = true;
        yield this.saveCredit(credit, path);
      } else {
        // If the tx is mined, we can safely
        // remove the coin being spent. This
        // coin will be indexed as an undo
        // coin so it can be reconnected
        // later during a reorg.
        this.pending.confirmed -= coin.value;
        yield this.removeCredit(credit, path);
      }

      updated = true;
    }
  }

  // Potentially add coins to the utxo set.
  for (i = 0; i < tx.outputs.length; i++) {
    output = tx.outputs[i];
    path = yield this.getPath(output);

    if (!path)
      continue;

    details.setOutput(i, path);

    // Attempt to resolve an input we
    // did not know was ours at the time.
    if (yield this.resolveInput(tx, i, height, path)) {
      updated = true;
      continue;
    }

    credit = Credit.fromTX(tx, i, height);

    this.pending.coin++;
    this.pending.unconfirmed += output.value;

    if (block)
      this.pending.confirmed += output.value;

    yield this.saveCredit(credit, path);

    updated = true;
  }

  // If this didn't update any coins,
  // it's not our transaction.
  if (!updated) {
    // Clear the spent list inserts.
    this.clear();
    return;
  }

  // Save and index the transaction record.
  this.put(layout.t(hash), wtx.toRaw());
  this.put(layout.m(wtx.ps, hash), DUMMY);

  if (!block)
    this.put(layout.p(hash), DUMMY);
  else
    this.put(layout.h(height, hash), DUMMY);

  // Do some secondary indexing for account-based
  // queries. This saves us a lot of time for
  // queries later.
  for (i = 0; i < details.accounts.length; i++) {
    account = details.accounts[i];

    this.put(layout.T(account, hash), DUMMY);
    this.put(layout.M(account, wtx.ps, hash), DUMMY);

    if (!block)
      this.put(layout.P(account, hash), DUMMY);
    else
      this.put(layout.H(account, height, hash), DUMMY);
  }

  // Update block records.
  if (block) {
    yield this.addBlockMap(hash, height);
    yield this.addBlock(tx.hash(), block);
  }

  // Update the transaction counter and
  // commit the new state. This state will
  // only overwrite the best state once
  // the batch has actually been written
  // to disk.
  this.pending.tx++;
  this.put(layout.R, this.pending.commit());

  // This transaction may unlock some
  // coins now that we've seen it.
  this.unlockTX(tx);

  // Emit events for potential local and
  // websocket listeners. Note that these
  // will only be emitted if the batch is
  // successfully written to disk.
  this.emit('tx', tx, details);
  this.emit('balance', this.pending.toBalance(), details);

  return details;
});

/**
 * Attempt to confirm a transaction.
 * @private
 * @param {TX} tx
 * @param {BlockMeta} block
 * @returns {Promise}
 */

TXDB.prototype.confirm = co(function* confirm(hash, block) {
  var wtx = yield this.getTX(hash);
  var details;

  if (!wtx)
    return;

  if (wtx.height !== -1)
    throw new Error('TX is already confirmed.');

  assert(block);

  this.start();

  try {
    details = yield this._confirm(wtx, block);
  } catch (e) {
    this.drop();
    throw e;
  }

  yield this.commit();

  return details;
});

/**
 * Attempt to confirm a transaction.
 * @private
 * @param {TXRecord} wtx
 * @param {BlockMeta} block
 * @returns {Promise}
 */

TXDB.prototype._confirm = co(function* confirm(wtx, block) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var height = block.height;
  var details = new Details(this, wtx, block);
  var i, account, output, coin, input, prevout;
  var path, credit, credits;

  wtx.setBlock(block);

  if (!tx.isCoinbase()) {
    credits = yield this.getSpentCredits(tx);

    // Potentially spend coins. Now that the tx
    // is mined, we can actually _remove_ coins
    // from the utxo state.
    for (i = 0; i < tx.inputs.length; i++) {
      input = tx.inputs[i];
      prevout = input.prevout;
      credit = credits[i];

      // There may be new credits available
      // that we haven't seen yet.
      if (!credit) {
        credit = yield this.getCredit(prevout.hash, prevout.index);

        if (!credit)
          continue;

        // Add a spend record and undo coin
        // for the coin we now know is ours.
        // We don't need to remove the coin
        // since it was never added in the
        // first place.
        this.spendCredit(credit, tx, i);

        this.pending.coin--;
        this.pending.unconfirmed -= credit.coin.value;
      }

      coin = credit.coin;

      assert(coin.height !== -1);

      path = yield this.getPath(coin);
      assert(path);

      details.setInput(i, path, coin);

      // We can now safely remove the credit
      // entirely, now that we know it's also
      // been removed on-chain.
      this.pending.confirmed -= coin.value;

      yield this.removeCredit(credit, path);
    }
  }

  // Update credit heights, including undo coins.
  for (i = 0; i < tx.outputs.length; i++) {
    output = tx.outputs[i];
    path = yield this.getPath(output);

    if (!path)
      continue;

    details.setOutput(i, path);

    credit = yield this.getCredit(hash, i);
    assert(credit);

    // Credits spent in the mempool add an
    // undo coin for ease. If this credit is
    // spent in the mempool, we need to
    // update the undo coin's height.
    if (credit.spent)
      yield this.updateSpentCoin(tx, i, height);

    // Update coin height and confirmed
    // balance. Save once again.
    coin = credit.coin;
    coin.height = height;

    this.pending.confirmed += output.value;

    yield this.saveCredit(credit, path);
  }

  // Remove the RBF index if we have one.
  this.del(layout.r(hash));

  // Save the new serialized transaction as
  // the block-related properties have been
  // updated. Also reindex for height.
  this.put(layout.t(hash), wtx.toRaw());
  this.del(layout.p(hash));
  this.put(layout.h(height, hash), DUMMY);

  // Secondary indexing also needs to change.
  for (i = 0; i < details.accounts.length; i++) {
    account = details.accounts[i];
    this.del(layout.P(account, hash));
    this.put(layout.H(account, height, hash), DUMMY);
  }

  if (block) {
    yield this.addBlockMap(hash, height);
    yield this.addBlock(tx.hash(), block);
  }

  // Commit the new state. The balance has updated.
  this.put(layout.R, this.pending.commit());

  this.unlockTX(tx);

  this.emit('confirmed', tx, details);
  this.emit('balance', this.pending.toBalance(), details);

  return details;
});

/**
 * Recursively remove a transaction
 * from the database.
 * @param {Hash} hash
 * @returns {Promise}
 */

TXDB.prototype.remove = co(function* remove(hash) {
  var wtx = yield this.getTX(hash);

  if (!wtx)
    return;

  return yield this.removeRecursive(wtx);
});

/**
 * Remove a transaction from the
 * database. Disconnect inputs.
 * @private
 * @param {TXRecord} wtx
 * @returns {Promise}
 */

TXDB.prototype.erase = co(function* erase(wtx, block) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var height = block ? block.height : -1;
  var details = new Details(this, wtx, block);
  var i, path, account, credits;
  var input, output, coin, credit;

  if (!tx.isCoinbase()) {
    // We need to undo every part of the
    // state this transaction ever touched.
    // Start by getting the undo coins.
    credits = yield this.getSpentCredits(tx);

    for (i = 0; i < tx.inputs.length; i++) {
      input = tx.inputs[i];
      credit = credits[i];

      if (!credit) {
        // This input never had an undo
        // coin, but remove it from the
        // stxo set.
        this.removeInput(tx, i);
        continue;
      }

      coin = credit.coin;
      path = yield this.getPath(coin);
      assert(path);

      details.setInput(i, path, coin);

      // Recalculate the balance, remove
      // from stxo set, remove the undo
      // coin, and resave the credit.
      this.pending.coin++;
      this.pending.unconfirmed += coin.value;

      if (block)
        this.pending.confirmed += coin.value;

      this.unspendCredit(tx, i);
      yield this.saveCredit(credit, path);
    }
  }

  // We need to remove all credits
  // this transaction created.
  for (i = 0; i < tx.outputs.length; i++) {
    output = tx.outputs[i];
    path = yield this.getPath(output);

    if (!path)
      continue;

    details.setOutput(i, path);

    credit = Credit.fromTX(tx, i, height);

    this.pending.coin--;
    this.pending.unconfirmed -= output.value;

    if (block)
      this.pending.confirmed -= output.value;

    yield this.removeCredit(credit, path);
  }

  // Remove the RBF index if we have one.
  this.del(layout.r(hash));

  // Remove the transaction data
  // itself as well as unindex.
  this.del(layout.t(hash));
  this.del(layout.m(wtx.ps, hash));

  if (!block)
    this.del(layout.p(hash));
  else
    this.del(layout.h(height, hash));

  // Remove all secondary indexing.
  for (i = 0; i < details.accounts.length; i++) {
    account = details.accounts[i];

    this.del(layout.T(account, hash));
    this.del(layout.M(account, wtx.ps, hash));

    if (!block)
      this.del(layout.P(account, hash));
    else
      this.del(layout.H(account, height, hash));
  }

  // Update block records.
  if (block) {
    yield this.removeBlockMap(hash, height);
    yield this.removeBlockSlow(hash, height);
  }

  // Update the transaction counter
  // and commit new state due to
  // balance change.
  this.pending.tx--;
  this.put(layout.R, this.pending.commit());

  this.emit('remove tx', tx, details);
  this.emit('balance', this.pending.toBalance(), details);

  return details;
});

/**
 * Remove a transaction and recursively
 * remove all of its spenders.
 * @private
 * @param {TXRecord} wtx
 * @returns {Promise}
 */

TXDB.prototype.removeRecursive = co(function* removeRecursive(wtx) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var i, spent, stx, details;

  for (i = 0; i < tx.outputs.length; i++) {
    spent = yield this.getSpent(hash, i);

    if (!spent)
      continue;

    // Remove all of the spender's spenders first.
    stx = yield this.getTX(spent.hash);

    assert(stx);

    yield this.removeRecursive(stx);
  }

  this.start();

  // Remove the spender.
  details = yield this.erase(wtx, wtx.getBlock());

  assert(details);

  yield this.commit();

  return details;
});

/**
 * Unconfirm a transaction. Necessary after a reorg.
 * @param {Hash} hash
 * @returns {Promise}
 */

TXDB.prototype.unconfirm = co(function* unconfirm(hash) {
  var details;

  this.start();

  try {
    details = yield this._unconfirm(hash);
  } catch (e) {
    this.drop();
    throw e;
  }

  yield this.commit();

  return details;
});

/**
 * Unconfirm a transaction without a batch.
 * @private
 * @param {Hash} hash
 * @returns {Promise}
 */

TXDB.prototype._unconfirm = co(function* unconfirm(hash) {
  var wtx = yield this.getTX(hash);

  if (!wtx)
    return;

  return yield this.disconnect(wtx, wtx.getBlock());
});

/**
 * Unconfirm a transaction. Necessary after a reorg.
 * @param {TXRecord} wtx
 * @returns {Promise}
 */

TXDB.prototype.disconnect = co(function* disconnect(wtx, block) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var height = block.height;
  var details = new Details(this, wtx, block);
  var i, account, output, coin, credits;
  var input, path, credit;

  assert(block);

  wtx.unsetBlock();

  if (!tx.isCoinbase()) {
    // We need to reconnect the coins. Start
    // by getting all of the undo coins we know
    // about.
    credits = yield this.getSpentCredits(tx);

    for (i = 0; i < tx.inputs.length; i++) {<commit_msg>txdb: failsafe for unconfirm.<commit_after>/*!
 * txdb.js - persistent transaction pool
 * Copyright (c) 2014-2015, <NAME> (MIT License)
 * Copyright (c) 2014-2016, <NAME> (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

var util = require('../utils/util');
var LRU = require('../utils/lru');
var co = require('../utils/co');
var assert = require('assert');
var BufferReader = require('../utils/reader');
var StaticWriter = require('../utils/staticwriter');
var Amount = require('../btc/amount');
var CoinView = require('../coins/coinview');
var Coin = require('../primitives/coin');
var Outpoint = require('../primitives/outpoint');
var records = require('./records');
var layout = require('./layout').txdb;
var encoding = require('../utils/encoding');
var policy = require('../protocol/policy');
var Script = require('../script/script');
var BlockMapRecord = records.BlockMapRecord;
var OutpointMapRecord = records.OutpointMapRecord;
var TXRecord = records.TXRecord;
var DUMMY = new Buffer([0]);

/**
 * TXDB
 * @exports TXDB
 * @constructor
 * @param {Wallet} wallet
 */

function TXDB(wallet) {
  if (!(this instanceof TXDB))
    return new TXDB(wallet);

  this.wallet = wallet;
  this.walletdb = wallet.db;
  this.db = wallet.db.db;
  this.logger = wallet.db.logger;
  this.network = wallet.db.network;
  this.options = wallet.db.options;
  this.coinCache = new LRU(10000);

  this.locked = {};
  this.state = null;
  this.pending = null;
  this.events = [];
}

/**
 * Database layout.
 * @type {Object}
 */

TXDB.layout = layout;

/**
 * Open TXDB.
 * @returns {Promise}
 */

TXDB.prototype.open = co(function* open() {
  var state = yield this.getState();

  if (state) {
    this.state = state;
    this.logger.info('TXDB loaded for %s.', this.wallet.id);
  } else {
    this.state = new TXDBState(this.wallet.wid, this.wallet.id);
    this.logger.info('TXDB created for %s.', this.wallet.id);
  }

  this.logger.info('TXDB State: tx=%d coin=%s.',
    this.state.tx, this.state.coin);

  this.logger.info(
    'Balance: unconfirmed=%s confirmed=%s.',
    Amount.btc(this.state.unconfirmed),
    Amount.btc(this.state.confirmed));
});

/**
 * Start batch.
 * @private
 */

TXDB.prototype.start = function start() {
  this.pending = this.state.clone();
  this.coinCache.start();
  return this.wallet.start();
};

/**
 * Drop batch.
 * @private
 */

TXDB.prototype.drop = function drop() {
  this.pending = null;
  this.events.length = 0;
  this.coinCache.drop();
  return this.wallet.drop();
};

/**
 * Clear batch.
 * @private
 */

TXDB.prototype.clear = function clear() {
  this.pending = this.state.clone();
  this.events.length = 0;
  this.coinCache.clear();
  return this.wallet.clear();
};

/**
 * Save batch.
 * @returns {Promise}
 */

TXDB.prototype.commit = co(function* commit() {
  var i, item;

  try {
    yield this.wallet.commit();
  } catch (e) {
    this.pending = null;
    this.events.length = 0;
    this.coinCache.drop();
    throw e;
  }

  // Overwrite the entire state
  // with our new committed state.
  if (this.pending.committed) {
    this.state = this.pending;

    // Emit buffered events now that
    // we know everything is written.
    for (i = 0; i < this.events.length; i++) {
      item = this.events[i];
      this.walletdb.emit(item[0], this.wallet.id, item[1], item[2]);
      this.wallet.emit(item[0], item[1], item[2]);
    }
  }

  this.pending = null;
  this.events.length = 0;
  this.coinCache.commit();
});

/**
 * Emit transaction event.
 * @private
 * @param {String} event
 * @param {Object} data
 * @param {Details} details
 */

TXDB.prototype.emit = function emit(event, data, details) {
  this.events.push([event, data, details]);
};

/**
 * Prefix a key.
 * @param {Buffer} key
 * @returns {Buffer} Prefixed key.
 */

TXDB.prototype.prefix = function prefix(key) {
  assert(this.wallet.wid);
  return layout.prefix(this.wallet.wid, key);
};

/**
 * Put key and value to current batch.
 * @param {String} key
 * @param {Buffer} value
 */

TXDB.prototype.put = function put(key, value) {
  assert(this.wallet.current);
  this.wallet.current.put(this.prefix(key), value);
};

/**
 * Delete key from current batch.
 * @param {String} key
 */

TXDB.prototype.del = function del(key) {
  assert(this.wallet.current);
  this.wallet.current.del(this.prefix(key));
};

/**
 * Get.
 * @param {String} key
 */

TXDB.prototype.get = function get(key) {
  return this.db.get(this.prefix(key));
};

/**
 * Has.
 * @param {String} key
 */

TXDB.prototype.has = function has(key) {
  return this.db.has(this.prefix(key));
};

/**
 * Iterate.
 * @param {Object} options
 * @returns {Promise}
 */

TXDB.prototype.range = function range(options) {
  if (options.gte)
    options.gte = this.prefix(options.gte);
  if (options.lte)
    options.lte = this.prefix(options.lte);
  return this.db.range(options);
};

/**
 * Iterate.
 * @param {Object} options
 * @returns {Promise}
 */

TXDB.prototype.keys = function keys(options) {
  if (options.gte)
    options.gte = this.prefix(options.gte);
  if (options.lte)
    options.lte = this.prefix(options.lte);
  return this.db.keys(options);
};

/**
 * Iterate.
 * @param {Object} options
 * @returns {Promise}
 */

TXDB.prototype.values = function values(options) {
  if (options.gte)
    options.gte = this.prefix(options.gte);
  if (options.lte)
    options.lte = this.prefix(options.lte);
  return this.db.values(options);
};

/**
 * Get wallet path for output.
 * @param {Output} output
 * @returns {Promise} - Returns {@link Path}.
 */

TXDB.prototype.getPath = function getPath(output) {
  if (!output)
    return Promise.resolve();
  return this.wallet.getPath(output.getAddress());
};

/**
 * Test whether path exists for output.
 * @param {Output} output
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.hasPath = function hasPath(output) {
  if (!output)
    return Promise.resolve();
  return this.wallet.hasPath(output.getAddress());
};

/**
 * Save credit.
 * @param {Credit} credit
 * @param {Path} path
 */

TXDB.prototype.saveCredit = co(function* saveCredit(credit, path) {
  var coin = credit.coin;
  var key = coin.toKey();
  var raw = credit.toRaw();

  yield this.addOutpointMap(coin.hash, coin.index);

  this.put(layout.c(coin.hash, coin.index), raw);
  this.put(layout.C(path.account, coin.hash, coin.index), DUMMY);

  this.coinCache.push(key, raw);
});

/**
 * Remove credit.
 * @param {Credit} credit
 * @param {Path} path
 */

TXDB.prototype.removeCredit = co(function* removeCredit(credit, path) {
  var coin = credit.coin;
  var key = coin.toKey();

  yield this.removeOutpointMap(coin.hash, coin.index);

  this.del(layout.c(coin.hash, coin.index));
  this.del(layout.C(path.account, coin.hash, coin.index));

  this.coinCache.unpush(key);
});

/**
 * Spend credit.
 * @param {Credit} credit
 * @param {TX} tx
 * @param {Number} index
 */

TXDB.prototype.spendCredit = function spendCredit(credit, tx, index) {
  var prevout = tx.inputs[index].prevout;
  var spender = Outpoint.fromTX(tx, index);
  this.put(layout.s(prevout.hash, prevout.index), spender.toRaw());
  this.put(layout.d(spender.hash, spender.index), credit.coin.toRaw());
};

/**
 * Unspend credit.
 * @param {TX} tx
 * @param {Number} index
 */

TXDB.prototype.unspendCredit = function unspendCredit(tx, index) {
  var prevout = tx.inputs[index].prevout;
  var spender = Outpoint.fromTX(tx, index);
  this.del(layout.s(prevout.hash, prevout.index));
  this.del(layout.d(spender.hash, spender.index));
};

/**
 * Write input record.
 * @param {TX} tx
 * @param {Number} index
 */

TXDB.prototype.writeInput = function writeInput(tx, index) {
  var prevout = tx.inputs[index].prevout;
  var spender = Outpoint.fromTX(tx, index);
  this.put(layout.s(prevout.hash, prevout.index), spender.toRaw());
};

/**
 * Remove input record.
 * @param {TX} tx
 * @param {Number} index
 */

TXDB.prototype.removeInput = function removeInput(tx, index) {
  var prevout = tx.inputs[index].prevout;
  this.del(layout.s(prevout.hash, prevout.index));
};

/**
 * Resolve orphan input.
 * @param {TX} tx
 * @param {Number} index
 * @param {Number} height
 * @param {Path} path
 * @returns {Boolean}
 */

TXDB.prototype.resolveInput = co(function* resolveInput(tx, index, height, path) {
  var hash = tx.hash('hex');
  var spent = yield this.getSpent(hash, index);
  var stx, credit;

  if (!spent)
    return false;

  // If we have an undo coin, we
  // already knew about this input.
  if (yield this.hasSpentCoin(spent))
    return false;

  // Get the spending transaction so
  // we can properly add the undo coin.
  stx = yield this.getTX(spent.hash);
  assert(stx);

  // Crete the credit and add the undo coin.
  credit = Credit.fromTX(tx, index, height);

  this.spendCredit(credit, stx.tx, spent.index);

  // If the spender is unconfirmed, save
  // the credit as well, and mark it as
  // unspent in the mempool. This is the
  // same behavior `insert` would have
  // done for inputs. We're just doing
  // it retroactively.
  if (stx.height === -1) {
    credit.spent = true;
    yield this.saveCredit(credit, path);
    if (height !== -1)
      this.pending.confirmed += credit.coin.value;
  }

  return true;
});

/**
 * Test an entire transaction to see
 * if any of its outpoints are a double-spend.
 * @param {TX} tx
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.isDoubleSpend = co(function* isDoubleSpend(tx) {
  var i, input, prevout, spent;

  for (i = 0; i < tx.inputs.length; i++) {
    input = tx.inputs[i];
    prevout = input.prevout;
    spent = yield this.isSpent(prevout.hash, prevout.index);
    if (spent)
      return true;
  }

  return false;
});

/**
 * Test an entire transaction to see
 * if any of its outpoints are replace by fee.
 * @param {TX} tx
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.isRBF = co(function* isRBF(tx) {
  var i, input, prevout;

  if (tx.isRBF())
    return true;

  for (i = 0; i < tx.inputs.length; i++) {
    input = tx.inputs[i];
    prevout = input.prevout;
    if (yield this.has(layout.r(prevout.hash)))
      return true;
  }

  return false;
});

/**
 * Test a whether a coin has been spent.
 * @param {Hash} hash
 * @param {Number} index
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.getSpent = co(function* getSpent(hash, index) {
  var data = yield this.get(layout.s(hash, index));

  if (!data)
    return;

  return Outpoint.fromRaw(data);
});

/**
 * Test a whether a coin has been spent.
 * @param {Hash} hash
 * @param {Number} index
 * @returns {Promise} - Returns Boolean.
 */

TXDB.prototype.isSpent = function isSpent(hash, index) {
  return this.has(layout.s(hash, index));
};

/**
 * Append to the global unspent record.
 * @param {Hash} hash
 * @param {Number} index
 * @returns {Promise}
 */

TXDB.prototype.addOutpointMap = co(function* addOutpointMap(hash, i) {
  var map = yield this.walletdb.getOutpointMap(hash, i);

  if (!map)
    map = new OutpointMapRecord(hash, i);

  if (!map.add(this.wallet.wid))
    return;

  this.walletdb.writeOutpointMap(this.wallet, hash, i, map);
});

/**
 * Remove from the global unspent record.
 * @param {Hash} hash
 * @param {Number} index
 * @returns {Promise}
 */

TXDB.prototype.removeOutpointMap = co(function* removeOutpointMap(hash, i) {
  var map = yield this.walletdb.getOutpointMap(hash, i);

  if (!map)
    return;

  if (!map.remove(this.wallet.wid))
    return;

  if (map.wids.length === 0) {
    this.walletdb.unwriteOutpointMap(this.wallet, hash, i);
    return;
  }

  this.walletdb.writeOutpointMap(this.wallet, hash, i, map);
});

/**
 * Append to the global block record.
 * @param {Hash} hash
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.addBlockMap = co(function* addBlockMap(hash, height) {
  var block = yield this.walletdb.getBlockMap(height);

  if (!block)
    block = new BlockMapRecord(height);

  if (!block.add(hash, this.wallet.wid))
    return;

  this.walletdb.writeBlockMap(this.wallet, height, block);
});

/**
 * Remove from the global block record.
 * @param {Hash} hash
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.removeBlockMap = co(function* removeBlockMap(hash, height) {
  var block = yield this.walletdb.getBlockMap(height);

  if (!block)
    return;

  if (!block.remove(hash, this.wallet.wid))
    return;

  if (block.txs.length === 0) {
    this.walletdb.unwriteBlockMap(this.wallet, height);
    return;
  }

  this.walletdb.writeBlockMap(this.wallet, height, block);
});

/**
 * List block records.
 * @returns {Promise}
 */

TXDB.prototype.getBlocks = function getBlocks() {
  return this.keys({
    gte: layout.b(0),
    lte: layout.b(0xffffffff),
    parse: function(key) {
      return layout.bb(key);
    }
  });
};

/**
 * Get block record.
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.getBlock = co(function* getBlock(height) {
  var data = yield this.get(layout.b(height));

  if (!data)
    return;

  return BlockRecord.fromRaw(data);
});

/**
 * Append to the global block record.
 * @param {Hash} hash
 * @param {BlockMeta} meta
 * @returns {Promise}
 */

TXDB.prototype.addBlock = co(function* addBlock(hash, meta) {
  var key = layout.b(meta.height);
  var data = yield this.get(key);
  var block, size;

  if (!data) {
    block = BlockRecord.fromMeta(meta);
    data = block.toRaw();
  }

  block = new Buffer(data.length + 32);
  data.copy(block, 0);

  size = block.readUInt32LE(40, true);
  block.writeUInt32LE(size + 1, 40, true);
  hash.copy(block, data.length);

  this.put(key, block);
});

/**
 * Remove from the global block record.
 * @param {Hash} hash
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.removeBlock = co(function* removeBlock(hash, height) {
  var key = layout.b(height);
  var data = yield this.get(key);
  var block, size;

  if (!data)
    return;

  size = data.readUInt32LE(40, true);

  assert(size > 0);
  assert(util.equal(data.slice(-32), hash));

  if (size === 1) {
    this.del(key);
    return;
  }

  block = data.slice(0, -32);
  block.writeUInt32LE(size - 1, 40, true);

  this.put(key, block);
});

/**
 * Append to the global block record.
 * @param {Hash} hash
 * @param {BlockMeta} meta
 * @returns {Promise}
 */

TXDB.prototype.addBlockSlow = co(function* addBlockSlow(hash, meta) {
  var block = yield this.getBlock(meta.height);

  if (!block)
    block = BlockRecord.fromMeta(meta);

  if (!block.add(hash))
    return;

  this.put(layout.b(meta.height), block.toRaw());
});

/**
 * Remove from the global block record.
 * @param {Hash} hash
 * @param {Number} height
 * @returns {Promise}
 */

TXDB.prototype.removeBlockSlow = co(function* removeBlockSlow(hash, height) {
  var block = yield this.getBlock(height);

  if (!block)
    return;

  if (!block.remove(hash))
    return;

  if (block.hashes.length === 0) {
    this.del(layout.b(height));
    return;
  }

  this.put(layout.b(height), block.toRaw());
});

/**
 * Add transaction, potentially runs
 * `confirm()` and `removeConflicts()`.
 * @param {TX} tx
 * @param {BlockMeta} block
 * @returns {Promise}
 */

TXDB.prototype.add = co(function* add(tx, block) {
  var result;

  this.start();

  try {
    result = yield this._add(tx, block);
  } catch (e) {
    this.drop();
    throw e;
  }

  yield this.commit();

  return result;
});

/**
 * Add transaction without a batch.
 * @private
 * @param {TX} tx
 * @returns {Promise}
 */

TXDB.prototype._add = co(function* add(tx, block) {
  var hash = tx.hash('hex');
  var existing = yield this.getTX(hash);
  var wtx;

  assert(!tx.mutable, 'Cannot add mutable TX to wallet.');

  if (existing) {
    // Existing tx is already confirmed. Ignore.
    if (existing.height !== -1)
      return;

    // The incoming tx won't confirm the
    // existing one anyway. Ignore.
    if (!block)
      return;

    // Confirm transaction.
    return yield this._confirm(existing, block);
  }

  wtx = TXRecord.fromTX(tx, block);

  if (!block) {
    // We ignore any unconfirmed txs
    // that are replace-by-fee.
    if (yield this.isRBF(tx)) {
      // We need to index every spender
      // hash to detect "passive"
      // replace-by-fee.
      this.put(layout.r(hash), DUMMY);
      return;
    }

    // Potentially remove double-spenders.
    // Only remove if they're not confirmed.
    if (!(yield this.removeConflicts(tx, true)))
      return;
  } else {
    // Potentially remove double-spenders.
    yield this.removeConflicts(tx, false);

    // Delete the replace-by-fee record.
    this.del(layout.r(hash));
  }

  // Finally we can do a regular insertion.
  return yield this.insert(wtx, block);
});

/**
 * Insert transaction.
 * @private
 * @param {TXRecord} wtx
 * @param {BlockMeta} block
 * @returns {Promise}
 */

TXDB.prototype.insert = co(function* insert(wtx, block) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var height = block ? block.height : -1;
  var details = new Details(this, wtx, block);
  var updated = false;
  var i, input, output, coin;
  var prevout, credit, path, account;

  if (!tx.isCoinbase()) {
    // We need to potentially spend some coins here.
    for (i = 0; i < tx.inputs.length; i++) {
      input = tx.inputs[i];
      prevout = input.prevout;
      credit = yield this.getCredit(prevout.hash, prevout.index);

      if (!credit) {
        // Maintain an stxo list for every
        // spent input (even ones we don't
        // recognize). This is used for
        // detecting double-spends (as best
        // we can), as well as resolving
        // inputs we didn't know were ours
        // at the time. This built-in error
        // correction is not technically
        // necessary assuming no messages
        // are ever missed from the mempool,
        // but shit happens.
        this.writeInput(tx, i);
        continue;
      }

      coin = credit.coin;

      // Do some verification.
      if (!block) {
        if (!(yield this.verifyInput(tx, i, coin))) {
          this.clear();
          return;
        }
      }

      path = yield this.getPath(coin);
      assert(path);

      // Build the tx details object
      // as we go, for speed.
      details.setInput(i, path, coin);

      // Write an undo coin for the credit
      // and add it to the stxo set.
      this.spendCredit(credit, tx, i);

      // Unconfirmed balance should always
      // be updated as it reflects the on-chain
      // balance _and_ mempool balance assuming
      // everything in the mempool were to confirm.
      this.pending.coin--;
      this.pending.unconfirmed -= coin.value;

      if (!block) {
        // If the tx is not mined, we do not
        // disconnect the coin, we simply mark
        // a `spent` flag on the credit. This
        // effectively prevents the mempool
        // from altering our utxo state
        // permanently. It also makes it
        // possible to compare the on-chain
        // state vs. the mempool state.
        credit.spent = true;
        yield this.saveCredit(credit, path);
      } else {
        // If the tx is mined, we can safely
        // remove the coin being spent. This
        // coin will be indexed as an undo
        // coin so it can be reconnected
        // later during a reorg.
        this.pending.confirmed -= coin.value;
        yield this.removeCredit(credit, path);
      }

      updated = true;
    }
  }

  // Potentially add coins to the utxo set.
  for (i = 0; i < tx.outputs.length; i++) {
    output = tx.outputs[i];
    path = yield this.getPath(output);

    if (!path)
      continue;

    details.setOutput(i, path);

    // Attempt to resolve an input we
    // did not know was ours at the time.
    if (yield this.resolveInput(tx, i, height, path)) {
      updated = true;
      continue;
    }

    credit = Credit.fromTX(tx, i, height);

    this.pending.coin++;
    this.pending.unconfirmed += output.value;

    if (block)
      this.pending.confirmed += output.value;

    yield this.saveCredit(credit, path);

    updated = true;
  }

  // If this didn't update any coins,
  // it's not our transaction.
  if (!updated) {
    // Clear the spent list inserts.
    this.clear();
    return;
  }

  // Save and index the transaction record.
  this.put(layout.t(hash), wtx.toRaw());
  this.put(layout.m(wtx.ps, hash), DUMMY);

  if (!block)
    this.put(layout.p(hash), DUMMY);
  else
    this.put(layout.h(height, hash), DUMMY);

  // Do some secondary indexing for account-based
  // queries. This saves us a lot of time for
  // queries later.
  for (i = 0; i < details.accounts.length; i++) {
    account = details.accounts[i];

    this.put(layout.T(account, hash), DUMMY);
    this.put(layout.M(account, wtx.ps, hash), DUMMY);

    if (!block)
      this.put(layout.P(account, hash), DUMMY);
    else
      this.put(layout.H(account, height, hash), DUMMY);
  }

  // Update block records.
  if (block) {
    yield this.addBlockMap(hash, height);
    yield this.addBlock(tx.hash(), block);
  }

  // Update the transaction counter and
  // commit the new state. This state will
  // only overwrite the best state once
  // the batch has actually been written
  // to disk.
  this.pending.tx++;
  this.put(layout.R, this.pending.commit());

  // This transaction may unlock some
  // coins now that we've seen it.
  this.unlockTX(tx);

  // Emit events for potential local and
  // websocket listeners. Note that these
  // will only be emitted if the batch is
  // successfully written to disk.
  this.emit('tx', tx, details);
  this.emit('balance', this.pending.toBalance(), details);

  return details;
});

/**
 * Attempt to confirm a transaction.
 * @private
 * @param {TX} tx
 * @param {BlockMeta} block
 * @returns {Promise}
 */

TXDB.prototype.confirm = co(function* confirm(hash, block) {
  var wtx = yield this.getTX(hash);
  var details;

  if (!wtx)
    return;

  if (wtx.height !== -1)
    throw new Error('TX is already confirmed.');

  assert(block);

  this.start();

  try {
    details = yield this._confirm(wtx, block);
  } catch (e) {
    this.drop();
    throw e;
  }

  yield this.commit();

  return details;
});

/**
 * Attempt to confirm a transaction.
 * @private
 * @param {TXRecord} wtx
 * @param {BlockMeta} block
 * @returns {Promise}
 */

TXDB.prototype._confirm = co(function* confirm(wtx, block) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var height = block.height;
  var details = new Details(this, wtx, block);
  var i, account, output, coin, input, prevout;
  var path, credit, credits;

  wtx.setBlock(block);

  if (!tx.isCoinbase()) {
    credits = yield this.getSpentCredits(tx);

    // Potentially spend coins. Now that the tx
    // is mined, we can actually _remove_ coins
    // from the utxo state.
    for (i = 0; i < tx.inputs.length; i++) {
      input = tx.inputs[i];
      prevout = input.prevout;
      credit = credits[i];

      // There may be new credits available
      // that we haven't seen yet.
      if (!credit) {
        credit = yield this.getCredit(prevout.hash, prevout.index);

        if (!credit)
          continue;

        // Add a spend record and undo coin
        // for the coin we now know is ours.
        // We don't need to remove the coin
        // since it was never added in the
        // first place.
        this.spendCredit(credit, tx, i);

        this.pending.coin--;
        this.pending.unconfirmed -= credit.coin.value;
      }

      coin = credit.coin;

      assert(coin.height !== -1);

      path = yield this.getPath(coin);
      assert(path);

      details.setInput(i, path, coin);

      // We can now safely remove the credit
      // entirely, now that we know it's also
      // been removed on-chain.
      this.pending.confirmed -= coin.value;

      yield this.removeCredit(credit, path);
    }
  }

  // Update credit heights, including undo coins.
  for (i = 0; i < tx.outputs.length; i++) {
    output = tx.outputs[i];
    path = yield this.getPath(output);

    if (!path)
      continue;

    details.setOutput(i, path);

    credit = yield this.getCredit(hash, i);
    assert(credit);

    // Credits spent in the mempool add an
    // undo coin for ease. If this credit is
    // spent in the mempool, we need to
    // update the undo coin's height.
    if (credit.spent)
      yield this.updateSpentCoin(tx, i, height);

    // Update coin height and confirmed
    // balance. Save once again.
    coin = credit.coin;
    coin.height = height;

    this.pending.confirmed += output.value;

    yield this.saveCredit(credit, path);
  }

  // Remove the RBF index if we have one.
  this.del(layout.r(hash));

  // Save the new serialized transaction as
  // the block-related properties have been
  // updated. Also reindex for height.
  this.put(layout.t(hash), wtx.toRaw());
  this.del(layout.p(hash));
  this.put(layout.h(height, hash), DUMMY);

  // Secondary indexing also needs to change.
  for (i = 0; i < details.accounts.length; i++) {
    account = details.accounts[i];
    this.del(layout.P(account, hash));
    this.put(layout.H(account, height, hash), DUMMY);
  }

  if (block) {
    yield this.addBlockMap(hash, height);
    yield this.addBlock(tx.hash(), block);
  }

  // Commit the new state. The balance has updated.
  this.put(layout.R, this.pending.commit());

  this.unlockTX(tx);

  this.emit('confirmed', tx, details);
  this.emit('balance', this.pending.toBalance(), details);

  return details;
});

/**
 * Recursively remove a transaction
 * from the database.
 * @param {Hash} hash
 * @returns {Promise}
 */

TXDB.prototype.remove = co(function* remove(hash) {
  var wtx = yield this.getTX(hash);

  if (!wtx)
    return;

  return yield this.removeRecursive(wtx);
});

/**
 * Remove a transaction from the
 * database. Disconnect inputs.
 * @private
 * @param {TXRecord} wtx
 * @returns {Promise}
 */

TXDB.prototype.erase = co(function* erase(wtx, block) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var height = block ? block.height : -1;
  var details = new Details(this, wtx, block);
  var i, path, account, credits;
  var input, output, coin, credit;

  if (!tx.isCoinbase()) {
    // We need to undo every part of the
    // state this transaction ever touched.
    // Start by getting the undo coins.
    credits = yield this.getSpentCredits(tx);

    for (i = 0; i < tx.inputs.length; i++) {
      input = tx.inputs[i];
      credit = credits[i];

      if (!credit) {
        // This input never had an undo
        // coin, but remove it from the
        // stxo set.
        this.removeInput(tx, i);
        continue;
      }

      coin = credit.coin;
      path = yield this.getPath(coin);
      assert(path);

      details.setInput(i, path, coin);

      // Recalculate the balance, remove
      // from stxo set, remove the undo
      // coin, and resave the credit.
      this.pending.coin++;
      this.pending.unconfirmed += coin.value;

      if (block)
        this.pending.confirmed += coin.value;

      this.unspendCredit(tx, i);
      yield this.saveCredit(credit, path);
    }
  }

  // We need to remove all credits
  // this transaction created.
  for (i = 0; i < tx.outputs.length; i++) {
    output = tx.outputs[i];
    path = yield this.getPath(output);

    if (!path)
      continue;

    details.setOutput(i, path);

    credit = Credit.fromTX(tx, i, height);

    this.pending.coin--;
    this.pending.unconfirmed -= output.value;

    if (block)
      this.pending.confirmed -= output.value;

    yield this.removeCredit(credit, path);
  }

  // Remove the RBF index if we have one.
  this.del(layout.r(hash));

  // Remove the transaction data
  // itself as well as unindex.
  this.del(layout.t(hash));
  this.del(layout.m(wtx.ps, hash));

  if (!block)
    this.del(layout.p(hash));
  else
    this.del(layout.h(height, hash));

  // Remove all secondary indexing.
  for (i = 0; i < details.accounts.length; i++) {
    account = details.accounts[i];

    this.del(layout.T(account, hash));
    this.del(layout.M(account, wtx.ps, hash));

    if (!block)
      this.del(layout.P(account, hash));
    else
      this.del(layout.H(account, height, hash));
  }

  // Update block records.
  if (block) {
    yield this.removeBlockMap(hash, height);
    yield this.removeBlockSlow(hash, height);
  }

  // Update the transaction counter
  // and commit new state due to
  // balance change.
  this.pending.tx--;
  this.put(layout.R, this.pending.commit());

  this.emit('remove tx', tx, details);
  this.emit('balance', this.pending.toBalance(), details);

  return details;
});

/**
 * Remove a transaction and recursively
 * remove all of its spenders.
 * @private
 * @param {TXRecord} wtx
 * @returns {Promise}
 */

TXDB.prototype.removeRecursive = co(function* removeRecursive(wtx) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var i, spent, stx, details;

  for (i = 0; i < tx.outputs.length; i++) {
    spent = yield this.getSpent(hash, i);

    if (!spent)
      continue;

    // Remove all of the spender's spenders first.
    stx = yield this.getTX(spent.hash);

    assert(stx);

    yield this.removeRecursive(stx);
  }

  this.start();

  // Remove the spender.
  details = yield this.erase(wtx, wtx.getBlock());

  assert(details);

  yield this.commit();

  return details;
});

/**
 * Unconfirm a transaction. Necessary after a reorg.
 * @param {Hash} hash
 * @returns {Promise}
 */

TXDB.prototype.unconfirm = co(function* unconfirm(hash) {
  var details;

  this.start();

  try {
    details = yield this._unconfirm(hash);
  } catch (e) {
    this.drop();
    throw e;
  }

  yield this.commit();

  return details;
});

/**
 * Unconfirm a transaction without a batch.
 * @private
 * @param {Hash} hash
 * @returns {Promise}
 */

TXDB.prototype._unconfirm = co(function* unconfirm(hash) {
  var wtx = yield this.getTX(hash);

  if (!wtx)
    return;

  if (wtx.height === -1)
    return;

  return yield this.disconnect(wtx, wtx.getBlock());
});

/**
 * Unconfirm a transaction. Necessary after a reorg.
 * @param {TXRecord} wtx
 * @returns {Promise}
 */

TXDB.prototype.disconnect = co(function* disconnect(wtx, block) {
  var tx = wtx.tx;
  var hash = wtx.hash;
  var height = block.height;
  var details = new Details(this, wtx, block);
  var i, account, output, coin, credits;
  var input, path, credit;

  assert(block);

  wtx.unsetBlock();

  if (!tx.isCoinbase()) {
    // We need to reconnect the coins. Start
    // by getting all of the undo coins we know
    // about.
    credits = yield this.getSpentCredits(tx);

    for (i = 0; i < tx.inputs.length; i++) {
{'fraction_non_alphanumeric': 0.15052955020920503, 'fraction_numerical': 0.002827536610878661, 'mean_word_length': 2.8262147457945095, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 2, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4581492', 'n_tokens_mistral': 20966, 'n_tokens_neox': 20329, 'n_words': 5609}