<commit_before>var _           = require('lodash');
var async       = require('async');
var ripple      = require('ripple-lib');
var validator   = require('./../lib/schema-validator');
var remote      = require('./../lib/remote.js');
var dbinterface = require('./../lib/db-interface.js');
var respond     = require('./../lib/response-handler.js');
var errors      = require('./../lib/errors.js');

module.exports = {
  DEFAULT_RESULTS_PER_PAGE: 10,
  NUM_TRANSACTION_TYPES: 5,
  DEFAULT_LEDGER_BUFFER: 6,
  submit: submitTransaction,
  get: getTransaction,
  getTransactionHelper: getTransactionHelper,
  getAccountTransactions: getAccountTransactions
};

/**
 *  Submit a normal ripple-lib transaction, blocking duplicates
 *  for payments and orders.
 *
 *  @param {Remote} remote
 *  @param {/lib/db-interface} dbinterface
 *  @param {Transaction} data.transaction
 *  @param {String} data.secret
 *  @param {String} data.client_resource_id
 *  @param {Express.js Response} res Used to send error messages directly to the client
 *  @param {Function} callback
 *
 *  @callback
 *  @param {Error} error Submission Error
 *  @param {submission response} response The response received from the 'proposed' event
 */
function submitTransaction(data, response, callback) {

  function prepareTransaction(async_callback) {
    data.transaction.secret(data.secret);
    data.transaction.clientID(data.client_resource_id);
    async_callback(null, data.transaction);
  };

  function blockDuplicates(transaction, async_callback) {    
    var type = transaction.tx_json.TransactionType;
    if (type !== 'Payment' && type !== 'OfferCreate' && type !== 'OfferCancel') {
      return async_callback(null, transaction);
    }
    dbinterface.getTransaction({
      source_account: transaction.tx_json.Account,
      client_resource_id: data.client_resource_id,
      type: transaction.tx_json.TransactionType.toLowerCase()
    }, function(error, db_record) {
        if (error) {
          return async_callback(error);
        }
        if (db_record && db_record.state !== 'failed') {
          async_callback(new errors.ApiError('Duplicate Transaction. ' +
            'A record already exists in the database for a transaction of this type ' +
            'with the same client_resource_id. If this was not an accidental resubmission ' +
            'please submit the transaction again with a unique client_resource_id')
          );
        } else {
          async_callback(null, transaction);
        }
    });
  };

  function submitTransaction(transaction, async_callback) {
    transaction.remote = remote;

    var ledgerIndex = remote._ledger_current_index;

    if (!isNaN(ledgerIndex)) {
      transaction.lastLedger(Number(ledgerIndex) + module.exports.DEFAULT_LEDGER_BUFFER);
    }

    transaction.once('error', async_callback);

    transaction.once('proposed', function() {
      transaction.removeListener('error', async_callback);
      async_callback(null, transaction._clientID);
    });

    transaction.submit();
  };

  var steps = [
    prepareTransaction,
    blockDuplicates,
    submitTransaction
  ];

  async.waterfall(steps, callback);
};

/**
 *  Wrapper around getTransactionHelper function that is
 *  meant to be used directly as a client-facing function.
 *  Unlike getTransactionHelper, it will call next with any errors
 *  and send a JSON response to the client on success.
 *
 *  See getTransactionHelper for parameter details
 */
function getTransaction(request, response, next) {
  getTransactionHelper(request, response, function(error, transaction) {
    if (error) {
      next(error);
    } else {
      respond.success(response, { transaction: transaction });
    }
  });
};

/**
 *  Retrieve a transaction from the Remote and local database
 *  based on the account and either hash or client_resource_id.
 *
 *  Note that if any errors are encountered while executing this function
 *  they will be sent back to the client through the res. If the query is
 *  successful it will be passed to the callback function which can either
 *  send the transaction directly back to the client (e.g. in the case of
 *  getTransaction) or can process the transaction more (e.g. in the case
 *  of the Notification or Payment related functions).
 *
 *  @param {Remote} remote
 *  @param {/lib/db-interface} dbinterface
 *  @param {RippleAddress} req.params.account
 *  @param {Hex-encoded String|ASCII printable character String} req.params.identifier
 *  @param {Express.js Response} res
 *  @param {Function} callback
 *
 *  @callback
 *  @param {Error} error
 *  @param {Transaction} transaction
 */
