text:
/* This is free and unencumbered software released into the public domain. */

import { Request } from './request.js';

export type Data = string;
export type Quantity = string;
export type Tag = 'latest' | 'earliest' | 'pending';
export type TypedData = unknown;

export type BlockResult = Record<
  string,
  Quantity | Data | Data[] | TransactionResult[] | null
>;

export interface TransactionForCall {
  from?: Data;
  to: Data;
  gas?: Quantity;
  gasPrice?: Quantity;
  value?: Quantity;
  data?: Data;
}

export interface TransactionForSend {
  from: Data;
  to?: Data;
  gas?: Quantity;
  gasPrice?: Quantity;
  value?: Quantity;
  data: Data;
  nonce?: Quantity;
}

export interface TransactionResult {
  blockHash: Data | null;
  blockNumber: Quantity | null;
  transactionIndex: Quantity | null;
  hash: Data;
  from: Data;
  to: Data | null;
  gas: Quantity;
  gasPrice: Quantity;
  value: Quantity;
  input: Data;
  nonce: Quantity;
  v: Quantity;
  r: Data;
  s: Data;
}

export type TransactionReceipt = Record<
  string,
  Data | Quantity | LogObject[] | null
>;

export type FilterTopic = FilterTopic[] | Data | null;

export interface FilterOptions {
  fromBlock?: Quantity | Tag;
  toBlock?: Quantity | Tag;
  address?: Data | Data[];
  topics?: FilterTopic[];
  blockHash?: Data; // EIP-234
  index?: Quantity | null;
}

export type ProofResult = Record<string, unknown>;
export type SyncStatus = Record<string, Quantity>;
export type LogObject =
  | Data
  | Record<string, boolean | Quantity | Data | Data[] | null>;

export interface Service {
  web3_clientVersion(_request: Request): Promise<string>;
  web3_sha3(_request: Request, input: Data): Promise<Data>;
  net_listening(_request: Request): Promise<boolean>;
  net_peerCount(_request: Request): Promise<Quantity>;
  net_version(_request: Request): Promise<string>;
  eth_accounts(_request: Request): Promise<Data[]>;
  eth_blockNumber(_request: Request): Promise<Quantity>;
  eth_call(
    _request: Request,
    transaction: TransactionForCall,
    blockNumber?: Quantity | Tag
  ): Promise<Data>;
  eth_chainId(_request: Request): Promise<Quantity>; // EIP-695
  eth_coinbase(_request: Request): Promise<Data>;
  eth_compileLLL(_request: Request, code: string): Promise<Data>;
  eth_compileSerpent(_request: Request, code: string): Promise<Data>;
  eth_compileSolidity(_request: Request, code: string): Promise<Data>;
  eth_estimateGas(
    _request: Request,
    transaction: TransactionForCall,
    blockNumber?: Quantity | Tag
  ): Promise<Quantity>;
  eth_gasPrice(_request: Request): Promise<Quantity>;
  eth_getBalance(
    _request: Request,
    address: Data,
    blockNumber?: Quantity | Tag
  ): Promise<Quantity>;
  eth_getBlockByHash(
    _request: Request,
    blockHash: Data,
    fullObject?: boolean
  ): Promise<BlockResult | null>;
  eth_getBlockByNumber(
    _request: Request,
    blockNumber: Quantity | Tag,
    fullObject?: boolean
  ): Promise<BlockResult | null>;
  eth_getBlockTransactionCountByHash(
    _request: Request,
    blockHash: Data
  ): Promise<Quantity | null>;
  eth_getBlockTransactionCountByNumber(
    _request: Request,
    blockNumber: Quantity | Tag
  ): Promise<Quantity | null>;
  eth_getCode(
    _request: Request,
    address: Data,
    blockNumber: Quantity | Tag
  ): Promise<Data>;
  eth_getCompilers(__request: Request, request: any): Promise<string[]>;
  eth_getFilterChanges(
    _request: Request,
    filterID: Quantity
  ): Promise<LogObject[]>;
  eth_getFilterLogs(
    _request: Request,
    filterID: Quantity
  ): Promise<LogObject[]>;
  eth_getLogs(_request: Request, filter: FilterOptions): Promise<LogObject[]>;
  eth_getProof(
    _request: Request,
    address: Data,
    keys: Data[],
    blockNumber: Quantity | Tag
  ): Promise<ProofResult>; // EIP-1186
  eth_getStorageAt(
    _request: Request,
    address: Data,
    key: Quantity,
    blockNumber: Quantity | Tag
  ): Promise<Data>;
  eth_getTransactionByBlockHashAndIndex(
    _request: Request,
    blockHash: Data,
    transactionIndex: Quantity
  ): Promise<TransactionResult | null>;
  eth_getTransactionByBlockNumberAndIndex(
    _request: Request,
    blockNumber: Quantity | Tag,
    transactionIndex: Quantity
  ): Promise<TransactionResult | null>;
  eth_getTransactionByHash(
    _request: Request,
    transactionHash: Data
  ): Promise<TransactionResult | null>;
  eth_getTransactionCount(
    _request: Request,
    address: Data,
    blockNumber: Quantity | Tag
  ): Promise<Quantity>;
  eth_getTransactionReceipt(
    _request: Request,
    transactionHash: string
  ): Promise<TransactionReceipt | null>;
  eth_getUncleByBlockHashAndIndex(
    _request: Request,
    blockHash: Data,
    uncleIndex: Quantity
  ): Promise<BlockResult | null>;
  eth_getUncleByBlockNumberAndIndex(
    _request: Request,
    blockNumber: Quantity | Tag,
    uncleIndex: Quantity
  ): Promise<BlockResult | null>;
  eth_getUncleCountByBlockHash(
    _request: Request,
    blockHash: Data
  ): Promise<Quantity | null>;
  eth_getUncleCountByBlockNumber(
    _request: Request,
    blockNumber: Quantity | Tag
  ): Promise<Quantity | null>;
  eth_getWork(_request: Request): Promise<Data[]>;
  eth_hashrate(_request: Request): Promise<Quantity>;
  eth_mining(_request: Request): Promise<boolean>;
  eth_newBlockFilter(_request: Request): Promise<Quantity>;
  eth_newFilter(_request: Request, filter: FilterOptions): Promise<Quantity>;
  eth_newPendingTransactionFilter(_request: Request): Promise<Quantity>;
  eth_pendingTransactions(
    _request: Request
  ): Promise<Record<string, string | number | null>[]>; // undocumented
  eth_protocolVersion(_request: Request): Promise<string>;
  eth_sendRawTransaction(_request: Request, transaction: Data): Promise<Data>;
  eth_sendTransaction(
    _request: Request,
    transaction: TransactionForSend
  ): Promise<Data>;
  eth_sign(_request: Request, account: Data, message: Data): Promise<Data>;
  eth_signTransaction(
    _request: Request,
    transaction: TransactionForSend
  ): Promise<Data>;
  eth_signTypedData(
    _request: Request,
    address: Data,
    data: TypedData
  ): Promise<Data>; // EIP-712
  eth_submitHashrate(
    _request: Request,
    hashrate: Quantity,
    clientID: Quantity
  ): Promise<boolean>;
  eth_submitWork(
    _request: Request,
    nonce: Data,
    powHash: Data,
    mixDigest: Data
  ): Promise<boolean>;
  eth_syncing(_request: Request): Promise<SyncStatus | false>;
  eth_uninstallFilter(_request: Request, filterID: Quantity): Promise<boolean>;
  txpool_content(_request: Request): Promise<Record<string, any>>;
  txpool_inspect(_request: Request): Promise<Record<string, any>>;
  txpool_status(_request: Request): Promise<Record<string, number>>;
  parity_pendingTransactions(
    _request: Request,
    limit?: number | null,
    filter?: Record<string, any>
  ): Promise<any[]>;
}


text:
/* This is free and unencumbered software released into the public domain. */

import { Config } from './config.js';
import middleware from './middleware.js';
import { createServer } from './server.js';
import { createWsServer } from './ws_server.js';

import { Engine, TransactionErrorDetails } from '@aurora-is-near/engine';
import bodyParser from 'body-parser';
import cors from 'cors';
import express from 'express';
import jayson from 'jayson';
//import { exit } from 'process';
import { Logger } from 'pino';
import { parse as parseRawTransaction } from '@ethersproject/transactions';

//import { assert } from 'node:console';

interface Headers {
  'Content-Length'?: number;
  'Content-Type': string;
  'X-Aurora-Process-Result'?: string;
}

export async function createApp(
  config: Config,
  logger: Logger,
  engine: Engine
): Promise<any> {
  const app = express();
  app.disable('x-powered-by');

  app.use(middleware.setRequestID());
  app.use(middleware.blacklistIPs());
  app.use(bodyParser.json({ type: 'application/json' }));
  app.use(middleware.logger(logger));
  app.use(cors()); // Access-Control-Allow-Origin: *
  app.get('/health', (req, res) => {
    res.send('OK');
  });
  app.get('/metrics', (req, res) => {
    res.send(''); // TODO
  });
  app.use(rpcMiddleware(createServer(config, logger, engine)));
  app.use(middleware.handleErrors());
  await createWsServer(config, logger, engine, app);

  return app;
}

interface HandleResponseProps {
  body: any;
  res: any;
  req: any;
  response: any;
  options: any;
  sendRawTransactionExists?: boolean;
}

interface SendRawTransactionMetaData {
  code?: string;
  details: {
    tx?: string;
    gasBurned?: number;
  };
  transactionResult?: string;
  errorExists: boolean;
}

interface SendRawTransactionMetaDataResult {
  neargas?: number;
  tx?: string;
  result?: string;
  error?: string;
}

const sendRawTransactionMetaData = ({
  code,
  details,
  transactionResult,
  errorExists,
}: SendRawTransactionMetaData): SendRawTransactionMetaDataResult => {
  const metadata: SendRawTransactionMetaDataResult = {};

  if (details.gasBurned) {
    metadata.neargas = details.gasBurned;
  }
  if (details.tx) {
    metadata.tx = details.tx;
  }
  if (transactionResult) {
    metadata.result = transactionResult;
  }
  if (errorExists && code) {
    metadata.error = code;
  }

  return metadata;
};

const handleResponse = ({
  body,
  res,
  req,
  response,
  options,
  sendRawTransactionExists,
}: HandleResponseProps) => {
  if (!body) {
    res.writeHead(204);
  } else {
    const headers: Headers = {
      'Content-Type': 'application/json; charset=utf-8',
    };

    if (
      sendRawTransactionExists &&
      Array.isArray(req?.body) &&
      Array.isArray(response)
    ) {
      const processResultHeader = response.reduce((acc, transaction) => {
        const transactionRequest = req.body.find(
          (transactionRequest: { id: number | string }) =>
            transactionRequest.id === transaction.id
        );

        let metadata = {};

        if (transactionRequest.method === 'eth_sendRawTransaction') {
          const { code, details } = parseTransactionDetails(
            transaction?.error?.message || transaction?.result
          );

          if (typeof transaction?.error?.message === 'string') {
            if (code) {
              transaction.error.message = code;
            }
          } else if (transaction?.result) {
            transaction.result = code;
          }

          metadata = sendRawTransactionMetaData({
            code,
            details,
            transactionResult: transaction?.result && code,
            errorExists: typeof transaction?.error?.message === 'string',
          });
        }

        acc.push(metadata);
        return acc;
      }, []);

      headers['X-Aurora-Process-Result'] = JSON.stringify(processResultHeader);
      body = JSON.stringify(response);
    }

    if (req?.body?.method == 'eth_sendRawTransaction') {
      const { code, details } = parseTransactionDetails(
        response?.error?.message || response?.result
      );

      const metadata = sendRawTransactionMetaData({
        code,
        details,
        transactionResult: response?.result && code,
        errorExists: typeof response?.error?.message === 'string',
      });

      headers['X-Aurora-Process-Result'] = JSON.stringify([metadata]);

      if (typeof response?.error?.message === 'string') {
        if (code) {
          response.error.message = code;
        }
        body = JSON.stringify(response);
      } else if (response?.result) {
        response.result = code;
        body = JSON.stringify(response);
      }
    }
    headers['Content-Length'] = Buffer.byteLength(body, options.encoding);
    res.writeHead(200, headers);
    res.write(body);
  }
  res.end();
};

