<commit_before>const Ember = require('ember-source/dist/ember.debug');
const { dasherize } = Ember.String;
const { pluralize, singularize } = require('inflection');
const { declareInjections } = require('@cardstack/di');
const Session = require('@cardstack/plugin-utils/session');
const log = require('@cardstack/logger')('cardstack/ethereum/event-indexer');
const { fieldTypeFor } = require('./abi-utils');
const { get, set, uniqBy, merge } = require('lodash');

function attachMeta(model, meta) {
  model.meta = Object.assign(model.meta || {}, meta);
  return model;
}

module.exports = declareInjections({
  indexer: 'hub:indexers',
  searchers: 'hub:searchers',
  currentSchema: 'hub:current-schema',
  pgsearchClient: `plugin-client:${require.resolve('@cardstack/pgsearch/client')}`
},

class EthereumEventIndexer {

  static create(...args) {
    return new this(...args);
  }

  constructor({ indexer, pgsearchClient, currentSchema, searchers }) {
    this.ethereumClient = null;
    this.indexer = indexer;
    this.searchers = searchers;
    this.currentSchema = currentSchema;
    this.pgsearchClient = pgsearchClient;
    this.contractDefinitions = {};
    this.contractName = null;
    this._indexingPromise = null; // this is exposed to the tests as web3 has poor support for async in event handlers
  }

  async start({ name, contract, ethereumClient }) {
    await this.pgsearchClient.ensureDatabaseSetup();

    this.contractDefinitions[name] = contract;
    this.ethereumClient = ethereumClient;

    await this.ethereumClient.startEventListening({ name, contract, eventIndexer: this });
  }

  async index(contractName, blockHeight, history) {
    this._indexingPromise = Promise.resolve(this._indexingPromise)
      .then(() => this._processRecords({contractName, blockHeight, history }));

    await this._indexingPromise;
  }

  async shouldSkipIndexing(contractName) {
    return await this.ethereumClient.shouldSkipIndexing(contractName);
  }

  async getBlockHeight() {
    return await this.ethereumClient.getBlockHeight();
  }

  async _indexRecord(batch, record) {
    log.debug('indexing model in pgsearch %j', record);
    let { id, type } = record;
    let schema = await this.currentSchema.getSchema();
    let contentType = schema.types.get(type);
    let sourceId = contentType.dataSource.id;
    let context = this.searchers.createDocumentContext({
      id,
      type,
      schema,
      sourceId,
      upstreamDoc: { data: record }
    });

    await batch.saveDocument(context);
  }

  _getContractNameForEvent(event) {
    let eventAddress = event.address;
    for (let contractName of Object.keys(this.contractDefinitions)) {
      let contractAddress = get(this.contractDefinitions, `${contractName}.address`);
      if (contractAddress.toLowerCase() === eventAddress.toLowerCase()) {
        return contractName;
      }
    }
  }

  async _processRecords({ contractName, blockHeight, history }) {
    log.debug('processing records for ethereum with last indexed blockheight %j, history: %j', blockHeight, history);
    let contractDefinition = this.contractDefinitions[contractName];
    if (!contractDefinition) { return; }

    let batch = this.pgsearchClient.beginBatch(this.currentSchema, this.searchers);

    let blockheight = await this.ethereumClient.getBlockHeight();
    await this._indexRecord(batch, attachMeta(await this.ethereumClient.getContractInfo({ contract: contractName }), { blockheight, contractName }));

    if (!history || !history.length) {
      log.info(`Retreving full history for contract ${contractName} address: ${JSON.stringify((contractDefinition.addresses))} since blockheight ${blockHeight}`);
      history = await this.ethereumClient.getContractHistorySince(blockHeight);
    }

    for (let { event, identifiers } of history) {
      // The history includes the combined events from all contracts--need to get the specific contract context for the event
      let contractNameForEvent = this._getContractNameForEvent(event);
      if (!contractNameForEvent) { continue; }

      let contractDefinitionForEvent = this.contractDefinitions[contractNameForEvent];<commit_msg>fixing log message<commit_after>const Ember = require('ember-source/dist/ember.debug');
const { dasherize } = Ember.String;
const { pluralize, singularize } = require('inflection');
const { declareInjections } = require('@cardstack/di');
const Session = require('@cardstack/plugin-utils/session');
const log = require('@cardstack/logger')('cardstack/ethereum/event-indexer');
const { fieldTypeFor } = require('./abi-utils');
const { get, set, uniqBy, merge } = require('lodash');

function attachMeta(model, meta) {
  model.meta = Object.assign(model.meta || {}, meta);
  return model;
}

module.exports = declareInjections({
  indexer: 'hub:indexers',
  searchers: 'hub:searchers',
  currentSchema: 'hub:current-schema',
  pgsearchClient: `plugin-client:${require.resolve('@cardstack/pgsearch/client')}`
},

class EthereumEventIndexer {

  static create(...args) {
    return new this(...args);
  }

  constructor({ indexer, pgsearchClient, currentSchema, searchers }) {
    this.ethereumClient = null;
    this.indexer = indexer;
    this.searchers = searchers;
    this.currentSchema = currentSchema;
    this.pgsearchClient = pgsearchClient;
    this.contractDefinitions = {};
    this.contractName = null;
    this._indexingPromise = null; // this is exposed to the tests as web3 has poor support for async in event handlers
  }

  async start({ name, contract, ethereumClient }) {
    await this.pgsearchClient.ensureDatabaseSetup();

    this.contractDefinitions[name] = contract;
    this.ethereumClient = ethereumClient;

    await this.ethereumClient.startEventListening({ name, contract, eventIndexer: this });
  }

  async index(contractName, blockHeight, history) {
    this._indexingPromise = Promise.resolve(this._indexingPromise)
      .then(() => this._processRecords({contractName, blockHeight, history }));

    await this._indexingPromise;
  }

  async shouldSkipIndexing(contractName) {
    return await this.ethereumClient.shouldSkipIndexing(contractName);
  }

  async getBlockHeight() {
    return await this.ethereumClient.getBlockHeight();
  }

  async _indexRecord(batch, record) {
    log.debug('indexing model in pgsearch %j', record);
    let { id, type } = record;
    let schema = await this.currentSchema.getSchema();
    let contentType = schema.types.get(type);
    let sourceId = contentType.dataSource.id;
    let context = this.searchers.createDocumentContext({
      id,
      type,
      schema,
      sourceId,
      upstreamDoc: { data: record }
    });

    await batch.saveDocument(context);
  }

  _getContractNameForEvent(event) {
    let eventAddress = event.address;
    for (let contractName of Object.keys(this.contractDefinitions)) {
      let contractAddress = get(this.contractDefinitions, `${contractName}.address`);
      if (contractAddress.toLowerCase() === eventAddress.toLowerCase()) {
        return contractName;
      }
    }
  }

  async _processRecords({ contractName, blockHeight, history }) {
    log.debug('processing records for ethereum with last indexed blockheight %j, history: %j', blockHeight, history);
    let contractDefinition = this.contractDefinitions[contractName];
    if (!contractDefinition) { return; }

    let batch = this.pgsearchClient.beginBatch(this.currentSchema, this.searchers);

    let blockheight = await this.ethereumClient.getBlockHeight();
    await this._indexRecord(batch, attachMeta(await this.ethereumClient.getContractInfo({ contract: contractName }), { blockheight, contractName }));

    if (!history || !history.length) {
      log.info(`Retrieving full history for contract ${contractName} address: ${JSON.stringify((contractDefinition.address))} since blockheight ${blockHeight || 0 }`);
      history = await this.ethereumClient.getContractHistorySince(blockHeight);
    }

    for (let { event, identifiers } of history) {
      // The history includes the combined events from all contracts--need to get the specific contract context for the event
      let contractNameForEvent = this._getContractNameForEvent(event);
      if (!contractNameForEvent) { continue; }

      let contractDefinitionForEvent = this.contractDefinitions[contractNameForEvent];
{'fraction_non_alphanumeric': 0.11802884615384615, 'fraction_numerical': 0.0003605769230769231, 'mean_word_length': 4.320332480818414, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, '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': '5315499', 'n_tokens_mistral': 2350, 'n_tokens_neox': 2338, 'n_words': 544}