function getTransactionHelper(request, response, callback) {
  var options = {
    account: request.params.account,
    identifier: request.params.identifier
  };

  var steps = [
    validateOptions,
    queryTransaction,
    checkIfRelatedToAccount,
    attachResourceID,
    attachDate
  ];

  async.waterfall(steps, callback);

  function validateOptions(async_callback) {
    if (options.account && !validator.isValid(options.account, 'RippleAddress')) {
      return callback(new errors.InvalidRequestError('Invalid parameter: account. Must be a valid Ripple Address'));
    }

    if (!options.identifier) {
      return callback(new errors.InvalidRequestError('Missing parameter: identifier'));
    }

    if (validator.isValid(options.identifier, 'Hash256')) {
      options.hash = options.identifier;
      async_callback();
    } else if (validator.isValid(options.identifier, 'ResourceId')) {
      options.client_resource_id = options.identifier;
      async_callback();
    } else {
      return callback(new errors.InvalidRequestError('Parameter not a valid transaction hash or client_resource_id: identifier'));
    }
  };

  function queryTransaction(async_callback) {
    dbinterface.getTransaction(options, function(error, entry) {
      if (error) {
        return async_callback(error);
      }

      if (!entry && !options.hash) {
        // Need transaction hash
        return async_callback(new errors.InvalidRequestError('Transaction not found. Missing hash'));
      }

      if (options.hash && entry) {
        // Verify that transaction hashes match
        if (options.hash !== entry.transaction.hash) {
          return async_callback(new errors.InvalidRequestError('Transaction not found. Hashes do not match'));
        }
      }

      var transactionHash = options.hash || entry.transaction.hash;

      remote.requestTx(transactionHash, function(error, transaction) {
        if (error) {
          return async_callback(error);
        }

        // we found a transaction
        if (entry && transaction) {
          transaction.client_resource_id = entry.client_resource_id;
        }

        return async_callback(null, transaction);
      });
    });
  };

  function checkIfRelatedToAccount(transaction, async_callback) {
    if (options.account) {
      var transaction_string = JSON.stringify(transaction);
      var account_regex = new RegExp(options.account);
      if (!account_regex.test(transaction_string)) {
        return async_callback(new errors.InvalidRequestError('Transaction specified did not affect the given account'));
      }
    }

    async_callback(null, transaction);
  };

  function attachResourceID(transaction, async_callback) {
    if (transaction && options.client_resource_id) {
      transaction.client_resource_id = options.client_resource_id;<commit_msg>Fix transaction hash lookup for validated db entries<commit_after>var _           = require('lodash');
var async       = require('async');
var ripple      = require('ripple-lib');
var validator   = require('./../lib/schema-validator');
var remote      = require('./../lib/remote.js');
var dbinterface = require('./../lib/db-interface.js');
var respond     = require('./../lib/response-handler.js');
var errors      = require('./../lib/errors.js');

module.exports = {
  DEFAULT_RESULTS_PER_PAGE: 10,
  NUM_TRANSACTION_TYPES: 5,
  DEFAULT_LEDGER_BUFFER: 6,
  submit: submitTransaction,
  get: getTransaction,
  getTransactionHelper: getTransactionHelper,
  getAccountTransactions: getAccountTransactions
};

/**
 *  Submit a normal ripple-lib transaction, blocking duplicates
 *  for payments and orders.
 *
 *  @param {Remote} remote
 *  @param {/lib/db-interface} dbinterface
 *  @param {Transaction} data.transaction
 *  @param {String} data.secret
 *  @param {String} data.client_resource_id
 *  @param {Express.js Response} res Used to send error messages directly to the client
 *  @param {Function} callback
 *
 *  @callback
 *  @param {Error} error Submission Error
 *  @param {submission response} response The response received from the 'proposed' event
 */
function submitTransaction(data, response, callback) {

  function prepareTransaction(async_callback) {
    data.transaction.secret(data.secret);
    data.transaction.clientID(data.client_resource_id);
    async_callback(null, data.transaction);
  };

  function blockDuplicates(transaction, async_callback) {    
    var type = transaction.tx_json.TransactionType;
    if (type !== 'Payment' && type !== 'OfferCreate' && type !== 'OfferCancel') {
      return async_callback(null, transaction);
    }
    dbinterface.getTransaction({
      source_account: transaction.tx_json.Account,
      client_resource_id: data.client_resource_id,
      type: transaction.tx_json.TransactionType.toLowerCase()
    }, function(error, db_record) {
        if (error) {
          return async_callback(error);
        }
        if (db_record && db_record.state !== 'failed') {
          async_callback(new errors.ApiError('Duplicate Transaction. ' +
            'A record already exists in the database for a transaction of this type ' +
            'with the same client_resource_id. If this was not an accidental resubmission ' +
            'please submit the transaction again with a unique client_resource_id')
          );
        } else {
          async_callback(null, transaction);
        }
    });
  };

  function submitTransaction(transaction, async_callback) {
    transaction.remote = remote;

    var ledgerIndex = remote._ledger_current_index;

    if (!isNaN(ledgerIndex)) {
      transaction.lastLedger(Number(ledgerIndex) + module.exports.DEFAULT_LEDGER_BUFFER);
    }

    transaction.once('error', async_callback);

    transaction.once('proposed', function() {
      transaction.removeListener('error', async_callback);
      async_callback(null, transaction._clientID);
    });

    transaction.submit();
  };

  var steps = [
    prepareTransaction,
    blockDuplicates,
    submitTransaction
  ];

  async.waterfall(steps, callback);
};

/**
 *  Wrapper around getTransactionHelper function that is
 *  meant to be used directly as a client-facing function.
 *  Unlike getTransactionHelper, it will call next with any errors
 *  and send a JSON response to the client on success.
 *
 *  See getTransactionHelper for parameter details
 */
function getTransaction(request, response, next) {
  getTransactionHelper(request, response, function(error, transaction) {
    if (error) {
      next(error);
    } else {
      respond.success(response, { transaction: transaction });
    }
  });
};

/**
 *  Retrieve a transaction from the Remote and local database
 *  based on the account and either hash or client_resource_id.
 *
 *  Note that if any errors are encountered while executing this function
 *  they will be sent back to the client through the res. If the query is
 *  successful it will be passed to the callback function which can either
 *  send the transaction directly back to the client (e.g. in the case of
 *  getTransaction) or can process the transaction more (e.g. in the case
 *  of the Notification or Payment related functions).
 *
 *  @param {Remote} remote
 *  @param {/lib/db-interface} dbinterface
 *  @param {RippleAddress} req.params.account
 *  @param {Hex-encoded String|ASCII printable character String} req.params.identifier
 *  @param {Express.js Response} res
 *  @param {Function} callback
 *
 *  @callback
 *  @param {Error} error
 *  @param {Transaction} transaction
 */
function getTransactionHelper(request, response, callback) {
  var options = {
    account: request.params.account,
    identifier: request.params.identifier
  };

  var steps = [
    validateOptions,
    queryTransaction,
    checkIfRelatedToAccount,
    attachResourceID,
    attachDate
  ];

  async.waterfall(steps, callback);

  function validateOptions(async_callback) {
    if (options.account && !validator.isValid(options.account, 'RippleAddress')) {
      return callback(new errors.InvalidRequestError('Invalid parameter: account. Must be a valid Ripple Address'));
    }

    if (!options.identifier) {
      return callback(new errors.InvalidRequestError('Missing parameter: identifier'));
    }

    if (validator.isValid(options.identifier, 'Hash256')) {
      options.hash = options.identifier;
      async_callback();
    } else if (validator.isValid(options.identifier, 'ResourceId')) {
      options.client_resource_id = options.identifier;
      async_callback();
    } else {
      return callback(new errors.InvalidRequestError('Parameter not a valid transaction hash or client_resource_id: identifier'));
    }
  };

  function queryTransaction(async_callback) {
    dbinterface.getTransaction(options, function(error, entry) {
      if (error) {
        return async_callback(error);
      }

      if (!entry && !options.hash) {
        // Need transaction hash
        return async_callback(new errors.InvalidRequestError('Transaction not found. Missing hash'));
      }

      var transactionHash = entry.hash || (entry.transaction || {}).hash;

      if (options.hash) {
        // Verify that transaction hashes match
        if (options.hash !== transactionHash) {
          return async_callback(new errors.InvalidRequestError('Transaction not found. Hashes do not match'));
        }
      }

      remote.requestTx(transactionHash, function(error, transaction) {
        if (error) {
          return async_callback(error);
        }

        // we found a transaction
        if (entry && transaction) {
          transaction.client_resource_id = entry.client_resource_id;
        }

        return async_callback(null, transaction);
      });
    });
  };

  function checkIfRelatedToAccount(transaction, async_callback) {
    if (options.account) {
      var transaction_string = JSON.stringify(transaction);
      var account_regex = new RegExp(options.account);
      if (!account_regex.test(transaction_string)) {
        return async_callback(new errors.InvalidRequestError('Transaction specified did not affect the given account'));
      }
    }

    async_callback(null, transaction);
  };

  function attachResourceID(transaction, async_callback) {
    if (transaction && options.client_resource_id) {
      transaction.client_resource_id = options.client_resource_id;
{'fraction_non_alphanumeric': 0.10437687938523221, 'fraction_numerical': 0.0009355162044771133, 'mean_word_length': 3.427810650887574, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4780559', 'n_tokens_mistral': 4053, 'n_tokens_neox': 3972, 'n_words': 1096}