function parseErrorDetails(details: string): TransactionErrorDetails {
  try {
    return JSON.parse(details);
  } catch (e) {
    return {};
  }
}

function parseTransactionDetails(
  message: string | undefined
): { code: string | undefined; details: any } {
  if (message === undefined) {
    return { code: message, details: {} };
  }
  let code = message;
  const sepIndex = code.lastIndexOf('|');
  if (sepIndex > -1) {
    code = message.substring(0, sepIndex);
    const details = parseErrorDetails(message.substring(sepIndex + 1));
    return { code: code, details: details };
  } else {
    return { code: code, details: {} };
  }
}

function rpcMiddleware(server: jayson.Server): any {
  return async function (req: any, res: any, next: any): Promise<any> {
    const options: any = server.options;

    if (req.headers['sec-websocket-key']) {
      return next();
    }

    if ((req.method || '') != 'POST') {
      return error(405, { Allow: 'POST' });
    }

    if (
      !RegExp('application/json', 'i').test(
        (req || { headers: {} }).headers['content-type'] || ''
      )
    ) {
      return error(415);
    }

    const findRawTx = (element: any) =>
      element.method == 'eth_sendRawTransaction';

    if (Array.isArray(req.body) && req.body.some(findRawTx)) {
      const parsedPayloads = req.body.map((element: any, index: number) => {
        return {
          ...element,
          aurora_req_position: index,
          parsed:
            element.method === 'eth_sendRawTransaction' &&
            parseRawTransaction(element.params[0]),
        };
      });

      parsedPayloads.sort((a: any, b: any) =>
        a.parsed.nonce > b.parsed.nonce ? 1 : -1
      );

      const executedPayloads = await parsedPayloads.reduce(
        async (acc: any, element: any) => {
          const collection = await acc;
          const promise = new Promise((resolve) => {
            server.call(element, req, function (error: any, success: any) {
              const response = success || error;
              response.aurora_req_position = element.aurora_req_position;
              resolve(response);
            });
          });

          const result = await promise;
          collection.push(result);

          return collection;
        },
        Promise.resolve([])
      );

      executedPayloads.sort((a: any, b: any) =>
        a.aurora_req_position > b.aurora_req_position ? 1 : -1
      );

      executedPayloads.forEach((payload: any) => {
        delete payload.aurora_req_position;
      });

      const body = JSON.stringify(executedPayloads);

      handleResponse({
        body,
        res,
        req,
        response: executedPayloads,
        options,
        sendRawTransactionExists: true,
      });
    } else {
      server.call(req.body, req, function (error: any, success: any) {
        const response = error || success;
        const body = JSON.stringify(response);

        handleResponse({
          body,
          res,
          req,
          response,
          options,
        });
      });
    }

    function error(code: any, headers?: any) {
      res.writeHead(code, headers || {});
      res.end();
    }
  };
}

// function response(id: string, result: any, error: any) {
//   const resp = { jsonrpc: '2.0', id };
//   if (error) {
//     Object.assign(resp, { error });
//   } else {
//     Object.assign(resp, { result });
//   }
//   return resp;
// }

repo_name: aurora-is-near/aurora-relayer

repo_language: TypeScript

mime_type: text/plain


text:
/* This is free and unencumbered software released into the public domain. */

import { Config } from './config.js';
import { SkeletonServer } from './servers/skeleton.js';
import { DatabaseServer } from './servers/database.js';
import { Request } from './request.js';
import { ExpectedError } from './errors.js';

import { bytesToHex } from '@aurora-is-near/engine';
import { Engine } from '@aurora-is-near/engine';
import jayson from 'jayson';
import { Logger } from 'pino';

interface MethodMap {
  [methodName: string]: jayson.MethodLike;
}

export class Method extends jayson.Method {
  public readonly server;

  constructor(
    public handler?: jayson.MethodHandlerType,
    options?: jayson.MethodOptions
  ) {
    super(handler, {});
    this.server = options?.useContext as any; // HACK
  }

  getHandler(): jayson.MethodHandlerType {
    return this.handler!;
  }

  setHandler(handler: jayson.MethodHandlerType) {
    this.handler = handler;
  }

  execute(
    server: jayson.Server,
    requestParams: jayson.RequestParamsLike,
    request: any, // context
    callback: jayson.JSONRPCCallbackType
  ): any {
    const args = (requestParams || []) as any[];
    const result: Promise<any> = (this.handler as any).call(
      this.server,
      new Request(request),
      ...args
    );
    result
      .then((value: any) => (callback as any)(undefined, value))
      .catch((error: Error) => {
        const metadata = {
          host: request?.headers?.host || undefined,
          'cf-ray': request?.headers?.['cf-ray'] || undefined,
          'cf-request-id': request?.headers?.['cf-request-id'] || undefined,
        };
        if (error instanceof ExpectedError) {
          return (callback as any)(
            server.error(
              error.code,
              error.message,
              error.data
                ? ((bytesToHex(error.data) as unknown) as undefined)
                : {
                    ...metadata,
                    ...{ request_body: request.body },
                  }
            )
          );
        }
        if (this.server?.config?.debug) {
          console.error(error);
        }
        if (this.server?.logger) {
          this.server.logger.error(error);
        }
        return (callback as any)(
          server.error(
            -32603,
            `Internal Error - ${error.message}. Please report a bug at <https://github.com/aurora-is-near/aurora-relayer/issues>`,
            {
              ...metadata,
              ...{ request_body: request.body },
            }
          )
        );
      });
    return null;
  }
}

export function createServer(
  config: Config,
  logger: Logger,
  engine: Engine
): jayson.Server {
  const server = new DatabaseServer(config, logger, engine);
  const methodList = Object.getOwnPropertyNames(SkeletonServer.prototype)
    .filter((id: string) => id !== 'constructor' && id[0] != '_')
    .map((id: string) => [id, (server as any)[id]]);
  const methodMap: MethodMap = Object.fromEntries(methodList);
  const jaysonServer = new jayson.Server(methodMap, {
    methodConstructor: Method,
    useContext: server as any,
  });
  return jaysonServer;
}


text:
/* This is free and unencumbered software released into the public domain. */

import yaml from 'js-yaml';
import fs from 'fs';

export interface BlacklistConfig {
  IPs: Set<string>;
  EOAs: Set<string>;
  CAs: Set<string>;
}

export function blacklist(method: string) {
  const globalAny = global as any;
  if (
    globalAny.blacklistConfig === undefined ||
    globalAny.blacklistConfig === null
  ) {
    try {
      const blacklistConfig = yaml.load(
        fs.readFileSync('config/blacklist.yaml', 'utf8')
      ) as BlacklistConfig;
      globalAny.blacklistConfig = {
        IPs: new Set(blacklistConfig.IPs || []),
        EOAs: new Set(blacklistConfig.EOAs || []),
        CAs: new Set(blacklistConfig.CAs || []),
      };
    } catch (e) {
      console.error(`Blacklist configuration file can not be loaded (${e}).`);
      globalAny.blacklistConfig = {
        IPs: new Set([]),
        EOAs: new Set([]),
        CAs: new Set([]),
      };
    }
  }
  return globalAny.blacklistConfig[method];
}


text:
/* This is free and unencumbered software released into the public domain. */

import { Config, parseConfig } from '../config.js';
import { pg, sql } from '../database.js';
import { emptyTransactionsRoot } from '../utils.js';

import {
  AccountID,
  Engine,
  ConnectEnv,
  NetworkConfig,
} from '@aurora-is-near/engine';
import { program } from 'commander';
import externalConfig from 'config';
import pino, { Logger } from 'pino';

const logger = pino(pino.destination(2));

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace NodeJS {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface ProcessEnv extends ConnectEnv {}
  }
}

class ReindexWorker {
  protected readonly contractID: AccountID;
  protected readonly pgClient: pg.Client;

  constructor(
    public readonly config: Config,
    public readonly network: NetworkConfig,
    public readonly logger: Logger,
    public readonly engine: Engine
  ) {
    this.contractID = AccountID.parse(this.config.engine).unwrap();
    this.pgClient = new pg.Client(config.database);
  }

  async run(blockId: number): Promise<void> {
    await this.pgClient.connect();
    const step = Number(this.config.batchSize || 1000);

    let startBlockQuery = sql
      .select('block.id')
      .from('block')
      .order('block.id DESC')
      .limit(1);
    if (blockId > 0) {
      startBlockQuery = startBlockQuery.where(sql(`block.id <= $1`, blockId));
    }
    const transactionQueryResult = await this.pgClient.query(
      startBlockQuery.toParams()
    );
    const startBlockId = transactionQueryResult.rows[0].id;

    for (let blockId = startBlockId; blockId > 0; blockId -= step) {
      const endBlockId = blockId - step > 0 ? blockId - step : 0;
      logger.info(`Fetching blocks ${endBlockId}..${blockId}`);
      const updateQuery = sql
        .update('block', { transactions_root: emptyTransactionsRoot() })
        .where(
          sql(
            '(SELECT COUNT(id) FROM transaction t WHERE t.block = block.id) = 0'
          )
        )
        .where(
          sql(`block.id >= $1 AND block.id < $2`, blockId - step, blockId)
        );
      await this.pgClient.query(updateQuery.toParams());
    }
    process.exit(0); // EX_OK
  }
}

async function main(argv: string[], env: NodeJS.ProcessEnv) {
  program
    .option('-d, --debug', 'enable debug output')
    .option('-v, --verbose', 'enable verbose output')
    .option(
      '--network <network>',
      `specify NEAR network ID (default: "${env.NEAR_ENV || 'local'}")`
    )
    .option(
      '--engine <account>',
      `specify Aurora Engine account ID (default: "${
        env.AURORA_ENGINE || 'aurora.test.near'
      }")`
    )
    .option(
      '-B, --block <block>',
      `specify block height to begin indexing from (default: 0)`
    )
    .option(
      '--batch-size <batchSize>',
      `specify batch size for fetching block metadata (default: 1000)`
    )
    .parse(argv);

  const opts = program.opts() as Config;
  const [network, config] = parseConfig(
    opts,
    (externalConfig as unknown) as Config,
    env
  );
  const blockID = opts.block !== undefined ? parseInt(opts.block as string) : 0;

  if (config.debug) {
    for (const source of externalConfig.util.getConfigSources()) {
      console.error(`Loaded configuration file ${source.name}.`);
    }
    console.error('Configuration:', config);
  }

  logger.info('starting reindexing of transactions');
  const engine = await Engine.connect(
    {
      network: network.id,
      endpoint: config.endpoint,
      contract: config.engine,
    },
    env
  );
  const indexer = new ReindexWorker(config, network, logger, engine);
  await indexer.run(blockID);
}

main(process.argv, process.env).catch((error: Error) => {
  const errorMessage = error.message.startsWith('<')
    ? error.name
    : error.message;
  logger.error(errorMessage);
  process.exit(70); // EX_SOFTWARE
});


text:
/* This is free and unencumbered software released into the public domain. */

import { Config, parseConfig } from '../config.js';
import { pg, sql } from '../database.js';

import {
  AccountID,
  Engine,
  LogEventWithAddress,
  ConnectEnv,
  NetworkConfig,
  Transaction,
} from '@aurora-is-near/engine';
import { program } from 'commander';
import externalConfig from 'config';
import pino, { Logger } from 'pino';

const logger = pino(pino.destination(2));

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace NodeJS {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface ProcessEnv extends ConnectEnv {}
  }
}

export class ReindexWorker {
  protected readonly contractID: AccountID;
  protected readonly pgClient: pg.Client;

  constructor(
    public readonly config: Config,
    public readonly network: NetworkConfig,
    public readonly logger: Logger,
    public readonly engine: Engine
  ) {
    this.contractID = AccountID.parse(this.config.engine).unwrap();
    this.pgClient = new pg.Client(config.database);
  }

  async run(startBlockId: number, mode?: string): Promise<void> {
    await this.pgClient.connect();
    const step = Number(this.config.batchSize || 1000);
    let startEventQuery = sql
      .select('event.id')
      .from('transaction')
      .join('event')
      .on({ 'transaction.id': 'event.transaction' })
      .order('event.id DESC')
      .limit(1);
    if (startBlockId > 0) {
      startEventQuery = startEventQuery.where(
        sql(`transaction.block <= ${startBlockId}`)
      );
    }
    const eventQueryResult = await this.pgClient.query(
      startEventQuery.toParams()
    );

    const startEventId = eventQueryResult.rows[0].id;
    for (let eventId = startEventId; eventId > 0; eventId -= step) {
      logger.info(`Fetching events ${eventId - step}..${eventId}`);
      const query = sql
        .select(
          'DISTINCT transaction.block AS block_id, transaction.id AS transaction_id'
        )
        .from('transaction')
        .join('event')
        .on({ 'transaction.id': 'event.transaction' })
        .where({ 'event.from': null })
        .where(sql(`event.id >= ${eventId - step} AND event.id < ${eventId}`));
      const result = await this.pgClient.query(query.toParams());

      await Promise.all(
        result.rows.map(async (row: any) => {
          const blockId = parseInt(row.block_id);
          const blockProxy = await this.engine.getBlock(blockId, {
            transactions: 'full',
            contractID: this.contractID,
          });
          if (blockProxy.isErr()) {
            const error = blockProxy.unwrapErr();
            logger.error(error);
          } else {
            const block_ = blockProxy.unwrap();
            const block = block_.getMetadata();

            (block.transactions as Transaction[]).forEach((transaction) => {
              (transaction.result?.result?.logs || []).forEach(
                (event, eventIndex) => {
                  const event_ = event as LogEventWithAddress;
                  const updateQuery = sql
                    .update('event', {
                      from: event_.address
                        ? Buffer.from(event_.address)
                        : Buffer.alloc(20),
                    })
                    .where({
                      index: eventIndex,
                      transaction: row.transaction_id,
                    });
                  this.pgClient.query(updateQuery.toParams());
                }
              );
            });
          }
        })
      );
    }
    process.exit(0); // EX_OK
  }
}

async function main(argv: string[], env: NodeJS.ProcessEnv) {
  program
    .option('-d, --debug', 'enable debug output')
    .option('-v, --verbose', 'enable verbose output')
    .option(
      '--network <network>',
      `specify NEAR network ID (default: "${env.NEAR_ENV || 'local'}")`
    )
    .option(
      '--engine <account>',
      `specify Aurora Engine account ID (default: "${
        env.AURORA_ENGINE || 'aurora.test.near'
      }")`
    )
    .option(
      '-B, --block <block>',
      `specify block height to begin indexing from (default: 0)`
    )
    .option(
      '--batch-size <batchSize>',
      `specify batch size for fetching block metadata (default: 1000)`
    )
    .parse(argv);

  const opts = program.opts() as Config;
  const [network, config] = parseConfig(
    opts,
    (externalConfig as unknown) as Config,
    env
  );
  const blockID = opts.block !== undefined ? parseInt(opts.block as string) : 0;

  if (config.debug) {
    for (const source of externalConfig.util.getConfigSources()) {
      console.error(`Loaded configuration file ${source.name}.`);
    }
    console.error('Configuration:', config);
  }

  logger.info('starting reindexing of events');
  const engine = await Engine.connect(
    {
      network: network.id,
      endpoint: config.endpoint,
      contract: config.engine,
    },
    env
  );
  const indexer = new ReindexWorker(config, network, logger, engine);
  await indexer.run(blockID, 'follow');
}

main(process.argv, process.env).catch((error: Error) => {
  const errorMessage = error.message.startsWith('<')
    ? error.name
    : error.message;
  logger.error(errorMessage);
  process.exit(70); // EX_SOFTWARE
});


text:
/* This is free and unencumbered software released into the public domain. */

import { SkeletonServer } from './skeleton.js';

import { Bus } from '../bus.js';
import { pg, sql } from '../database.js';
import {
  InvalidAddress,
  InvalidArguments,
  RevertError,
  TransactionError,
  UnexpectedError,
  UnknownFilter,
  UnsupportedMethod,
  GasPriceTooLow,
  LimitExceeded,
} from '../errors.js';
import { Request } from '../request.js';
import { compileTopics } from '../topics.js';
import * as web3 from '../web3.js';
import { parse } from 'postgres-array';

import {
  Address,
  BlockID,
  bytesToHex,
  exportJSON,
  formatU256,
  hexToBytes,
  hexToInt,
  intToHex,
} from '@aurora-is-near/engine';
import fs from 'fs';
import { getRandomBytesSync } from 'ethereum-cryptography/random.js';
import { ResErr } from '@hqoss/monads/dist/lib/result/result';

import {
  parse as parseRawTransaction,
  Transaction,
} from '@ethersproject/transactions';
import { keccak256 } from 'ethereumjs-util';

export class DatabaseServer extends SkeletonServer {
  protected pgClient?: pg.Client;
  protected bus?: Bus;

  protected async _init(): Promise<void> {
    // Connect to the PostgreSQL database:
    const pgClient = new pg.Client(this.config.database);
    this.pgClient = pgClient;
    await pgClient.connect();

    // Connect to the NATS message broker:
    if (this.config.broker) {
      this.bus = new Bus(this.config);
    }

    // Add type parsers for relevant numeric types:
    (pgClient as any).setTypeParser(pg.types.builtins.INT8, (val: string) =>
      BigInt(val)
    );
    (pgClient as any).setTypeParser(pg.types.builtins.NUMERIC, (val: string) =>
      BigInt(val)
    );
    for (const typeName of ['blockno', 'chainid', 'u64', 'u256']) {
      const query = sql
        .select('oid')
        .from('pg_type')
        .where({ typname: typeName });
      const { rows } = await pgClient.query(query.toParams());
      if (rows.length > 0) {
        const [{ oid }] = rows;
        (pgClient as any).setTypeParser(oid, (val: string) => BigInt(val));
      }
    }
  }

  protected _query(
    query: string | /*sql.SelectStatement*/ any,
    args?: unknown[]
  ): Promise<pg.QueryResult<any>> {
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    return this.pgClient!.query(
      typeof query === 'string' ? query : query.toParams(),
      args
    );
  }

  async eth_blockNumber(_request: Request): Promise<web3.Quantity> {
    return intToHex(await this._fetchCurrentBlockID());
  }

  async eth_call(
    _request: Request,
    transaction: web3.TransactionForCall,
    blockNumberOrHash?: web3.Quantity | web3.Tag | web3.Data
  ): Promise<web3.Data> {
    let blockNumber_;
    // EIP-1898 (enables the argument to be not only blockNumber, but also blockHash)
    if (typeof blockNumberOrHash === 'string') {
      if (/^0x([A-Fa-f0-9]{64})$/.test(blockNumberOrHash)) {
        // Regex reference here https://ethereum.stackexchange.com/questions/34285/what-is-the-regex-to-validate-an-ethereum-transaction-hash/34286#34286
        const block_ = await this.eth_getBlockByHash(
          _request,
          blockNumberOrHash
        );
        if (block_ === null) throw Error('Block is temporarily unavailable');
        blockNumber_ = parseBlockSpec(
          block_ ? block_['number']?.toString() : null
        );
      } else {
        blockNumber_ = parseBlockSpec(blockNumberOrHash);
      }
    }
    const from = transaction.from
      ? parseAddress(transaction.from)
      : Address.zero();
    this._enforceEOABan(from, 'eth_call');
    const to = parseAddress(transaction.to);
    this._enforceBans(to, 'eth_call');
    const value = transaction.value ? hexToInt(transaction.value) : 0;
    const data = transaction.data
      ? hexToBytes(transaction.data)
      : Buffer.alloc(0);

    try {
      return (
        await this.engine.view(from, to, value, data, {
          block: blockNumber_ !== null ? blockNumber_ : undefined,
        })
      ).match({
        ok: (result) => {
          if (
            !Buffer.isBuffer(result) &&
            (result as ResErr<any, any>).isErr()
          ) {
            const errorInstance = (result as ResErr<any, any>).err().unwrap()
              .constructor.name;

            if (errorInstance === 'OutOfFund') {
              throw new TransactionError('Out Of Fund');
            } else if (errorInstance === 'OutOfOffset') {
              throw new TransactionError('Out Of Offset');
            } else if (errorInstance === 'OutOfGas') {
              throw new TransactionError('Out Of Gas');
            }
          }

          return bytesToHex(result as Uint8Array);
        },
        err: (message) => {
          throw new Error(message);
        },
      });
    } catch (error: any) {
      if (error?.message?.includes('ERR_STACK_OVERFLOW')) {
        throw new TransactionError('ERR_STACK_OVERFLOW');
      }

      if (
        error?.message?.includes('FunctionCallError(WasmTrap(Unreachable))')
      ) {
        throw new TransactionError('FunctionCallError(WasmTrap(Unreachable))');
      }

      throw error;
    }
  }

  async eth_chainId(_request: Request): Promise<web3.Quantity> {
    // EIP-695
    const chainID = (await this.engine.getChainID()).unwrap();
    return intToHex(chainID);
  }

  async eth_coinbase(_request: Request): Promise<web3.Data> {
    return (await this.engine.getCoinbase()).unwrap().toString();
  }

  async eth_gasPrice(_request: Request): Promise<web3.Quantity> {
    const minGasPrice =
      this.config.minGasPrice !== undefined ? this.config.minGasPrice : 0;
    return intToHex(minGasPrice);
  }

  async eth_getBalance(
    _request: Request,
    address: web3.Data,
    blockNumber?: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity> {
    const address_ = parseAddress(address);
    this._enforceBans(address_, 'eth_getBalance');
    const balance = (await this.engine.getBalance(address_)).unwrap();
    return intToHex(balance);
  }

  async eth_getBlockByHash(
    _request: Request,
    blockHash: web3.Data,
    fullObject?: boolean
  ): Promise<web3.BlockResult | null> {
    if (!blockHash.startsWith('0x')) throw new InvalidArguments();
    const blockHash_ = hexToBytes(blockHash);
    try {
      const {
        rows: [block],
      } = await this._query('SELECT * FROM eth_getBlockByHash($1) LIMIT 1', [
        blockHash_,
      ]);
      //assert(block, 'block is not null');
      block.uncles = [];
      block.transactions = await this._fetchTransactions(
        blockHash_,
        fullObject || false
      );
      return exportJSON(block);
    } catch (error) {
      if (this.config.debug) {
        console.debug('eth_getBlockByHash', error);
      }
      return null;
    }
  }

  async eth_getBlockByNumber(
    _request: Request,
    blockNumber: web3.Quantity | web3.Tag,
    fullObject?: boolean
  ): Promise<web3.BlockResult | null> {
    const blockNumber_ =
      parseBlockSpec(blockNumber) != 0
        ? parseBlockSpec(blockNumber) || (await this._fetchCurrentBlockID())
        : 0;
    try {
      const {
        rows: [block],
      } = await this._query('SELECT * FROM eth_getBlockByNumber($1) LIMIT 1', [
        blockNumber_,
      ]);
      //assert(block, 'block is not null');
      block.uncles = [];
      block.transactions = await this._fetchTransactions(
        blockNumber_ as number,
        fullObject || false
      );
      return exportJSON(block);
    } catch (error) {
      if (this.config.debug) {
        console.debug('eth_getBlockByNumber', error);
      }
      return null;
    }
  }

  async eth_getBlockTransactionCountByHash(
    _request: Request,
    blockHash: web3.Data
  ): Promise<web3.Quantity | null> {
    const blockHash_ = blockHash.startsWith('0x')
      ? hexToBytes(blockHash)
      : blockHash;
    const {
      rows: [{ result }],
    } = await this._query(
      'SELECT eth_getBlockTransactionCountByHash($1) AS result',
      [blockHash_]
    );
    return result !== null ? intToHex(result) : null;
  }

  async eth_getBlockTransactionCountByNumber(
    _request: Request,
    blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity | null> {
    const blockNumber_ =
      parseBlockSpec(blockNumber) != 0
        ? parseBlockSpec(blockNumber) || (await this._fetchCurrentBlockID())
        : 0;
    const {
      rows: [{ result }],
    } = await this._query(
      'SELECT eth_getBlockTransactionCountByNumber($1) AS result',
      [blockNumber_]
    );
    return result !== null ? intToHex(result) : null;
  }

  async eth_getCode(
    _request: Request,
    address: web3.Data,
    blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Data> {
    const blockNumber_ = parseBlockSpec(blockNumber);
    const address_ = parseAddress(address);
    this._enforceBans(address_, 'eth_getCode');
    const code = (
      await this.engine.getCode(address_, {
        block: blockNumber_ !== null ? blockNumber_ : undefined,
      })
    ).unwrap();
    return bytesToHex(code);
  }

  async eth_getFilterChanges(
    _request: Request,
    filterID: web3.Quantity
  ): Promise<web3.LogObject[]> {
    const filterID_ = parseFilterID(filterID);
    if (filterID_.every((b) => b === 0)) {
      return [];
    }

    const filterData = await this._query(
      `SELECT
          type,
          poll_block,
          to_block,
          addresses,
          topics
        FROM filter
        WHERE uuid_send(id) = $1 LIMIT 1`,
      [filterID_]
    );

    if (!filterData.rows.length) throw new UnknownFilter(filterID);
    const filter = filterData.rows[0];

    switch (filter.type) {
      case 'block': {
        const {
          rows,
        } = await this._query(
          'SELECT * FROM eth_getFilterChanges_block($1::bytea)',
          [filterID_]
        );
        const buffers = rows.flatMap((row: Record<string, unknown>) =>
          Object.values(row)
        ) as Buffer[];
        return buffers.map(bytesToHex);
      }
      case 'event': {
        return await this._getFilterChangesEvent(filter, filterID_);
      }
      case 'transaction':
      default:
        return [];
    }
  }

  async eth_getFilterLogs(
    _request: Request,
    filterID: web3.Quantity
  ): Promise<web3.LogObject[]> {
    const filterID_ = parseFilterID(filterID);
    if (filterID_.every((b) => b === 0)) {
      return [];
    }

    const filterData = await this._query(
      `SELECT
          addresses,
          from_block,
          to_block,
          topics,
          type
        FROM filter
        WHERE uuid_send(id) = $1 LIMIT 1`,
      [filterID_]
    );

    if (!filterData.rows.length) throw new UnknownFilter(filterID);
    const filter = filterData.rows[0];

    switch (filter.type) {
      case 'block': {
        const {
          rows,
        } = await this._query(
          'SELECT * FROM eth_getFilterLogs_block($1::bytea)',
          [filterID_]
        );
        const buffers = rows.flatMap((row: Record<string, unknown>) =>
          Object.values(row)
        ) as Buffer[];
        return buffers.map(bytesToHex);
      }
      case 'event': {
        const filterOptions: web3.FilterOptions = {
          fromBlock: filter.from_block,
          toBlock: filter.to_block,
          address: filter.addresses
            ? parse(filter.addresses, (address) => {
                return address.replace('\\x', '0x');
              })
            : undefined,
          topics: filter.topics,
        };
        return await this._getLogs(filterOptions);
      }
      case 'transaction':
      default:
        return [];
    }
  }

  async eth_getLogs(
    _request: Request,
    filter: web3.FilterOptions
  ): Promise<web3.LogObject[]> {
    return await this._getLogs(filter);
  }

  async eth_getStorageAt(
    _request: Request,
    address: web3.Data,
    key: web3.Quantity,
    blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Data> {
    const address_ = parseAddress(address);
    this._enforceBans(address_, 'eth_getStorageAt');
    const result = (await this.engine.getStorageAt(address_, key)).unwrap();
    return formatU256(result);
  }

  async eth_getTransactionByBlockHashAndIndex(
    _request: Request,
    blockHash: web3.Data,
    transactionIndex: web3.Quantity
  ): Promise<web3.TransactionResult | null> {
    const [blockHash_, transactionIndex_] = [
      hexToBytes(blockHash),
      parseInt(transactionIndex),
    ];
    try {
      const {
        rows,
      } = await this._query(
        'SELECT * FROM eth_getTransactionByBlockHashAndIndex($1::hash, $2::int)',
        [blockHash_, transactionIndex_]
      );
      return !rows || !rows.length ? null : exportJSON(rows[0]);
    } catch (error) {
      if (this.config.debug) {
        console.debug('eth_getTransactionByBlockHashAndIndex', error);
      }
      return null;
    }
  }

  async eth_getTransactionByBlockNumberAndIndex(
    _request: Request,
    blockNumber: web3.Quantity | web3.Tag,
    transactionIndex: web3.Quantity
  ): Promise<web3.TransactionResult | null> {
    const blockNumber_ =
      parseBlockSpec(blockNumber) != 0
        ? parseBlockSpec(blockNumber) || (await this._fetchCurrentBlockID())
        : 0;
    const transactionIndex_ = parseInt(transactionIndex);
    try {
      const {
        rows,
      } = await this._query(
        'SELECT * FROM eth_getTransactionByBlockNumberAndIndex($1::blockno, $2::int)',
        [blockNumber_, transactionIndex_]
      );
      return !rows || !rows.length ? null : exportJSON(rows[0]);
    } catch (error) {
      if (this.config.debug) {
        console.debug('eth_getTransactionByBlockNumberAndIndex', error);
      }
      return null;
    }
  }

  async eth_getTransactionByHash(
    _request: Request,
    transactionHash: web3.Data
  ): Promise<web3.TransactionResult | null> {
    const transactionHash_ = hexToBytes(transactionHash);
    try {
      const {
        rows,
      } = await this._query('SELECT * FROM eth_getTransactionByHash($1)', [
        transactionHash_,
      ]);
      return !rows || !rows.length ? null : exportJSON(rows[0]);
    } catch (error) {
      if (this.config.debug) {
        console.debug('eth_getTransactionByHash', error);
      }
      return null;
    }
  }

  async eth_getTransactionCount(
    _request: Request,
    address: web3.Data,
    blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity> {
    const blockNumber_ = parseBlockSpec(blockNumber);
    const address_ = parseAddress(address);
    this._enforceBans(address_, 'eth_getTransactionCount');
    const nonce = (
      await this.engine.getNonce(address_, {
        block: Number.isInteger(blockNumber_)
          ? (blockNumber_ as number) + 1
          : undefined,
      })
    ).unwrap();
    return intToHex(nonce);
  }

  async eth_getTransactionReceipt(
    _request: Request,
    transactionHash: string
  ): Promise<web3.TransactionReceipt | null> {
    const transactionHash_ = hexToBytes(transactionHash);
    try {
      const {
        rows: [receipt],
      } = await this._query(
        'SELECT * FROM eth_getTransactionReceipt($1) LIMIT 1',
        [transactionHash_]
      );
      //assert(receipt, 'receipt is not null');
      receipt.logs = await this._fetchEvents(transactionHash_);
      return exportJSON(receipt);
    } catch (error) {
      if (this.config.debug) {
        console.debug('eth_getTransactionReceipt', error);
      }
      return null;
    }
  }

  async eth_getUncleCountByBlockHash(
    _request: Request,
    blockHash: web3.Data
  ): Promise<web3.Quantity | null> {
    const blockHash_ = blockHash.startsWith('0x')
      ? hexToBytes(blockHash)
      : blockHash;
    const {
      rows: [{ result }],
    } = await this._query('SELECT eth_getUncleCountByBlockHash($1) AS result', [
      blockHash_,
    ]);
    return result !== null ? intToHex(result) : null;
  }

  async eth_getUncleCountByBlockNumber(
    _request: Request,
    blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity | null> {
    const blockNumber_ =
      parseBlockSpec(blockNumber) != 0
        ? parseBlockSpec(blockNumber) || (await this._fetchCurrentBlockID())
        : 0;
    const {
      rows: [{ result }],
    } = await this._query(
      'SELECT eth_getUncleCountByBlockNumber($1) AS result',
      [blockNumber_]
    );
    return result !== null ? intToHex(result) : null;
  }

  async eth_newBlockFilter(_request: Request): Promise<web3.Quantity> {
    const {
      rows: [{ id }],
    } = await this._query('SELECT eth_newBlockFilter($1::inet) AS id', [
      '0.0.0.0', // TODO: IPv4
    ]);

    return intToHex(id.toString('hex'));
  }

  async eth_newFilter(
    _request: Request,
    filter: web3.FilterOptions
  ): Promise<web3.Quantity> {
    const fromBlock = parseBlockSpec(filter.fromBlock);
    const toBlock = parseBlockSpec(filter.toBlock);

    const addresses =
      !filter.address || !filter.address.length
        ? null
        : parseAddresses(filter.address).map((address) =>
            Buffer.from(address.toBytes())
          );
    if (addresses && addresses.length > 10) {
      // TODO: DoS
      throw new InvalidArguments();
    }

    const topics = filter.topics || null;
    if (topics && topics.length > 4) {
      throw new InvalidArguments();
    }

    const {
      rows: [{ id }],
    } = await this._query(
      'SELECT eth_newFilter($1::inet, $2::blockno, $3::blockno, $4::address[], $5::jsonb) AS id',
      [
        '0.0.0.0', // TODO: IPv4
        fromBlock,
        toBlock,
        addresses,
        topics ? JSON.stringify(topics) : null,
      ]
    );
    return intToHex(id.toString('hex'));
  }

  async eth_newPendingTransactionFilter(
    _request: Request
  ): Promise<web3.Quantity> {
    const {
      rows: [{ id }],
    } = await this._query('SELECT eth_newPendingTransactionFilter() AS id');
    return bytesToHex(id);
  }

  async eth_sendRawTransaction(
    request: any,
    transaction: web3.Data
  ): Promise<web3.Data> {
    if (!this.config.writable) {
      throw new UnsupportedMethod('eth_sendRawTransaction');
    }

    const ip = request.ip();
    const transactionBytes = Buffer.from(hexToBytes(transaction));
    const transactionHash = keccak256(transactionBytes);

    // Enforce the EOA blacklist:
    let rawTransaction: Transaction | undefined;
    try {
      rawTransaction = parseRawTransaction(transactionBytes);
      // eslint-disable-next-line no-empty
    } catch (error) {}
    if (rawTransaction?.from) {
      const sender = Address.parse(rawTransaction.from).unwrap();
      if (this._isBannedEOA(sender)) {
        this._banIP(ip, sender.toString());
        throw new UnsupportedMethod('eth_sendRawTransaction');
      }
    }

    // Enforce the CA blacklist:
    let banReason;
    if ((banReason = this._scanForCABans(transaction))) {
      this._banIP(ip, banReason);
      throw new UnsupportedMethod('eth_sendRawTransaction');
    }

    const gasPrice = rawTransaction?.gasPrice || 0;
    const minGasPrice =
      this.config.minGasPrice !== undefined ? this.config.minGasPrice : 0;
    if (gasPrice < minGasPrice) {
      throw new GasPriceTooLow();
    }

    return (await this.engine.submit(transactionBytes)).match({
      ok: (wrappedSubmitResult) => {
        const result = wrappedSubmitResult.submitResult;
        // Check if an error occurred
        if (result.output().isErr()) {
          if (result.result.kind === 'LegacyExecutionResult') {
            // legacy SubmitResult just put any error message in the output bytes
            throw new RevertError(result.result.output);
          } else {
            // new versions of SubmitResult carry error information in the status
            if (result.result.status.revert) {
              throw new RevertError(
                result.result.status.revert.output as Uint8Array
              );
            } else if (result.result.status.outOfFund) {
              throw new TransactionError('Out Of Fund');
            } else if (result.result.status.outOfGas) {
              throw new TransactionError('Out Of Gas');
            } else if (result.result.status.outOfOffset) {
              throw new TransactionError('Out Of Offset');
            } else if (result.result.status.callTooDeep) {
              throw new TransactionError('Call Too Deep');
            }
          }
        }
        return wrappedSubmitResult.gasBurned || wrappedSubmitResult.tx
          ? `${bytesToHex(transactionHash)}|${JSON.stringify({
              gasBurned: wrappedSubmitResult.gasBurned,
              tx: wrappedSubmitResult.tx,
            })}`
          : bytesToHex(transactionHash);
      },
      err: (message) => {
        const sepIndex = message.lastIndexOf('|');
        const code = sepIndex > -1 ? message.substring(0, sepIndex) : message;
        if (this.config.errorLog && !code.includes('<html>')) {
          const country = request.country();
          fs.appendFileSync(
            this.config.errorLog,
            `${ip}\t${country}\t${code}\n`
          );
        }
        switch (code) {
          case 'ERR_INTRINSIC_GAS':
            throw new TransactionError('intrinsic gas too low');
          case 'ERR_INCORRECT_NONCE':
          case 'ERR_TX_RLP_DECODE':
          case 'ERR_UNKNOWN_TX_TYPE':
            if (this.bus) {
              this.bus.publishError('eth_sendRawTransaction', ip, code);
            }
            throw new TransactionError(message);
          case 'ERR_MAX_GAS': // TODO
          case 'Exceeded the maximum amount of gas allowed to burn per contract.':
            if (this.bus) {
              this.bus.publishError(
                'eth_sendRawTransaction',
                ip,
                'ERR_MAX_GAS'
              );
            }
            if (!request.hasAuthorization()) {
              this._banIP(ip, 'ERR_MAX_GAS'); // temporarily heavy ban hammer
            }
            throw new TransactionError(message);
          default: {
            if (!code.startsWith('ERR_')) {
              throw new UnexpectedError(message);
            }
            throw new TransactionError(message);
          }
        }
      },
    });
  }

  async eth_subscribe(
    _request: Request,
    _subsciptionType: web3.Data,
    _filter: any
  ): Promise<web3.Data> {
    // Skip unsupported subs
    const id = bytesToHex(getRandomBytesSync(16));
    const filter: any = {};
    if (_filter !== null && _filter !== undefined) {
      if (_filter.address !== undefined && _filter.address !== null) {
        try {
          filter.address = parseAddresses(_filter.address);
        } catch (error) {
          throw new InvalidAddress();
        }
      }
      if (_filter.topics !== undefined && _filter.topics !== null) {
        filter.topics = _filter.topics;
      }
    }

    const query = sql.insert('subscription', {
      id: id,
      sec_websocket_key: _request.websocketKey(),
      type: _subsciptionType,
      ip: _request.ip(),
      filter: JSON.stringify(filter),
    });
    await this._query(
      `${query.toString()} ON CONFLICT (sec_websocket_key, type, filter) DO UPDATE SET id = EXCLUDED.id `
    );
    return id;
  }

  async eth_uninstallFilter(
    _request: Request,
    filterID: web3.Quantity
  ): Promise<boolean> {
    const filterID_ = parseFilterID(filterID);
    if (filterID_.every((b) => b === 0)) {
      return true;
    }
    const {
      rows: [{ found }],
    } = await this._query(
      'SELECT eth_uninstallFilter($1::inet, $2::bytea) AS found',
      ['0.0.0.0', filterID_] // TODO: IPv4
    );
    return found;
  }

  async eth_unsubscribe(
    _request: Request,
    _subsciptionId: web3.Data
  ): Promise<boolean> {
    const query = sql.delete('subscription').where({ id: _subsciptionId });
    await this._query(query.toString());
    return true;
  }

  protected async _updatePollBlock(filterID_: Buffer): Promise<void> {
    const latestBlock = await this._fetchCurrentBlockID();
    await this._query(
      'UPDATE filter SET poll_block = $1 + 1 WHERE uuid_send(id) = $2',
      [latestBlock, filterID_]
    );
  }

  protected async _getFilterChangesEvent(
    filter: {
      addresses?: string;
      poll_block: web3.Quantity;
      to_block?: web3.Quantity;
      topics?: web3.FilterTopic[];
      type: 'block' | 'event' | 'transaction';
    },
    filterID_: Buffer
  ): Promise<web3.LogObject[]> {
    const filterOptions: web3.FilterOptions = {
      fromBlock: filter.poll_block,
      toBlock: filter.to_block,
      address: filter.addresses
        ? parse(filter.addresses, (address) => {
            return address.replace('\\x', '0x');
          })
        : undefined,
      topics: filter.topics,
    };

    const result = await this._getLogs(filterOptions);
    await this._updatePollBlock(filterID_);
    return result;
  }

  protected async _fetchCurrentBlockID(): Promise<bigint> {
    const {
      rows: [{ result }],
    } = await this._query('SELECT eth_blockNumber() AS result');
    return result;
  }

  protected async _fetchEvents(transactionID: Uint8Array): Promise<unknown[]> {
    const { rows } = await this._query(
      `SELECT
          b.id AS "blockNumber",
          b.hash AS "blockHash",
          t.index AS "transactionIndex",
          t.hash AS "transactionHash",
          e.index AS "logIndex",
          e.from AS "address",
          ARRAY_TO_STRING(e.topics, ';') AS "topics",
          coalesce(e.data, repeat('\\000', 32)::bytea) AS "data",
          false AS "removed"
        FROM event e
          LEFT JOIN transaction t ON e.transaction = t.id
          LEFT JOIN block b ON t.block = b.id
        WHERE t.hash = $1
        ORDER BY b.id ASC, t.index ASC, e.index ASC`,
      [transactionID]
    );
    return rows.map((row: Record<string, unknown>) => {
      row['topics'] =
        row['topics'] === null
          ? []
          : (row['topics'] as string)
              .split(';')
              .map((topic) => topic.replace('\\x', '0x'));
      return row;
    });
  }

  protected async _fetchTransactions(
    blockID: bigint | number | Uint8Array,
    fullObject: boolean
  ): Promise<unknown[] | string[]> {
    const idColumn = ['bigint', 'number'].includes(typeof blockID)
      ? 'id'
      : 'hash';
    if (fullObject) {
      const { rows } = await this._query(
        `SELECT
            b.id AS "blockNumber",
            b.hash AS "blockHash",
            t.index AS "transactionIndex",
            t.hash AS "hash",
            t.from AS "from",
            t.to AS "to",
            t.gas_limit AS "gas",
            t.gas_price AS "gasPrice",
            t.nonce AS "nonce",
            t.value AS "value",
            coalesce(t.input, '\\x'::bytea) AS "input",
            t.v AS "v",
            t.r AS "r",
            t.s AS "s"
          FROM transaction t
            LEFT JOIN block b ON t.block = b.id
          WHERE b.${idColumn} = $1
          ORDER BY t.index ASC`,
        [blockID]
      );
      return rows;
    } else {
      const { rows } = await this._query(
        `SELECT t.hash FROM transaction t LEFT JOIN block b ON t.block = b.id
          WHERE b.${idColumn} = $1 ORDER BY t.index ASC`,
        [blockID]
      );
      return rows.map((row: Record<string, unknown>) =>
        bytesToHex(row['hash'] as Uint8Array)
      );
    }
  }

  protected async _getLogs(
    filter: web3.FilterOptions
  ): Promise<web3.LogObject[]> {
    const where = [];
    if (filter == undefined) {
      filter = {};
    }
    if (filter.blockHash !== undefined && filter.blockHash !== null) {
      // EIP-234
      where.push({ 'b.hash': hexToBytes(filter.blockHash) });
    } else {
      const fromBlock = parseBlockSpec(filter.fromBlock);
      if (fromBlock !== null) {
        where.push(sql.gte('b.id', fromBlock));
      }
      const toBlock = parseBlockSpec(filter.toBlock);
      if (toBlock !== null) {
        where.push(sql.lte('b.id', toBlock));
      }
    }
    if (filter.address) {
      const addresses = parseAddresses(filter.address).map((address) =>
        Buffer.from(address.toBytes())
      ); // TODO: handle 0x0 => NULL
      if (addresses.length > 0) {
        where.push(sql.in('e.from', addresses));
      }
    }
    if (filter.topics) {
      const clauses = compileTopics(filter.topics);
      if (clauses) {
        where.push(clauses);
      }
    }

    let query = sql
      .select(
        'b.id AS "blockNumber"',
        'b.hash AS "blockHash"',
        't.index AS "transactionIndex"',
        't.hash AS "transactionHash"',
        'e.index AS "logIndex"',
        'e.from AS "address"',
        "string_to_array(concat('0x',encode(e.topics[1], 'hex'), ',', '0x', encode(e.topics[2], 'hex'), ',', '0x', encode(e.topics[3], 'hex'), ',', '0x', encode(e.topics[4], 'hex')), ',') AS \"topics\"",
        'coalesce(e.data, repeat(\'\\000\', 32)::bytea) AS "data"',
        '0::boolean AS "removed"'
      )
      .from('event e')
      .join('transaction t', { 'e.transaction': 't.id' })
      .join('block b', { 't.block': 'b.id' })
      .orderBy('b.id ASC');

    if (this.config.getLogsLimit) {
      query = query.limit(this.config.getLogsLimit + 1);
    }

    if (where.length > 0) {
      query = query.where(sql.and(...where));
    }

    if (this.config.debug) {
      console.debug('eth_getLogs', 'query:', query.toParams());
      console.debug('eth_getLogs', 'query:', query.toString());
    }
    const { rows } = await this._query(query);
    if (this.config.debug) {
      console.debug('eth_getLogs', 'result:', rows);
    }

    const events = rows.map((row: Record<string, unknown>) => {
      if (row['address'] === null) {
        row['address'] = Address.zero().toString();
      }
      // remove null values
      if (Array.isArray(row['topics'])) {
        row['topics'] = row['topics'].filter((t: string) => {
          return t !== '0x';
        });
      }
      return row;
    });

    if (this.config.getLogsLimit && events.length > this.config.getLogsLimit) {
      throw new LimitExceeded(this.config.getLogsLimit);
    }

    return exportJSON(events);
  }
}

function parseAddress(input?: web3.Data): Address {
  if (!input) throw new InvalidArguments();
  return Address.parse(input).unwrapOrElse((_) => {
    throw new InvalidArguments();
  });
}

function parseAddresses(inputs: web3.Data | web3.Data[]): Address[] {
  return (Array.isArray(inputs) ? inputs : [inputs]).map(parseAddress);
}

function parseBlockSpec(
  blockSpec?: web3.Quantity | web3.Tag | null
): BlockID | null {
  switch (blockSpec) {
    case undefined:
      return null;
    case null:
      return null;
    case 'pending':
      return null;
    case 'latest':
      return null;
    case 'earliest':
      return 0;
    default: {
      const blockID = parseInt(blockSpec);
      if (isNaN(blockID)) {
        throw new InvalidArguments();
      }
      return blockID;
    }
  }
}

function parseFilterID(input: web3.Quantity): Buffer {
  if (input === '0x0') {
    return Buffer.alloc(16);
  }
  const bytes = hexToBytes(input);
  if (bytes.length != 16) {
    throw new InvalidArguments();
  }
  return Buffer.from(bytes);
}


text:
/* This is free and unencumbered software released into the public domain. */

import * as web3 from '../web3.js';
import { Config } from '../config.js';
import { unimplemented, unsupported } from '../errors.js';
import { Request } from '../request.js';
import { blacklist } from '../blacklist.js';

import { Address, bytesToHex, Engine, intToHex } from '@aurora-is-near/engine';
import { keccakFromHexString } from 'ethereumjs-util';
import { Logger } from 'pino';

import { spawn } from 'child_process';

export abstract class SkeletonServer implements web3.Service {
  constructor(
    public readonly config: Config,
    public readonly logger: Logger,
    public readonly engine: Engine
  ) {
    this._init();
  }

  protected abstract _init(): Promise<void>;

  protected _isBannedEOA(address: Address): boolean {
    const key = address.toString().toLowerCase();
    return blacklist('EOAs').has(key);
  }

  protected _isBannedCA(address: Address): boolean {
    const key = address.toString().toLowerCase();
    return blacklist('CAs').has(key);
  }

  protected _enforceBans(address: Address, method: string): void {
    this._enforceEOABan(address, method);
    this._enforceCABan(address, method);
  }

  protected _enforceEOABan(address: Address, method: string): void {
    if (this._isBannedEOA(address)) {
      unsupported(method);
    }
  }

  protected _enforceCABan(address: Address, method: string): void {
    if (this._isBannedCA(address)) {
      unsupported(method);
    }
  }

  protected _scanForCABans(bytes: string): string | null {
    for (const [address, _] of blacklist('CAs').entries()) {
      const match = address.substring(2); // strip '0x' prefix
      if (bytes.includes(match)) {
        return address;
      }
    }
    return null;
  }

  protected async _banIP(ip: string, reason?: string): Promise<void> {
    if (!ip) return;
    blacklist('IPs').add(ip);
    if (
      process.env.CF_API_TOKEN &&
      process.env.CF_ACCOUNT_ID &&
      process.env.CF_LIST_ID
    ) {
      const subprocess = spawn(
        '/srv/aurora/relayer/util/ban/ban', // FIXME: don't use absolute path
        [ip, reason || ''],
        {
          shell: false,
          detached: true,
          stdio: 'ignore',
          timeout: 60 * 1000,
          env: process.env,
        }
      );
      subprocess.unref();
    }
  }

  // web3_*

  async web3_clientVersion(_request: Request): Promise<string> {
    return 'Aurora-Relayer/0.0.0'; // TODO
  }

  async web3_sha3(_request: Request, input: web3.Data): Promise<web3.Data> {
    return bytesToHex(keccakFromHexString(input));
  }

  // net_*

  async net_listening(_request: Request): Promise<boolean> {
    return true;
  }

  async net_peerCount(_request: Request): Promise<web3.Quantity> {
    return intToHex(0);
  }

  async net_version(_request: Request): Promise<string> {
    const netVersion = (await this.engine.getChainID()).unwrap();
    return netVersion.toString();
  }

  // eth_*

  async eth_accounts(_request: Request): Promise<web3.Data[]> {
    return []; // no private keys under management here
  }

  async eth_blockNumber(_request: Request): Promise<web3.Quantity> {
    unimplemented('eth_blockNumber');
    return intToHex(0);
  }

  async eth_call(
    _request: Request,
    _transaction: web3.TransactionForCall,
    _blockNumber?: web3.Quantity | web3.Tag
  ): Promise<web3.Data> {
    unimplemented('eth_call');
    return '0x';
  }

  async eth_chainId(_request: Request): Promise<web3.Quantity> {
    // EIP-695
    unimplemented('eth_chainId');
    return intToHex(0);
  }

  async eth_coinbase(_request: Request): Promise<web3.Data> {
    unimplemented('eth_coinbase');
    return '0x';
  }

  async eth_compileLLL(_request: Request, _code: string): Promise<web3.Data> {
    unsupported('eth_compileLLL');
    return '0x';
  }

  async eth_compileSerpent(
    _request: Request,
    _code: string
  ): Promise<web3.Data> {
    unsupported('eth_compileSerpent');
    return '0x';
  }

  async eth_compileSolidity(
    _request: Request,
    _code: string
  ): Promise<web3.Data> {
    unsupported('eth_compileSolidity');
    return '0x';
  }

  async eth_estimateGas(
    _request: Request,
    _transaction: web3.TransactionForCall,
    _blockNumber?: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity> {
    // See: https://www.trufflesuite.com/docs/truffle/reference/configuration
    return intToHex(6_721_975); // 0x6691b7
  }

  async eth_gasPrice(_request: Request): Promise<web3.Quantity> {
    unimplemented('eth_gasPrice');
    return '0x';
  }

  async eth_getBalance(
    _request: Request,
    _address: web3.Data,
    _blockNumber?: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity> {
    unimplemented('eth_getBalance');
    return intToHex(0);
  }

  async eth_getBlockByHash(
    _request: Request,
    _blockHash: web3.Data,
    _fullObject?: boolean
  ): Promise<web3.BlockResult | null> {
    unimplemented('eth_getBlockByHash');
    return null;
  }

  async eth_getBlockByNumber(
    _request: Request,
    _blockNumber: web3.Quantity | web3.Tag,
    _fullObject?: boolean
  ): Promise<web3.BlockResult | null> {
    unimplemented('eth_getBlockByNumber');
    return null;
  }

  async eth_getBlockTransactionCountByHash(
    _request: Request,
    _blockHash: web3.Data
  ): Promise<web3.Quantity | null> {
    unimplemented('eth_getBlockTransactionCountByHash');
    return null;
  }

  async eth_getBlockTransactionCountByNumber(
    _request: Request,
    _blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity | null> {
    unimplemented('eth_getBlockTransactionCountByNumber');
    return null;
  }

  async eth_getCode(
    _request: Request,
    _address: web3.Data,
    _blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Data> {
    unimplemented('eth_getCode');
    return '0x';
  }

  async eth_getCompilers(_request: Request): Promise<string[]> {
    return [];
  }

  async eth_getFilterChanges(
    _request: Request,
    _filterID: web3.Quantity
  ): Promise<web3.LogObject[]> {
    unimplemented('eth_getFilterChanges');
    return [];
  }

  async eth_getFilterLogs(
    _request: Request,
    _filterID: web3.Quantity
  ): Promise<web3.LogObject[]> {
    unimplemented('eth_getFilterLogs');
    return [];
  }

  async eth_getLogs(
    _request: Request,
    _filter: web3.FilterOptions
  ): Promise<web3.LogObject[]> {
    unimplemented('eth_getLogs');
    return [];
  }

  async eth_getProof(
    _request: Request,
    _address: web3.Data,
    _keys: web3.Data[],
    _blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.ProofResult> {
    // EIP-1186
    unsupported('eth_getProof'); // EIP-1186 TODO?
    return {};
  }

  async eth_getStorageAt(
    _request: Request,
    _address: web3.Data,
    _key: web3.Quantity,
    _blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Data> {
    unimplemented('eth_getStorageAt');
    return '0x';
  }

  async eth_getTransactionByBlockHashAndIndex(
    _request: Request,
    _blockHash: web3.Data,
    _transactionIndex: web3.Quantity
  ): Promise<web3.TransactionResult | null> {
    unimplemented('eth_getTransactionByBlockHashAndIndex');
    return null;
  }

  async eth_getTransactionByBlockNumberAndIndex(
    _request: Request,
    _blockNumber: web3.Quantity | web3.Tag,
    _transactionIndex: web3.Quantity
  ): Promise<web3.TransactionResult | null> {
    unimplemented('eth_getTransactionByBlockNumberAndIndex');
    return null;
  }

  async eth_getTransactionByHash(
    _request: Request,
    _transactionHash: web3.Data
  ): Promise<web3.TransactionResult | null> {
    unimplemented('eth_getTransactionByHash');
    return null;
  }

  async eth_getTransactionCount(
    _request: Request,
    _address: web3.Data,
    _blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity> {
    unimplemented('eth_getTransactionCount');
    return intToHex(0);
  }

  async eth_getTransactionReceipt(
    _request: Request,
    _transactionHash: string
  ): Promise<web3.TransactionReceipt | null> {
    unimplemented('eth_getTransactionReceipt');
    return null;
  }

  async eth_getUncleByBlockHashAndIndex(
    _request: Request,
    _blockHash: web3.Data,
    _uncleIndex: web3.Quantity
  ): Promise<web3.BlockResult | null> {
    return null; // uncle blocks are never found
  }

  async eth_getUncleByBlockNumberAndIndex(
    _request: Request,
    _blockNumber: web3.Quantity | web3.Tag,
    _uncleIndex: web3.Quantity
  ): Promise<web3.BlockResult | null> {
    return null; // uncle blocks are never found
  }

  async eth_getUncleCountByBlockHash(
    _request: Request,
    _blockHash: web3.Data
  ): Promise<web3.Quantity | null> {
    unimplemented('eth_getUncleCountByBlockHash');
    return null;
  }

  async eth_getUncleCountByBlockNumber(
    _request: Request,
    _blockNumber: web3.Quantity | web3.Tag
  ): Promise<web3.Quantity | null> {
    unimplemented('eth_getUncleCountByBlockNumber');
    return null;
  }

  async eth_getWork(_request: Request): Promise<web3.Data[]> {
    unsupported('eth_getWork');
    return [];
  }

  async eth_hashrate(_request: Request): Promise<web3.Quantity> {
    return intToHex(0);
  }

  async eth_mining(_request: Request): Promise<false> {
    return false;
  }

  async eth_newBlockFilter(_request: Request): Promise<web3.Quantity> {
    unimplemented('eth_newBlockFilter');
    return intToHex(0);
  }

  async eth_newFilter(
    _request: Request,
    _filter: web3.FilterOptions
  ): Promise<web3.Quantity> {
    unimplemented('eth_newFilter');
    return intToHex(0);
  }

  async eth_newPendingTransactionFilter(
    _request: Request
  ): Promise<web3.Quantity> {
    return intToHex(0); // designates the empty filter
  }

  async eth_pendingTransactions(
    _request: Request
  ): Promise<Record<string, string | number | null>[]> {
    // undocumented
    return [];
  }

  async eth_protocolVersion(_request: Request): Promise<string> {
    return intToHex(0x41);
  }

  async eth_sendRawTransaction(
    _request: Request,
    _transaction: web3.Data
  ): Promise<web3.Data> {
    unimplemented('eth_sendRawTransaction');
    return '0x';
  }

  async eth_sendTransaction(
    _request: Request,
    _transaction: web3.TransactionForSend
  ): Promise<web3.Data> {
    unsupported('eth_sendTransaction');
    return '0x';
  }

  async eth_sign(
    _request: Request,
    _account: web3.Data,
    _message: web3.Data
  ): Promise<web3.Data> {
    unsupported('eth_sign'); // no private keys under management here
    return '0x';
  }

  async eth_signTransaction(
    _request: Request,
    _transaction: web3.TransactionForSend
  ): Promise<web3.Data> {
    unsupported('eth_signTransaction'); // no private keys under management here
    return '0x';
  }

  async eth_signTypedData(
    _request: Request,
    _address: web3.Data,
    _data: web3.TypedData
  ): Promise<web3.Data> {
    // EIP-712
    unsupported('eth_signTypedData'); // no private keys under management here
    return '0x';
  }

  async eth_submitHashrate(
    _request: Request,
    _hashrate: web3.Quantity,
    _clientID: web3.Quantity
  ): Promise<false> {
    unsupported('eth_submitHashrate');
    return false;
  }

  async eth_submitWork(
    _request: Request,
    _nonce: web3.Data,
    _powHash: web3.Data,
    _mixDigest: web3.Data
  ): Promise<false> {
    unsupported('eth_submitWork');
    return false;
  }

  async eth_subscribe(
    _request: Request,
    _subsciptionType: web3.Data,
    _filter: any
  ): Promise<web3.Data> {
    unimplemented('eth_subscribe');
    return '0x';
  }

  async eth_syncing(_request: Request): Promise<false> {
    return false;
  }

  async eth_uninstallFilter(
    _request: Request,
    _filterID: web3.Quantity
  ): Promise<boolean> {
    unimplemented('eth_uninstallFilter');
    return false;
  }

  async eth_unsubscribe(
    _request: Request,
    _subsciptionId: web3.Data
  ): Promise<boolean> {
    unimplemented('eth_unsubscribe');
    return false;
  }

  // @see {@link https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content}
  async txpool_content(_request: Request): Promise<Record<string, any>> {
    return { pending: {}, queued: {} };
  }

  // @see {@link https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect}
  async txpool_inspect(_request: Request): Promise<Record<string, any>> {
    return { pending: {}, queued: {} };
  }

  // @see {@link https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status}
  async txpool_status(_request: Request): Promise<Record<string, number>> {
    return { pending: 0, queued: 0 };
  }

  // @see {@link https://openethereum.github.io/JSONRPC-parity-module#parity_pendingtransactions}
  async parity_pendingTransactions(
    _request: Request,
    _limit?: number | null,
    _filter?: Record<string, any>
  ): Promise<any[]> {
    return [];
  }
}


text:
import { Config } from './config.js';
import * as nats from 'nats';
export declare class Bus {
    readonly config: Config;
    protected broker?: nats.NatsConnection;
    constructor(config: Config);
    protected _init(): Promise<void>;
    publishError(method: string, ip: string, code?: string): Promise<void>;
}


text:
import { Config } from './config.js';
import { Engine } from '@aurora-is-near/engine';
import { Logger } from 'pino';
export declare function createWsServer(config: Config, logger: Logger, engine: Engine, app: any): Promise<boolean>;


text:
/* This is free and unencumbered software released into the public domain. */
import { parseConfig } from './config.js';
import { pg, sql } from './database.js';
import { computeBlockHash } from './utils.js';
import { AccountID, base58ToBytes, } from '@aurora-is-near/engine';
import { program } from 'commander';
import externalConfig from 'config';
import pino from 'pino';
const logger = pino(pino.destination(2));
export class PrehistoryIndexer {
    constructor(config, network, logger) {
        this.config = config;
        this.network = network;
        this.logger = logger;
        this.blockID = 0;
        if (!network.archiveURL) {
            console.error(`No archive database for the network ${network.id}`);
        }
        this.archive = new pg.Client(network.archiveURL);
    }
    async run(startBlockID, mode) {
        const contractID = AccountID.parse(this.config.engine).unwrap();
        await this.archive.connect();
        const step = Number(this.config.batchSize || 1000);
        for (let blockID = startBlockID; blockID < this.network.firstBlock; blockID += step) {
            const query = sql
                .select()
                .from('blocks')
                .where(sql(`block_height >= ${blockID} AND block_height < ${blockID + step}`))
                .limit(step);
            //console.debug(query.toString()); // DEBUG
            const result = await this.archive.query(query.toParams());
            const rows = {};
            for (const row of result.rows) {
                rows[Number(row.block_height)] = row;
            }
            for (let i = 0; i < step; i++) {
                const blockID_ = blockID + i;
                if (blockID_ >= this.network.firstBlock)
                    break;
                const blockHash = computeBlockHash(blockID_, contractID.toString(), this.network.chainID);
                const parentHash = blockID_ == 0
                    ? Buffer.alloc(32)
                    : computeBlockHash(blockID_ - 1, contractID.toString(), this.network.chainID);
                const row = rows[blockID_];
                const columns = {
                    chain: this.network.chainID,
                    id: blockID_,
                    hash: blockHash,
                    near_hash: row ? base58ToBytes(row.block_hash) : null,
                    timestamp: blockID_ == 0
                        ? new Date(this.network.genesisDate)
                        : row
                            ? new Date(Number(row.block_timestamp) / 1000000)
                            : null,
                    size: 0,
                    gas_limit: 0,
                    gas_used: 0,
                    parent_hash: parentHash,
                    transactions_root: Buffer.alloc(32),
                    state_root: Buffer.alloc(32),
                    receipts_root: Buffer.alloc(32),
                };
                console.log(this.serialize(columns));
            }
        } // for
        process.exit(0); // EX_OK
    }
    serialize(query) {
        const items = Object.keys(query).map((key) => {
            const value = query[key];
            if (value === null) {
                return '\\N';
            }
            if (value instanceof Date) {
                return value.toISOString();
            }
            if (value instanceof Uint8Array) {
                return `\\\\x${Buffer.from(value).toString('hex')}`;
            }
            return value;
        });
        return items.join('\t');
    }
}
async function main(argv, env) {
    program
        .option('-d, --debug', 'enable debug output')
        .option('-v, --verbose', 'enable verbose output')
        .option('--network <network>', `specify NEAR network ID (default: "${env.NEAR_ENV || 'local'}")`)
        .option('--engine <account>', `specify Aurora Engine account ID (default: "${env.AURORA_ENGINE || 'aurora.test.near'}")`)
        .option('-B, --block <block>', `specify block height to begin indexing from (default: 0)`)
        .option('--batch-size <batchSize>', `specify batch size for fetching block metadata (default: 1000)`)
        .parse(argv);
    const opts = program.opts();
    const [network, config] = parseConfig(opts, externalConfig, env);
    const blockID = opts.block !== undefined ? parseInt(opts.block) : 0;
    if (config.debug) {
        for (const source of externalConfig.util.getConfigSources()) {
            console.error(`Loaded configuration file ${source.name}.`);
        }
        console.error('Configuration:', config);
    }
    logger.info('starting prehistory indexer');
    const indexer = new PrehistoryIndexer(config, network, logger);
    await indexer.run(blockID, 'follow');
}
main(process.argv, process.env).catch((error) => {
    const errorMessage = error.message.startsWith('<')
        ? error.name
        : error.message;
    logger.error(errorMessage);
    process.exit(70); // EX_SOFTWARE
});


text:
/* This is free and unencumbered software released into the public domain. */
import { sha256 } from 'ethereum-cryptography/sha256.js';
import * as ethers from 'ethers';
import { keccak256 } from 'ethereumjs-util';
import * as rlp from 'rlp';
export function computeBlockHash(blockHeight, accountId, chainId) {
    return sha256(generateBlockPreImage(blockHeight, accountId, chainId));
}
function generateBlockPreImage(blockHeight, accountId, chainId) {
    const blockHeightBuf = Buffer.alloc(4);
    blockHeightBuf.writeInt32BE(blockHeight, 0);
    const chainIdBuf = Buffer.alloc(4);
    chainIdBuf.writeInt32BE(chainId, 0);
    return Buffer.concat([
        Buffer.alloc(29),
        chainIdBuf,
        Buffer.from(accountId, 'utf8'),
        Buffer.alloc(4),
        blockHeightBuf,
    ]);
}
export function generateEmptyBlock(blockHeight, accountId, chainId) {
    const hash = computeBlockHash(blockHeight, accountId, chainId);
    const parentHash = computeBlockHash(blockHeight - 1, accountId, chainId);
    return {
        chain: chainId,
        id: blockHeight,
        hash: hash,
        nearHash: null,
        timestamp: null,
        size: 0,
        gasLimit: 0,
        gasUsed: 0,
        parentHash: parentHash,
        transactionsRoot: emptyTransactionsRoot(),
        stateRoot: Buffer.alloc(32),
        receiptsRoot: Buffer.alloc(32),
    };
}
export function emptyTransactionsRoot() {
    return keccak256(rlp.encode(''));
}
export function parseEVMRevertReason(reason) {
    if (reason.length > 0) {
        // only for valid decoded revert reason
        const coder = new ethers.utils.AbiCoder();
        try {
            return coder.decode(['string'], reason.slice(4)).toString();
        }
        catch {
            return reason;
        }
    }
    return reason;
}


text:
/* This is free and unencumbered software released into the public domain. */
import middleware from './middleware.js';
import { createServer } from './server.js';
import { createWsServer } from './ws_server.js';
import bodyParser from 'body-parser';
import cors from 'cors';
import express from 'express';
import { parse as parseRawTransaction } from '@ethersproject/transactions';
export async function createApp(config, logger, engine) {
    const app = express();
    app.disable('x-powered-by');
    app.use(middleware.setRequestID());
    app.use(middleware.blacklistIPs());
    app.use(bodyParser.json({ type: 'application/json' }));
    app.use(middleware.logger(logger));
    app.use(cors()); // Access-Control-Allow-Origin: *
    app.get('/health', (req, res) => {
        res.send('OK');
    });
    app.get('/metrics', (req, res) => {
        res.send(''); // TODO
    });
    app.use(rpcMiddleware(createServer(config, logger, engine)));
    app.use(middleware.handleErrors());
    await createWsServer(config, logger, engine, app);
    return app;
}
const sendRawTransactionMetaData = ({ code, details, transactionResult, errorExists, }) => {
    const metadata = {};
    if (details.gasBurned) {
        metadata.neargas = details.gasBurned;
    }
    if (details.tx) {
        metadata.tx = details.tx;
    }
    if (transactionResult) {
        metadata.result = transactionResult;
    }
    if (errorExists && code) {
        metadata.error = code;
    }
    return metadata;
};
const handleResponse = ({ body, res, req, response, options, sendRawTransactionExists, }) => {
    if (!body) {
        res.writeHead(204);
    }
    else {
        const headers = {
            'Content-Type': 'application/json; charset=utf-8',
        };
        if (sendRawTransactionExists &&
            Array.isArray(req?.body) &&
            Array.isArray(response)) {
            const processResultHeader = response.reduce((acc, transaction) => {
                const transactionRequest = req.body.find((transactionRequest) => transactionRequest.id === transaction.id);
                let metadata = {};
                if (transactionRequest.method === 'eth_sendRawTransaction') {
                    const { code, details } = parseTransactionDetails(transaction?.error?.message || transaction?.result);
                    if (typeof transaction?.error?.message === 'string') {
                        if (code) {
                            transaction.error.message = code;
                        }
                    }
                    else if (transaction?.result) {
                        transaction.result = code;
                    }
                    metadata = sendRawTransactionMetaData({
                        code,
                        details,
                        transactionResult: transaction?.result && code,
                        errorExists: typeof transaction?.error?.message === 'string',
                    });
                }
                acc.push(metadata);
                return acc;
            }, []);
            headers['X-Aurora-Process-Result'] = JSON.stringify(processResultHeader);
            body = JSON.stringify(response);
        }
        if (req?.body?.method == 'eth_sendRawTransaction') {
            const { code, details } = parseTransactionDetails(response?.error?.message || response?.result);
            const metadata = sendRawTransactionMetaData({
                code,
                details,
                transactionResult: response?.result && code,
                errorExists: typeof response?.error?.message === 'string',
            });
            headers['X-Aurora-Process-Result'] = JSON.stringify([metadata]);
            if (typeof response?.error?.message === 'string') {
                if (code) {
                    response.error.message = code;
                }
                body = JSON.stringify(response);
            }
            else if (response?.result) {
                response.result = code;
                body = JSON.stringify(response);
            }
        }
        headers['Content-Length'] = Buffer.byteLength(body, options.encoding);
        res.writeHead(200, headers);
        res.write(body);
    }
    res.end();
};
function parseErrorDetails(details) {
    try {
        return JSON.parse(details);
    }
    catch (e) {
        return {};
    }
}
function parseTransactionDetails(message) {
    if (message === undefined) {
        return { code: message, details: {} };
    }
    let code = message;
    const sepIndex = code.lastIndexOf('|');
    if (sepIndex > -1) {
        code = message.substring(0, sepIndex);
        const details = parseErrorDetails(message.substring(sepIndex + 1));
        return { code: code, details: details };
    }
    else {
        return { code: code, details: {} };
    }
}
function rpcMiddleware(server) {
    return async function (req, res, next) {
        const options = server.options;
        if (req.headers['sec-websocket-key']) {
            return next();
        }
        if ((req.method || '') != 'POST') {
            return error(405, { Allow: 'POST' });
        }
        if (!RegExp('application/json', 'i').test((req || { headers: {} }).headers['content-type'] || '')) {
            return error(415);
        }
        const findRawTx = (element) => element.method == 'eth_sendRawTransaction';
        if (Array.isArray(req.body) && req.body.some(findRawTx)) {
            const parsedPayloads = req.body.map((element, index) => {
                return {
                    ...element,
                    aurora_req_position: index,
                    parsed: element.method === 'eth_sendRawTransaction' &&
                        parseRawTransaction(element.params[0]),
                };
            });
            parsedPayloads.sort((a, b) => a.parsed.nonce > b.parsed.nonce ? 1 : -1);
            const executedPayloads = await parsedPayloads.reduce(async (acc, element) => {
                const collection = await acc;
                const promise = new Promise((resolve) => {
                    server.call(element, req, function (error, success) {
                        const response = success || error;
                        response.aurora_req_position = element.aurora_req_position;
                        resolve(response);
                    });
                });
                const result = await promise;
                collection.push(result);
                return collection;
            }, Promise.resolve([]));
            executedPayloads.sort((a, b) => a.aurora_req_position > b.aurora_req_position ? 1 : -1);
            executedPayloads.forEach((payload) => {
                delete payload.aurora_req_position;
            });
            const body = JSON.stringify(executedPayloads);
            handleResponse({
                body,
                res,
                req,
                response: executedPayloads,
                options,
                sendRawTransactionExists: true,
            });
        }
        else {
            server.call(req.body, req, function (error, success) {
                const response = error || success;
                const body = JSON.stringify(response);
                handleResponse({
                    body,
                    res,
                    req,
                    response,
                    options,
                });
            });
        }
        function error(code, headers) {
            res.writeHead(code, headers || {});
            res.end();
        }
    };
}
// function response(id: string, result: any, error: any) {
//   const resp = { jsonrpc: '2.0', id };
//   if (error) {
//     Object.assign(resp, { error });
//   } else {
//     Object.assign(resp, { result });
//   }
//   return resp;
// }


text:
/* This is free and unencumbered software released into the public domain. */
import { NETWORKS } from '@aurora-is-near/engine';
export const localConfig = {
    debug: false,
    verbose: false,
    force: false,
    database: undefined,
    broker: undefined,
    port: 8545,
    network: 'local',
    endpoint: 'http://127.0.0.1:3030',
    engine: 'aurora.test.near',
    signer: 'test.near',
    signerKey: undefined,
    signerKeys: [],
    writable: true,
    errorLog: undefined,
    minGasPrice: undefined,
    getLogsLimit: 10000,
};
export function parseConfig(options, config, env) {
    const networkID = options.network || env.NEAR_ENV || config.network;
    if (!networkID) {
        throw new Error(`Missing network ID. Use: --network <network>`);
    }
    const network = NETWORKS.get(networkID);
    if (!network) {
        throw new Error(`Unknown network ID: '${networkID}'`);
    }
    const debug = options.debug || config.debug;
    return [
        network,
        {
            debug: debug,
            verbose: debug || options.verbose || config.verbose,
            force: options.force || config.force,
            database: options.database || config.database,
            broker: options.broker || config.broker,
            port: parseInt(options.port || config.port),
            network: networkID,
            endpoint: options.endpoint ||
                env.NEAR_URL ||
                config.endpoint ||
                network.nearEndpoint,
            engine: options.engine ||
                env.AURORA_ENGINE ||
                config.engine ||
                network.contractID,
            signer: options.signer || env.NEAR_MASTER_ACCOUNT || config.signer,
            signerKey: options.signerKey || config.signerKey,
            signerKeys: config.signerKeys || [],
            batchSize: parseInt(options.batchSize || config.batchSize || '1000'),
            writable: config.writable !== undefined ? config.writable : true,
            errorLog: config.errorLog,
            minGasPrice: config.minGasPrice !== undefined ? config.minGasPrice : 0,
            getLogsLimit: options.getLogsLimit || config.getLogsLimit || 10000,
        },
    ];
}


text:
/* This is free and unencumbered software released into the public domain. */
import { SkeletonServer } from './servers/skeleton.js';
import { DatabaseServer } from './servers/database.js';
import { Request } from './request.js';
import { ExpectedError } from './errors.js';
import { bytesToHex } from '@aurora-is-near/engine';
import jayson from 'jayson';
export class Method extends jayson.Method {
    constructor(handler, options) {
        super(handler, {});
        this.handler = handler;
        this.server = options?.useContext; // HACK
    }
    getHandler() {
        return this.handler;
    }
    setHandler(handler) {
        this.handler = handler;
    }
    execute(server, requestParams, request, // context
    callback) {
        const args = (requestParams || []);
        const result = this.handler.call(this.server, new Request(request), ...args);
        result
            .then((value) => callback(undefined, value))
            .catch((error) => {
            const metadata = {
                host: request?.headers?.host || undefined,
                'cf-ray': request?.headers?.['cf-ray'] || undefined,
                'cf-request-id': request?.headers?.['cf-request-id'] || undefined,
            };
            if (error instanceof ExpectedError) {
                return callback(server.error(error.code, error.message, error.data
                    ? bytesToHex(error.data)
                    : {
                        ...metadata,
                        ...{ request_body: request.body },
                    }));
            }
            if (this.server?.config?.debug) {
                console.error(error);
            }
            if (this.server?.logger) {
                this.server.logger.error(error);
            }
            return callback(server.error(-32603, `Internal Error - ${error.message}. Please report a bug at <https://github.com/aurora-is-near/aurora-relayer/issues>`, {
                ...metadata,
                ...{ request_body: request.body },
            }));
        });
        return null;
    }
}
export function createServer(config, logger, engine) {
    const server = new DatabaseServer(config, logger, engine);
    const methodList = Object.getOwnPropertyNames(SkeletonServer.prototype)
        .filter((id) => id !== 'constructor' && id[0] != '_')
        .map((id) => [id, server[id]]);
    const methodMap = Object.fromEntries(methodList);
    const jaysonServer = new jayson.Server(methodMap, {
        methodConstructor: Method,
        useContext: server,
    });
    return jaysonServer;
}


text:
/* This is free and unencumbered software released into the public domain. */
export {};


text:
/* This is free and unencumbered software released into the public domain. */
import * as nats from 'nats';
const codec = nats.JSONCodec();
export class Bus {
    constructor(config) {
        this.config = config;
        this._init();
    }
    async _init() {
        this.broker = await nats.connect({ servers: this.config.broker });
    }
    async publishError(method, ip, code) {
        if (!ip)
            return;
        this.broker?.publish(`error.${method}`, codec.encode({ method, ip, code }));
    }
}


text:
/* This is free and unencumbered software released into the public domain. */
import pg from 'pg';
import sql from 'sql-bricks-postgres';
const sqlConvert = sql.convert;
sql.convert = (val) => {
    if (val instanceof Uint8Array) {
        return `'\\x${Buffer.from(val).toString('hex')}'`;
    }
    return sqlConvert(val);
};
export { pg, sql };