text:
/* This is free and unencumbered software released into the public domain. */
import { parentPort, workerData } from 'worker_threads';
import { pg, sql } from './database.js';
import { computeBlockHash, generateEmptyBlock, emptyTransactionsRoot, } from './utils.js';
import { AccountID, Engine, hexToBytes, } from '@aurora-is-near/engine';
export class Indexer {
    constructor(config, network, engine) {
        this.config = config;
        this.network = network;
        this.engine = engine;
        this.pendingBlocks = {};
        this.contractID = AccountID.parse(this.config.engine).unwrap();
        this.pgClient = new pg.Client(config.database);
        this.pendingHeadBlock = 0;
        this.pendingBlocks = {};
    }
    async start() {
        await this.pgClient.connect();
    }
    async indexBlock(blockID) {
        for (;;) {
            const proxy = await this.engine.getBlock(blockID, {
                transactions: 'full',
                contractID: this.contractID,
            });
            if (proxy.isErr()) {
                const error = proxy.unwrapErr();
                if (error.startsWith('[-32000] Server error: DB Not Found Error')) {
                    // Handling empty blocks
                    const emptyBlock = generateEmptyBlock(blockID, this.contractID.toString(), this.network.chainID);
                    //if (this.config.debug) console.debug(emptyBlock); // DEBUG
                    console.error(`Indexing skipped block #${blockID}...`);
                    const skippedBlockDataJson = {
                        chain: emptyBlock.chain,
                        id: emptyBlock.id,
                        hash: emptyBlock.hash,
                        near_hash: emptyBlock.nearHash,
                        timestamp: emptyBlock.timestamp,
                        size: emptyBlock.size,
                        gas_limit: emptyBlock.gasLimit,
                        gas_used: emptyBlock.gasUsed,
                        parent_hash: emptyBlock.parentHash,
                        transactions_root: emptyBlock.transactionsRoot,
                        state_root: emptyBlock.stateRoot,
                        receipts_root: emptyBlock.receiptsRoot,
                    };
                    return skippedBlockDataJson;
                }
                if (this.config.debug)
                    console.error(error); // DEBUG
                await this.delay(100);
                continue; // retry block
            }
            const block_ = proxy.unwrap();
            const block = block_.getMetadata();
            const blockHash = computeBlockHash(block.number, this.contractID.toString(), this.network.chainID);
            const parentHash = computeBlockHash(block.number - 1, this.contractID.toString(), this.network.chainID);
            const transactionsRoot = block.transactions.length == 0
                ? emptyTransactionsRoot()
                : block.transactionsRoot;
            // console.error(`Indexing block #${blockID}...`);
            const transactions = await block.transactions.map(async (transaction, transactionIndex) => {
                return await this.indexTransaction(blockID, transactionIndex, transaction);
            });
            const blockData = {
                chain: this.network.chainID,
                id: block.number,
                hash: blockHash,
                near_hash: block_.near?.hash,
                timestamp: new Date(block.timestamp * 1000).toISOString(),
                size: block.size,
                gas_limit: 0,
                gas_used: 0,
                parent_hash: parentHash,
                transactions_root: transactionsRoot,
                state_root: block.stateRoot,
                receipts_root: block.receiptsRoot,
            };
            const jsonBlockData = {
                ...blockData,
                ...{ transactions: await Promise.all(transactions) },
            };
            return jsonBlockData; // finish block
        } // for (;;)
    }
    async indexTransaction(blockID, transactionIndex, transaction) {
        console.error(`Indexing transaction ${transaction.hash} at #${blockID}:${transactionIndex}...`);
        const to = transaction.to;
        const result = transaction.result;
        let status;
        let output = null;
        if (typeof result?.result?.status === 'boolean') {
            status = result.result?.status;
            output = result.output;
        }
        else {
            status = result.result?.status.enum === 'success';
            if (result.result?.status.success?.output.length)
                output = result.result?.status.success?.output;
        }
        //if (this.config.debug) console.debug(query.toParams()); // DEBUG
        const logs = await (transaction.result?.result?.logs || []).map(async (event, eventIndex) => {
            return await this.indexEvent(blockID, transactionIndex, eventIndex, event);
        });
        // Index all log events emitted by this transaction:
        return {
            block: blockID,
            index: transactionIndex,
            //id: null,
            hash: Buffer.from(hexToBytes(transaction.hash)),
            near_hash: transaction.near?.hash,
            near_receipt_hash: transaction.near?.receiptHash,
            from: Buffer.from(transaction.from.toBytes()),
            to: to?.isSome() ? Buffer.from(to.unwrap().toBytes()) : null,
            nonce: transaction.nonce,
            gas_price: transaction.gasPrice,
            gas_limit: transaction.gasLimit,
            gas_used: result.result?.gasUsed || '0',
            value: transaction.value,
            input: transaction.data?.length ? transaction.data : null,
            v: transaction.v,
            r: transaction.r,
            s: transaction.s,
            status: status,
            output: output,
            logs: await Promise.all(logs),
        };
    }
    async indexEvent(blockID, transactionIndex, eventIndex, event) {
        console.error(`Indexing log event at #${blockID}:${transactionIndex}:${eventIndex}...`);
        const event_ = event;
        return {
            //id: null,
            data: event_.data?.length ? event_.data : null,
            from: event_.address ? Buffer.from(event_.address) : Buffer.alloc(20),
            topics: event_.topics?.length
                ? event_.topics.map((topic) => topic.toBytes())
                : null,
        };
    }
    async insertNewHeads() {
        for (;;) {
            if (this.pendingBlocks[this.pendingHeadBlock]) {
                await this.insert(this.pendingBlocks[this.pendingHeadBlock]);
                delete this.pendingBlocks[this.pendingHeadBlock];
                this.pendingHeadBlock += 1;
            }
            await this.delay(100);
        }
    }
    async notifyNewHeads(blockID, blockData) {
        if (this.pendingHeadBlock == 0) {
            this.pendingHeadBlock = blockID;
        }
        this.pendingBlocks[blockID] = blockData;
    }
    async insert(blockData) {
        try {
            const transactions = blockData.transactions || [];
            delete blockData.transactions;
            await this.pgClient.query('BEGIN');
            const query = sql.insert('block', blockData);
            await this.pgClient.query(query.toParams());
            for (const [transactionIndex, transaction] of transactions.entries()) {
                const logs = transaction.logs;
                delete transaction.logs;
                const transactionData = {
                    ...transaction,
                    ...{ block: blockData.id, index: transactionIndex },
                };
                const query = sql
                    .insert('transaction', transactionData)
                    .returning('id');
                const { rows: [{ id }], } = await this.pgClient.query(query.toParams());
                const transactionID = parseInt(id);
                for (const [eventIndex, event] of logs.entries()) {
                    const eventData = {
                        ...event,
                        ...{ transaction: transactionID, index: eventIndex },
                    };
                    const query = sql.insert('event', eventData);
                    await this.pgClient.query(query.toParams());
                }
            }
            await this.pgClient.query('COMMIT');
        }
        catch (error) {
            console.error('Error indexing. Performing Rollback', error);
            await this.pgClient.query('ROLLBACK');
            return; // abort block
        }
    }
    async delay(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }
}
async function main(parentPort, workerData) {
    const { config, network, env } = workerData;
    const engine = await Engine.connect({
        network: network.id,
        endpoint: config.endpoint,
        contract: config.engine,
    }, env);
    const indexer = new Indexer(config, network, engine);
    await indexer.start();
    setTimeout(function () {
        indexer.insertNewHeads();
    });
    parentPort
        .on('message', async (block) => {
        parentPort.postMessage(true); // ack the request
        const blockData = await indexer.indexBlock(block.id);
        if (block.is_head) {
            indexer.notifyNewHeads(block.id, blockData);
        }
        else {
            indexer.insert(blockData);
        }
    })
        .on('close', () => {
        return; // TODO?
    });
}
main(parentPort, workerData);


text:
export declare function unsupported(method: string): void;
export declare function unimplemented(method: string): void;
export declare function expectArgs(args: any[] | undefined, min: number, max: number, minMsg?: string): any[];
export declare abstract class CodedError extends Error {
    readonly code: number;
    readonly data?: any;
    constructor(code: number, message: string, data?: any);
}
export declare class UnexpectedError extends CodedError {
    constructor(message: string);
}
export declare abstract class ExpectedError extends CodedError {
    constructor(code: number, message: string, data?: any);
}
export declare class UnsupportedMethod extends ExpectedError {
    constructor(method: string);
}
export declare class UnimplementedMethod extends ExpectedError {
    constructor(method: string);
}
export declare class MissingArgument extends ExpectedError {
    constructor(index: number, message?: string);
}
export declare class TooManyArguments extends ExpectedError {
    constructor(maxCount: number);
}
export declare class InvalidArguments extends ExpectedError {
    constructor(message?: string);
}
export declare class InvalidAddress extends ExpectedError {
    constructor(message?: string);
}
export declare class UnknownFilter extends ExpectedError {
    constructor(_id: string);
}
export declare class TransactionError extends ExpectedError {
    constructor(message: string);
}
export declare class RevertError extends ExpectedError {
    constructor(reason: Uint8Array);
}
export declare class GasPriceTooLow extends ExpectedError {
    constructor(message?: string);
}
export declare class LimitExceeded extends ExpectedError {
    constructor(limit: number, message?: string);
}

repo_name: aurora-is-near/aurora-relayer

repo_language: TypeScript

mime_type: text/plain


text:
import { Config } from './config.js';
import { pg } from './database.js';
import { ConnectEnv, NetworkConfig } from '@aurora-is-near/engine';
import { Logger } from 'pino';
interface BlockRow {
    chain: number;
    id: number;
    hash: Uint8Array;
    near_hash: Uint8Array | null;
    timestamp: Date | null;
    size: number;
    gas_limit: number;
    gas_used: number;
    parent_hash: Uint8Array;
    transactions_root: Uint8Array;
    state_root: Uint8Array;
    receipts_root: Uint8Array;
}
declare global {
    namespace NodeJS {
        interface ProcessEnv extends ConnectEnv {
        }
    }
}
export declare class PrehistoryIndexer {
    readonly config: Config;
    readonly network: NetworkConfig;
    readonly logger: Logger;
    protected readonly archive: pg.Client;
    protected blockID: number;
    constructor(config: Config, network: NetworkConfig, logger: Logger);
    run(startBlockID: number, mode?: string): Promise<void>;
    serialize(query: BlockRow): string;
}
export {};


text:
/** Encapsulates the Express.js `Request` object. */
export declare class Request {
    readonly req: any;
    constructor(req: any);
    hasAuthorization(): boolean;
    ip(): string;
    country(): string;
    websocketKey(): string;
}


text:
/* This is free and unencumbered software released into the public domain. */
import sql from 'sql-bricks';
export function compileTopics(topics) {
    if (!topics || topics.length == 0)
        return null;
    if (Array.isArray(topics)) {
        const topicsDeepLevel = 15;
        const flattenedTopics = topics.flat(topicsDeepLevel);
        const isEmptyTopics = flattenedTopics.every((topic) => {
            return topic === null;
        });
        if (isEmptyTopics)
            return null;
    }
    const operands = topics
        .map((topic, i) => {
        if (topic === null)
            return [];
        if (typeof topic === 'string') {
            return [sql.eq(`e.topics[${i + 1}]`, hexToBytes(topic))];
        }
        if (Array.isArray(topic)) {
            if (!topic.length)
                return [];
            const operands = topic
                .map((topic) => {
                if (topic === null)
                    return [];
                return [sql.eq(`e.topics[${i + 1}]`, hexToBytes(topic))];
            })
                .flat();
            return [sql.or(...operands)];
        }
        throw Error('unreachable');
    })
        .flat();
    return sql.and(...operands);
}
// Duplicated here because of https://github.com/kulshekhar/ts-jest/issues/970
function hexToBytes(input) {
    return Buffer.from(input.substring(2), 'hex');
}
export function matchTopics(topics, topicsInTransaction) {
    return topics.every((topic, i) => {
        if (topic === null || (Array.isArray(topic) && !topic.length)) {
            return true;
        }
        // can be removed after https://github.com/aurora-is-near/aurora-relayer/pull/293 merge
        if (!Array.isArray(topicsInTransaction)) {
            return false;
        }
        if (Array.isArray(topic)) {
            return topic.includes(topicsInTransaction[i]);
        }
        if (topic !== topicsInTransaction[i]) {
            return false;
        }
        return true;
    });
}


text:
import { Request } from './request.js';
export declare type Data = string;
export declare type Quantity = string;
export declare type Tag = 'latest' | 'earliest' | 'pending';
export declare type TypedData = unknown;
export declare 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 declare type TransactionReceipt = Record<string, Data | Quantity | LogObject[] | null>;
export declare type FilterTopic = FilterTopic[] | Data | null;
export interface FilterOptions {
    fromBlock?: Quantity | Tag;
    toBlock?: Quantity | Tag;
    address?: Data | Data[];
    topics?: FilterTopic[];
    blockHash?: Data;
    index?: Quantity | null;
}
export declare type ProofResult = Record<string, unknown>;
export declare type SyncStatus = Record<string, Quantity>;
export declare 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>;
    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>;
    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>[]>;
    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>;
    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:
#!/usr/bin/env node
/* This is free and unencumbered software released into the public domain. */
import { createApp } from './app.js';
import { parseConfig } from './config.js';
import { pg } from './database.js';
import { Engine } from '@aurora-is-near/engine';
import { program } from 'commander';
import externalConfig from 'config';
import pino from 'pino';
async function main(argv, env) {
    program
        .option('-d, --debug', 'enable debug output')
        .option('-v, --verbose', 'enable verbose output')
        .option('--database <url>', `specify PostgreSQL database URL (default: none)`)
        .option('--port <port>', `specify port to listen to (default: ${8545})`)
        .option('--network <network>', `specify NEAR network ID (default: "${env.NEAR_ENV || 'local'}")`)
        .option('--endpoint <url>', `specify NEAR RPC endpoint URL (default: "${env.NEAR_URL || ''}")`)
        .option('--engine <account>', `specify Aurora Engine account ID (default: "${env.AURORA_ENGINE || 'aurora.test.near'}")`)
        .option('--signer <account>', `specify signer account ID (default: "${env.NEAR_MASTER_ACCOUNT || 'test.near'}")`)
        .option('--signer-key <path>', `specify path to signer key JSON file`)
        .parse(argv);
    const [network, config] = parseConfig(program.opts(), externalConfig, env);
    if (config.debug) {
        for (const source of externalConfig.util.getConfigSources()) {
            console.error(`Loaded configuration file ${source.name}.`);
        }
        console.error('Configuration:', config);
    }
    process.on('SIGHUP', () => {
        global.blacklistConfig = null;
    });
    const logger = pino();
    logger.info('starting server');
    if (config.database) {
        try {
            const sql = new pg.Client(config.database);
            await sql.connect();
            await sql.query('SELECT 1'); // test connectivity
        }
        catch (error) {
            console.error(`aurora-relayer: Invalid database configuration: ${error.message}`);
            if (config.debug)
                console.error(error);
            process.exit(78); // EX_CONFIG
        }
    }
    const engine = await Engine.connect({
        network: network.id,
        endpoint: config.endpoint,
        contract: config.engine,
        signer: config.signer,
    }, env);
    if (Array.isArray(config.signerKeys) && config.signerKeys.length > 0) {
        engine.keyStore.loadKeyFiles(config.signerKeys);
        console.error(`Loaded signer key files ${config.signerKeys.join(', ')}.`);
    }
    else if (config.signerKey) {
        engine.keyStore.loadKeyFile(config.signerKey);
        console.error(`Loaded signer key file ${config.signerKey}.`);
    }
    const app = await createApp(config, logger, engine);
    app.listen(config.port, () => {
        if (config.verbose || config.debug) {
            console.error(`Relayer for the NEAR ${network.label} listening at http://localhost:${config.port}...`);
        }
    });
}
main(process.argv, process.env).catch((error) => {
    console.error(`aurora-relayer: ${error.message}`);
    process.exit(78); // EX_CONFIG
});


text:
import { ConnectEnv } from '@aurora-is-near/engine';
declare global {
    namespace NodeJS {
        interface ProcessEnv extends ConnectEnv {
        }
    }
}


text:
/* This is free and unencumbered software released into the public domain. */
import { blacklist } from './blacklist.js';
import expressIpFilter from 'express-ipfilter';
const { IpDeniedError, IpFilter } = expressIpFilter;
import expressPinoLogger from 'express-pino-logger';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz', 16);
export function setRequestID() {
    return (req, res, next) => {
        const id = req.headers['x-request-id'] || req.headers['cf-request-id'] || nanoid();
        req['id'] = id;
        res.set('X-Request-ID', id);
        next();
    };
}
export function logger(logger) {
    return expressPinoLogger({
        logger,
        genReqId: (req) => {
            return req.id;
        },
        reqCustomProps: (req, _res) => {
            const body = req.body;
            if (!body || req?.method !== 'POST' || req?.url !== '/')
                return {};
            return {
                rpc: {
                    method: body?.method,
                    params: body?.params,
                },
            };
        },
    });
}
export function blacklistIPs() {
    const ipLookup = () => {
        return [...(blacklist('IPs') || [])];
    };
    return IpFilter(ipLookup, {
        mode: 'deny',
        log: false,
        trustProxy: true,
    });
}
export function handleErrors() {
    return (err, req, res, next) => {
        if (err instanceof IpDeniedError) {
            // TODO: req.log.info(...);
            res.status(403).end();
        }
        else {
            next();
        }
    };
}
export default {
    setRequestID,
    logger,
    blacklistIPs,
    handleErrors,
};


text:
import { Config } from './config.js';
import { pg } from './database.js';
import { AccountID, BlockHeight, Engine, LogEvent, LogEventWithAddress, NetworkConfig } from '@aurora-is-near/engine';
interface PendingBlocks {
    [key: number]: string;
}
export declare class Indexer {
    readonly config: Config;
    readonly network: NetworkConfig;
    readonly engine: Engine;
    protected readonly contractID: AccountID;
    protected readonly pgClient: pg.Client;
    protected pendingHeadBlock: number;
    protected pendingBlocks: PendingBlocks;
    constructor(config: Config, network: NetworkConfig, engine: Engine);
    start(): Promise<void>;
    indexBlock(blockID: BlockHeight): Promise<any>;
    indexTransaction(blockID: BlockHeight, transactionIndex: number, transaction: any): Promise<any>;
    indexEvent(blockID: BlockHeight, transactionIndex: number, eventIndex: number, event: LogEventWithAddress | LogEvent): Promise<any>;
    insertNewHeads(): Promise<void>;
    notifyNewHeads(blockID: number, blockData: any): Promise<void>;
    insert(blockData: any): Promise<void>;
    delay(ms: number): Promise<unknown>;
}
export {};


text:
/* This is free and unencumbered software released into the public domain. */
import { matchTopics } from './topics.js';
import { createServer } from './server.js';
import pg from 'pg';
import expressWs from 'express-ws';
import { exportJSON } from '@aurora-is-near/engine';
const syncInterval = 3000;
const newHeadsInterval = 200;
const retriesCount = 50;
export async function createWsServer(config, logger, engine, app) {
    // const pgPool = new pg.Pool({ "connectionString": config.database });
    const pgClient = new pg.Client(config.database);
    pgClient.connect();
    pgClient.query('DELETE FROM subscription');
    const jaysonWsServer = createServer(config, logger, engine);
    const expressWsApp = expressWs(app);
    expressWsApp.app.ws('/', function (ws, req) {
        ws.id = req.headers['sec-websocket-key'];
        ws.on('message', function (msg) {
            try {
                const parsedObject = JSON.parse(msg);
                jaysonWsServer.call(parsedObject, req, function (error, success) {
                    ws.send(JSON.stringify(error || success));
                });
            }
            catch (e) {
                ws.send('Invalid request');
            }
        });
        ws.on('close', function close() {
            pgClient.query('DELETE FROM subscription WHERE sec_websocket_key = $1', [
                ws.id,
            ]);
        });
        ws.on('error', () => {
            ws.send('Error connecting to WebSocket');
        });
    });
    setTimeout(sync, syncInterval, pgClient, expressWsApp);
    const blockHeight = (await engine.getBlockHeight()).unwrap() + 10;
    setTimeout(notifyNewHeads, newHeadsInterval, pgClient, expressWsApp, jaysonWsServer, blockHeight, 0);
    return true;
}
async function sync(pgClient, expressWsApp, blockNumber) {
    const maxIdResult = await pgClient.query('SELECT MAX(id)::int AS max_id FROM block');
    const maxID = maxIdResult.rows[0].max_id || 0;
    forSubscriptions(pgClient, 'syncing', function (row) {
        const payload = {
            jsonrpc: '2.0',
            subscription: row.sub_id,
            result: { syncing: maxID > blockNumber },
        };
        sendPayload(expressWsApp, row.ws_key, row.sub_id, JSON.stringify(payload));
    });
    setTimeout(sync, syncInterval, pgClient, expressWsApp, maxID);
}
async function notifyNewHeads(pgClient, expressWsApp, jaysonWsServer, blockNumber, retries) {
    const result = await pgClient.query('SELECT 1 FROM block WHERE id = $1 LIMIT 1', [blockNumber]);
    if (result.rows.length > 0) {
        const blockBody = {
            jsonrpc: '2.0',
            id: 1,
            method: 'eth_getBlockByNumber',
            params: [blockNumber, true],
        };
        jaysonWsServer.call(blockBody, {}, function (error, success) {
            forSubscriptions(pgClient, 'newHeads', function (row) {
                sendPayload(expressWsApp, row.ws_key, row.sub_id, error || success);
            });
        });
        const logsBody = {
            jsonrpc: '2.0',
            id: 1,
            method: 'eth_getLogs',
            params: [
                {
                    fromBlock: blockNumber,
                    toBlock: blockNumber,
                },
            ],
        };
        jaysonWsServer.call(logsBody, {}, function (error, success) {
            forSubscriptions(pgClient, 'logs', function (row) {
                const address = parseAddresses(row.filter?.address);
                const topics = row.filter?.topics || [];
                let result = success.result;
                if (address.length > 0) {
                    result = result.filter((result) => {
                        return address.includes(result.address);
                    });
                }
                if (topics.length > 0) {
                    result = result.filter((result) => {
                        return matchTopics(topics, result.topics);
                    });
                }
                result.forEach(function (res) {
                    sendPayload(expressWsApp, row.ws_key, row.sub_id, {
                        ...success,
                        ...{ result: res },
                    });
                });
            });
        });
        blockNumber = blockNumber + 1;
        retries = 0;
    }
    else {
        if (retries > retriesCount) {
            blockNumber = blockNumber + 1;
            retries = 0;
        }
        else {
            retries = retries + 1;
        }
    }
    forSubscriptions(pgClient, 'newHeads_debug', function (row) {
        const payload = {
            jsonrpc: '2.0',
            subscription: row.sub_id,
            result: {
                blockNumber,
            },
        };
        sendPayload(expressWsApp, row.ws_key, row.sub_id, JSON.stringify(payload));
    });
    setTimeout(notifyNewHeads, newHeadsInterval, pgClient, expressWsApp, jaysonWsServer, blockNumber, retries);
}
function sendPayload(express, ws_key, subId, payload) {
    express.getWss().clients.forEach(function (client) {
        if (ws_key == client.id) {
            typeof payload === 'string'
                ? client.send(payload)
                : client.send(wsResponse(payload, subId));
        }
    });
}
function wsResponse(rpcResponse, clientId) {
    const response = {
        jsonrpc: '2.0',
        method: 'eth_subscription',
        params: {
            result: rpcResponse.result,
            subscription: clientId,
        },
    };
    return JSON.stringify(exportJSON(response));
}
function forSubscriptions(pgClient, subscriptionType, callback) {
    pgClient
        .query(`SELECT sec_websocket_key AS ws_key, id AS sub_id, filter FROM subscription WHERE type = $1`, [subscriptionType])
        .then(function (pgResult) {
        pgResult.rows.forEach(callback);
    });
}
function parseAddresses(inputs) {
    const addresses = (Array.isArray(inputs) ? inputs : [inputs]).map((input) => {
        return input?.id?.toLowerCase() || null;
    });
    return addresses.filter((x) => x !== null);
}


text:
/* This is free and unencumbered software released into the public domain. */
import { parseConfig } from './config.js';
import { StaticPool } from 'node-worker-threads-pool';
import { program } from 'commander';
import externalConfig from 'config';
import FastPriorityQueue from 'fastpriorityqueue';
import * as readline from 'readline';
main(process.argv, process.env).catch((error) => {
    const errorMessage = error.message.startsWith('<')
        ? error.name
        : error.message;
    console.error(errorMessage);
    process.exit(70); // EX_SOFTWARE
});
async function main(argv, env) {
    program
        .option('-d, --debug', 'enable debug output')
        .option('-v, --verbose', 'enable verbose output')
        .option('-f, --force', 'reindex already indexed blocks')
        .option('--database <url>', `specify PostgreSQL database URL (default: none)`)
        .option('--network <network>', `specify NEAR network ID (default: "${env.NEAR_ENV || 'local'}")`)
        .option('--endpoint <url>', `specify NEAR RPC endpoint URL (default: "${env.NEAR_URL || ''}")`)
        .option('--engine <account>', `specify Aurora Engine account ID (default: "${env.AURORA_ENGINE || 'aurora.test.near'}")`)
        .parse(argv);
    const opts = program.opts();
    const [network, config] = parseConfig(opts, externalConfig, env);
    if (config.debug) {
        for (const source of externalConfig.util.getConfigSources()) {
            console.error(`Loaded configuration file ${source.name}.`);
        }
        console.error('Configuration:', config);
    }
    const workers = new StaticPool({
        size: 4,
        task: './lib/indexer_worker.js',
        workerData: {
            config,
            network,
            env: {
                NEAR_ENV: env.NEAR_ENV,
                NEAR_URL: env.NEAR_URL,
            },
        },
    });
    const queue = new FastPriorityQueue((a, b) => {
        return a > b;
    });
    let inputOpen = true;
    const input = readline.createInterface({
        input: process.stdin,
        terminal: false,
    });
    input
        .on('line', (line) => {
        queue.add(JSON.parse(line));
    })
        .on('close', () => {
        inputOpen = false;
    });
    while (!queue.isEmpty() || inputOpen) {
        const blockData = queue.poll();
        if (blockData === undefined) {
            // The queue is empty, wait for more input:
            await new Promise((resolve) => setTimeout(resolve, 10));
            continue;
        }
        await workers.exec(blockData);
    }
    process.exit(0);
}


text:
import { ConnectEnv, NetworkConfig } from '@aurora-is-near/engine';
export interface Config {
    debug: boolean;
    verbose: boolean;
    force: boolean;
    database?: string;
    broker?: string;
    port: number | string;
    network: string;
    endpoint?: string;
    engine: string;
    signer: string;
    signerKey?: string;
    signerKeys?: string[];
    block?: number | string;
    batchSize?: number | string;
    writable?: boolean;
    errorLog?: string;
    minGasPrice?: number;
    getLogsLimit: number;
}
export declare const localConfig: Config;
export declare function parseConfig(options: Config, config: Config, env: ConnectEnv): [NetworkConfig, Config];


text:
import * as web3 from './web3';
import sql from 'sql-bricks';
export declare function compileTopics(topics: web3.FilterTopic[]): sql.WhereExpression | null;
export declare function matchTopics(topics: Array<string | null | Array<string>>, topicsInTransaction: string[] | null | any[]): boolean;


text:
/* This is free and unencumbered software released into the public domain. */
import yaml from 'js-yaml';
import fs from 'fs';
export function blacklist(method) {
    const globalAny = global;
    if (globalAny.blacklistConfig === undefined ||
        globalAny.blacklistConfig === null) {
        try {
            const blacklistConfig = yaml.load(fs.readFileSync('config/blacklist.yaml', 'utf8'));
            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 { parseEVMRevertReason } from './utils.js';
export function unsupported(method) {
    throw new UnsupportedMethod(method);
}
export function unimplemented(method) {
    throw new UnimplementedMethod(method);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function expectArgs(args, min, max, minMsg) {
    if (args && args.length < min)
        throw new MissingArgument(min - 1, minMsg);
    if (args && args.length > max)
        throw new TooManyArguments(max);
    //if (args.length > max) throw new InvalidArguments();
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return args;
}
export class CodedError extends Error {
    constructor(code, message, data) {
        super(message);
        this.code = code;
        this.data = data;
        Object.setPrototypeOf(this, CodedError.prototype);
    }
}
export class UnexpectedError extends CodedError {
    constructor(message) {
        super(-32603, message);
        Object.setPrototypeOf(this, UnexpectedError.prototype);
    }
}
export class ExpectedError extends CodedError {
    constructor(code, message, data) {
        super(code, message, data);
        Object.setPrototypeOf(this, ExpectedError.prototype);
    }
}
export class UnsupportedMethod extends ExpectedError {
    constructor(method) {
        super(-32601, `Unsupported method: ${method}`);
        Object.setPrototypeOf(this, UnsupportedMethod.prototype);
    }
}
export class UnimplementedMethod extends ExpectedError {
    constructor(method) {
        super(-32601, `Unimplemented method: ${method}`);
        Object.setPrototypeOf(this, UnimplementedMethod.prototype);
    }
}
export class MissingArgument extends ExpectedError {
    constructor(index, message) {
        super(-32602, message || `missing value for required argument ${index}`);
        Object.setPrototypeOf(this, MissingArgument.prototype);
    }
}
export class TooManyArguments extends ExpectedError {
    constructor(maxCount) {
        super(-32602, `too many arguments, want at most ${maxCount}`);
        Object.setPrototypeOf(this, TooManyArguments.prototype);
    }
}
export class InvalidArguments extends ExpectedError {
    constructor(message) {
        super(-32602, message || `Invalid method parameter(s).`);
        Object.setPrototypeOf(this, InvalidArguments.prototype);
    }
}
export class InvalidAddress extends ExpectedError {
    constructor(message) {
        super(-32602, message || `Invalid address.`);
        Object.setPrototypeOf(this, InvalidAddress.prototype);
    }
}
export class UnknownFilter extends ExpectedError {
    constructor(_id) {
        super(-32000, `filter not found`);
        Object.setPrototypeOf(this, UnknownFilter.prototype);
    }
}
export class TransactionError extends ExpectedError {
    constructor(message) {
        super(-32000, message);
        Object.setPrototypeOf(this, TransactionError.prototype);
    }
}
export class RevertError extends ExpectedError {
    constructor(reason) {
        const revertReason = parseEVMRevertReason(reason);
        if (typeof revertReason === 'string') {
            super(3, revertReason);
        }
        else {
            super(3, `execution reverted`, revertReason);
        }
        Object.setPrototypeOf(this, RevertError.prototype);
    }
}
export class GasPriceTooLow extends ExpectedError {
    constructor(message) {
        super(-32000, message || `Gas price too low.`);
        Object.setPrototypeOf(this, InvalidAddress.prototype);
    }
}
export class LimitExceeded extends ExpectedError {
    constructor(limit, message) {
        super(-32005, message || `query returned more than ${limit} results`);
        Object.setPrototypeOf(this, InvalidAddress.prototype);
    }
}


text:
import { Config } from './config.js';
import { Engine } from '@aurora-is-near/engine';
import jayson from 'jayson';
import { Logger } from 'pino';
export declare class Method extends jayson.Method {
    handler?: jayson.MethodHandlerType | undefined;
    readonly server: any;
    constructor(handler?: jayson.MethodHandlerType | undefined, options?: jayson.MethodOptions);
    getHandler(): jayson.MethodHandlerType;
    setHandler(handler: jayson.MethodHandlerType): void;
    execute(server: jayson.Server, requestParams: jayson.RequestParamsLike, request: any, // context
    callback: jayson.JSONRPCCallbackType): any;
}
export declare function createServer(config: Config, logger: Logger, engine: Engine): jayson.Server;


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


text:
/* This is free and unencumbered software released into the public domain. */
/** Encapsulates the Express.js `Request` object. */
export class Request {
    constructor(req) {
        this.req = req;
    }
    hasAuthorization() {
        return 'authorization' in this.req.headers;
    }
    ip() {
        return this.req.headers['cf-connecting-ip'];
    }
    country() {
        return this.req.headers['cf-ipcountry'];
    }
    websocketKey() {
        return this.req.headers['sec-websocket-key'];
    }
}


text:
import pg from 'pg';
import sql from 'sql-bricks-postgres';
export { pg, sql };


text:
/// <reference types="node" />
export declare type EmptyBlock = {
    chain: number;
    id: number;
    hash: Buffer;
    nearHash: Buffer | null;
    timestamp: string | null;
    size: number;
    gasLimit: number;
    gasUsed: number;
    parentHash: Buffer;
    transactionsRoot: Buffer;
    stateRoot: Buffer;
    receiptsRoot: Buffer;
};
export declare function computeBlockHash(blockHeight: number, accountId: string, chainId: number): Buffer;
export declare function generateEmptyBlock(blockHeight: number, accountId: string, chainId: number): EmptyBlock;
export declare function emptyTransactionsRoot(): Buffer;
export declare function parseEVMRevertReason(reason: Uint8Array): string | Uint8Array;


text:
#!/usr/bin/env node
import { ConnectEnv } from '@aurora-is-near/engine';
declare global {
    namespace NodeJS {
        interface ProcessEnv extends ConnectEnv {
            CF_API_TOKEN?: string;
            CF_ACCOUNT_ID?: string;
            CF_LIST_ID?: string;
        }
    }
}


text:
import { Logger } from 'pino';
export declare function setRequestID(): (req: any, res: any, next: any) => void;
export declare function logger(logger: Logger): any;
export declare function blacklistIPs(): any;
export declare function handleErrors(): (err: any, req: any, res: any, next: any) => void;
declare const _default: {
    setRequestID: typeof setRequestID;
    logger: typeof logger;
    blacklistIPs: typeof blacklistIPs;
    handleErrors: typeof handleErrors;
};
export default _default;


text:
export interface BlacklistConfig {
    IPs: Set<string>;
    EOAs: Set<string>;
    CAs: Set<string>;
}
export declare function blacklist(method: string): any;


text:
import { Config } from '../config.js';
import { pg } from '../database.js';
import { AccountID, Engine, ConnectEnv, NetworkConfig } from '@aurora-is-near/engine';
import { Logger } from 'pino';
declare global {
    namespace NodeJS {
        interface ProcessEnv extends ConnectEnv {
        }
    }
}
export declare class ReindexWorker {
    readonly config: Config;
    readonly network: NetworkConfig;
    readonly logger: Logger;
    readonly engine: Engine;
    protected readonly contractID: AccountID;
    protected readonly pgClient: pg.Client;
    constructor(config: Config, network: NetworkConfig, logger: Logger, engine: Engine);
    run(startBlockId: number, mode?: string): Promise<void>;
}


text:
/* This is free and unencumbered software released into the public domain. */
import { parseConfig } from '../config.js';
import { pg, sql } from '../database.js';
import { AccountID, Engine, } 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 ReindexWorker {
    constructor(config, network, logger, engine) {
        this.config = config;
        this.network = network;
        this.logger = logger;
        this.engine = engine;
        this.contractID = AccountID.parse(this.config.engine).unwrap();
        this.pgClient = new pg.Client(config.database);
    }
    async run(startBlockId, mode) {
        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) => {
                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.forEach((transaction) => {
                        (transaction.result?.result?.logs || []).forEach((event, eventIndex) => {
                            const event_ = event;
                            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, 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 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) => {
    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 { parseConfig } from '../config.js';
import { pg, sql } from '../database.js';
import { emptyTransactionsRoot } from '../utils.js';
import { AccountID, Engine, } from '@aurora-is-near/engine';
import { program } from 'commander';
import externalConfig from 'config';
import pino from 'pino';
const logger = pino(pino.destination(2));
class ReindexWorker {
    constructor(config, network, logger, engine) {
        this.config = config;
        this.network = network;
        this.logger = logger;
        this.engine = engine;
        this.contractID = AccountID.parse(this.config.engine).unwrap();
        this.pgClient = new pg.Client(config.database);
    }
    async run(blockId) {
        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, 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 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) => {
    const errorMessage = error.message.startsWith('<')
        ? error.name
        : error.message;
    logger.error(errorMessage);
    process.exit(70); // EX_SOFTWARE
});


text:
import { ConnectEnv } from '@aurora-is-near/engine';
declare global {
    namespace NodeJS {
        interface ProcessEnv extends ConnectEnv {
        }
    }
}


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 { compileTopics } from '../topics.js';
import { parse } from 'postgres-array';
import { Address, bytesToHex, exportJSON, formatU256, hexToBytes, hexToInt, intToHex, } from '@aurora-is-near/engine';
import fs from 'fs';
import { getRandomBytesSync } from 'ethereum-cryptography/random.js';
import { parse as parseRawTransaction, } from '@ethersproject/transactions';
import { keccak256 } from 'ethereumjs-util';
export class DatabaseServer extends SkeletonServer {
    async _init() {
        // 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.setTypeParser(pg.types.builtins.INT8, (val) => BigInt(val));
        pgClient.setTypeParser(pg.types.builtins.NUMERIC, (val) => 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.setTypeParser(oid, (val) => BigInt(val));
            }
        }
    }
    _query(query, args) {
        // 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) {
        return intToHex(await this._fetchCurrentBlockID());
    }
    async eth_call(_request, transaction, blockNumberOrHash) {
        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.isErr()) {
                        const errorInstance = result.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);
                },
                err: (message) => {
                    throw new Error(message);
                },
            });
        }
        catch (error) {
            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) {
        // EIP-695
        const chainID = (await this.engine.getChainID()).unwrap();
        return intToHex(chainID);
    }
    async eth_coinbase(_request) {
        return (await this.engine.getCoinbase()).unwrap().toString();
    }
    async eth_gasPrice(_request) {
        const minGasPrice = this.config.minGasPrice !== undefined ? this.config.minGasPrice : 0;
        return intToHex(minGasPrice);
    }
    async eth_getBalance(_request, address, blockNumber) {
        const address_ = parseAddress(address);
        this._enforceBans(address_, 'eth_getBalance');
        const balance = (await this.engine.getBalance(address_)).unwrap();
        return intToHex(balance);
    }
    async eth_getBlockByHash(_request, blockHash, fullObject) {
        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, blockNumber, fullObject) {
        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_, fullObject || false);
            return exportJSON(block);
        }
        catch (error) {
            if (this.config.debug) {
                console.debug('eth_getBlockByNumber', error);
            }
            return null;
        }
    }
    async eth_getBlockTransactionCountByHash(_request, blockHash) {
        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, blockNumber) {
        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, address, blockNumber) {
        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, filterID) {
        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) => Object.values(row));
                return buffers.map(bytesToHex);
            }
            case 'event': {
                return await this._getFilterChangesEvent(filter, filterID_);
            }
            case 'transaction':
            default:
                return [];
        }
    }
    async eth_getFilterLogs(_request, filterID) {
        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) => Object.values(row));
                return buffers.map(bytesToHex);
            }
            case 'event': {
                const 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, filter) {
        return await this._getLogs(filter);
    }
    async eth_getStorageAt(_request, address, key, blockNumber) {
        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, blockHash, transactionIndex) {
        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, blockNumber, transactionIndex) {
        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, transactionHash) {
        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, address, blockNumber) {
        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_ + 1
                : undefined,
        })).unwrap();
        return intToHex(nonce);
    }
    async eth_getTransactionReceipt(_request, transactionHash) {
        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, blockHash) {
        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, blockNumber) {
        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) {
        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, filter) {
        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',
            fromBlock,
            toBlock,
            addresses,
            topics ? JSON.stringify(topics) : null,
        ]);
        return intToHex(id.toString('hex'));
    }
    async eth_newPendingTransactionFilter(_request) {
        const { rows: [{ id }], } = await this._query('SELECT eth_newPendingTransactionFilter() AS id');
        return bytesToHex(id);
    }
    async eth_sendRawTransaction(request, transaction) {
        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;
        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);
                        }
                        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, _subsciptionType, _filter) {
        // Skip unsupported subs
        const id = bytesToHex(getRandomBytesSync(16));
        const filter = {};
        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, filterID) {
        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, _subsciptionId) {
        const query = sql.delete('subscription').where({ id: _subsciptionId });
        await this._query(query.toString());
        return true;
    }
    async _updatePollBlock(filterID_) {
        const latestBlock = await this._fetchCurrentBlockID();
        await this._query('UPDATE filter SET poll_block = $1 + 1 WHERE uuid_send(id) = $2', [latestBlock, filterID_]);
    }
    async _getFilterChangesEvent(filter, filterID_) {
        const 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;
    }
    async _fetchCurrentBlockID() {
        const { rows: [{ result }], } = await this._query('SELECT eth_blockNumber() AS result');
        return result;
    }
    async _fetchEvents(transactionID) {
        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) => {
            row['topics'] =
                row['topics'] === null
                    ? []
                    : row['topics']
                        .split(';')
                        .map((topic) => topic.replace('\\x', '0x'));
            return row;
        });
    }
    async _fetchTransactions(blockID, fullObject) {
        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) => bytesToHex(row['hash']));
        }
    }
    async _getLogs(filter) {
        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) => {
            if (row['address'] === null) {
                row['address'] = Address.zero().toString();
            }
            // remove null values
            if (Array.isArray(row['topics'])) {
                row['topics'] = row['topics'].filter((t) => {
                    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) {
    if (!input)
        throw new InvalidArguments();
    return Address.parse(input).unwrapOrElse((_) => {
        throw new InvalidArguments();
    });
}
function parseAddresses(inputs) {
    return (Array.isArray(inputs) ? inputs : [inputs]).map(parseAddress);
}
function parseBlockSpec(blockSpec) {
    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) {
    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 { unimplemented, unsupported } from '../errors.js';
import { blacklist } from '../blacklist.js';
import { bytesToHex, intToHex } from '@aurora-is-near/engine';
import { keccakFromHexString } from 'ethereumjs-util';
import { spawn } from 'child_process';
export class SkeletonServer {
    constructor(config, logger, engine) {
        this.config = config;
        this.logger = logger;
        this.engine = engine;
        this._init();
    }
    _isBannedEOA(address) {
        const key = address.toString().toLowerCase();
        return blacklist('EOAs').has(key);
    }
    _isBannedCA(address) {
        const key = address.toString().toLowerCase();
        return blacklist('CAs').has(key);
    }
    _enforceBans(address, method) {
        this._enforceEOABan(address, method);
        this._enforceCABan(address, method);
    }
    _enforceEOABan(address, method) {
        if (this._isBannedEOA(address)) {
            unsupported(method);
        }
    }
    _enforceCABan(address, method) {
        if (this._isBannedCA(address)) {
            unsupported(method);
        }
    }
    _scanForCABans(bytes) {
        for (const [address, _] of blacklist('CAs').entries()) {
            const match = address.substring(2); // strip '0x' prefix
            if (bytes.includes(match)) {
                return address;
            }
        }
        return null;
    }
    async _banIP(ip, reason) {
        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) {
        return 'Aurora-Relayer/0.0.0'; // TODO
    }
    async web3_sha3(_request, input) {
        return bytesToHex(keccakFromHexString(input));
    }
    // net_*
    async net_listening(_request) {
        return true;
    }
    async net_peerCount(_request) {
        return intToHex(0);
    }
    async net_version(_request) {
        const netVersion = (await this.engine.getChainID()).unwrap();
        return netVersion.toString();
    }
    // eth_*
    async eth_accounts(_request) {
        return []; // no private keys under management here
    }
    async eth_blockNumber(_request) {
        unimplemented('eth_blockNumber');
        return intToHex(0);
    }
    async eth_call(_request, _transaction, _blockNumber) {
        unimplemented('eth_call');
        return '0x';
    }
    async eth_chainId(_request) {
        // EIP-695
        unimplemented('eth_chainId');
        return intToHex(0);
    }
    async eth_coinbase(_request) {
        unimplemented('eth_coinbase');
        return '0x';
    }
    async eth_compileLLL(_request, _code) {
        unsupported('eth_compileLLL');
        return '0x';
    }
    async eth_compileSerpent(_request, _code) {
        unsupported('eth_compileSerpent');
        return '0x';
    }
    async eth_compileSolidity(_request, _code) {
        unsupported('eth_compileSolidity');
        return '0x';
    }
    async eth_estimateGas(_request, _transaction, _blockNumber) {
        // See: https://www.trufflesuite.com/docs/truffle/reference/configuration
        return intToHex(6721975); // 0x6691b7
    }
    async eth_gasPrice(_request) {
        unimplemented('eth_gasPrice');
        return '0x';
    }
    async eth_getBalance(_request, _address, _blockNumber) {
        unimplemented('eth_getBalance');
        return intToHex(0);
    }
    async eth_getBlockByHash(_request, _blockHash, _fullObject) {
        unimplemented('eth_getBlockByHash');
        return null;
    }
    async eth_getBlockByNumber(_request, _blockNumber, _fullObject) {
        unimplemented('eth_getBlockByNumber');
        return null;
    }
    async eth_getBlockTransactionCountByHash(_request, _blockHash) {
        unimplemented('eth_getBlockTransactionCountByHash');
        return null;
    }
    async eth_getBlockTransactionCountByNumber(_request, _blockNumber) {
        unimplemented('eth_getBlockTransactionCountByNumber');
        return null;
    }
    async eth_getCode(_request, _address, _blockNumber) {
        unimplemented('eth_getCode');
        return '0x';
    }
    async eth_getCompilers(_request) {
        return [];
    }
    async eth_getFilterChanges(_request, _filterID) {
        unimplemented('eth_getFilterChanges');
        return [];
    }
    async eth_getFilterLogs(_request, _filterID) {
        unimplemented('eth_getFilterLogs');
        return [];
    }
    async eth_getLogs(_request, _filter) {
        unimplemented('eth_getLogs');
        return [];
    }
    async eth_getProof(_request, _address, _keys, _blockNumber) {
        // EIP-1186
        unsupported('eth_getProof'); // EIP-1186 TODO?
        return {};
    }
    async eth_getStorageAt(_request, _address, _key, _blockNumber) {
        unimplemented('eth_getStorageAt');
        return '0x';
    }
    async eth_getTransactionByBlockHashAndIndex(_request, _blockHash, _transactionIndex) {
        unimplemented('eth_getTransactionByBlockHashAndIndex');
        return null;
    }
    async eth_getTransactionByBlockNumberAndIndex(_request, _blockNumber, _transactionIndex) {
        unimplemented('eth_getTransactionByBlockNumberAndIndex');
        return null;
    }
    async eth_getTransactionByHash(_request, _transactionHash) {
        unimplemented('eth_getTransactionByHash');
        return null;
    }
    async eth_getTransactionCount(_request, _address, _blockNumber) {
        unimplemented('eth_getTransactionCount');
        return intToHex(0);
    }
    async eth_getTransactionReceipt(_request, _transactionHash) {
        unimplemented('eth_getTransactionReceipt');
        return null;
    }
    async eth_getUncleByBlockHashAndIndex(_request, _blockHash, _uncleIndex) {
        return null; // uncle blocks are never found
    }
    async eth_getUncleByBlockNumberAndIndex(_request, _blockNumber, _uncleIndex) {
        return null; // uncle blocks are never found
    }
    async eth_getUncleCountByBlockHash(_request, _blockHash) {
        unimplemented('eth_getUncleCountByBlockHash');
        return null;
    }
    async eth_getUncleCountByBlockNumber(_request, _blockNumber) {
        unimplemented('eth_getUncleCountByBlockNumber');
        return null;
    }
    async eth_getWork(_request) {
        unsupported('eth_getWork');
        return [];
    }
    async eth_hashrate(_request) {
        return intToHex(0);
    }
    async eth_mining(_request) {
        return false;
    }
    async eth_newBlockFilter(_request) {
        unimplemented('eth_newBlockFilter');
        return intToHex(0);
    }
    async eth_newFilter(_request, _filter) {
        unimplemented('eth_newFilter');
        return intToHex(0);
    }
    async eth_newPendingTransactionFilter(_request) {
        return intToHex(0); // designates the empty filter
    }
    async eth_pendingTransactions(_request) {
        // undocumented
        return [];
    }
    async eth_protocolVersion(_request) {
        return intToHex(0x41);
    }
    async eth_sendRawTransaction(_request, _transaction) {
        unimplemented('eth_sendRawTransaction');
        return '0x';
    }
    async eth_sendTransaction(_request, _transaction) {
        unsupported('eth_sendTransaction');
        return '0x';
    }
    async eth_sign(_request, _account, _message) {
        unsupported('eth_sign'); // no private keys under management here
        return '0x';
    }
    async eth_signTransaction(_request, _transaction) {
        unsupported('eth_signTransaction'); // no private keys under management here
        return '0x';
    }
    async eth_signTypedData(_request, _address, _data) {
        // EIP-712
        unsupported('eth_signTypedData'); // no private keys under management here
        return '0x';
    }
    async eth_submitHashrate(_request, _hashrate, _clientID) {
        unsupported('eth_submitHashrate');
        return false;
    }
    async eth_submitWork(_request, _nonce, _powHash, _mixDigest) {
        unsupported('eth_submitWork');
        return false;
    }
    async eth_subscribe(_request, _subsciptionType, _filter) {
        unimplemented('eth_subscribe');
        return '0x';
    }
    async eth_syncing(_request) {
        return false;
    }
    async eth_uninstallFilter(_request, _filterID) {
        unimplemented('eth_uninstallFilter');
        return false;
    }
    async eth_unsubscribe(_request, _subsciptionId) {
        unimplemented('eth_unsubscribe');
        return false;
    }
    // @see {@link https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content}
    async txpool_content(_request) {
        return { pending: {}, queued: {} };
    }
    // @see {@link https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect}
    async txpool_inspect(_request) {
        return { pending: {}, queued: {} };
    }
    // @see {@link https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status}
    async txpool_status(_request) {
        return { pending: 0, queued: 0 };
    }
    // @see {@link https://openethereum.github.io/JSONRPC-parity-module#parity_pendingtransactions}
    async parity_pendingTransactions(_request, _limit, _filter) {
        return [];
    }
}


text:
/// <reference types="node" />
import { SkeletonServer } from './skeleton.js';
import { Bus } from '../bus.js';
import { pg } from '../database.js';
import { Request } from '../request.js';
import * as web3 from '../web3.js';
export declare class DatabaseServer extends SkeletonServer {
    protected pgClient?: pg.Client;
    protected bus?: Bus;
    protected _init(): Promise<void>;
    protected _query(query: string | /*sql.SelectStatement*/ any, args?: unknown[]): Promise<pg.QueryResult<any>>;
    eth_blockNumber(_request: Request): Promise<web3.Quantity>;
    eth_call(_request: Request, transaction: web3.TransactionForCall, blockNumberOrHash?: web3.Quantity | web3.Tag | web3.Data): Promise<web3.Data>;
    eth_chainId(_request: Request): Promise<web3.Quantity>;
    eth_coinbase(_request: Request): Promise<web3.Data>;
    eth_gasPrice(_request: Request): Promise<web3.Quantity>;
    eth_getBalance(_request: Request, address: web3.Data, blockNumber?: web3.Quantity | web3.Tag): Promise<web3.Quantity>;
    eth_getBlockByHash(_request: Request, blockHash: web3.Data, fullObject?: boolean): Promise<web3.BlockResult | null>;
    eth_getBlockByNumber(_request: Request, blockNumber: web3.Quantity | web3.Tag, fullObject?: boolean): Promise<web3.BlockResult | null>;
    eth_getBlockTransactionCountByHash(_request: Request, blockHash: web3.Data): Promise<web3.Quantity | null>;
    eth_getBlockTransactionCountByNumber(_request: Request, blockNumber: web3.Quantity | web3.Tag): Promise<web3.Quantity | null>;
    eth_getCode(_request: Request, address: web3.Data, blockNumber: web3.Quantity | web3.Tag): Promise<web3.Data>;
    eth_getFilterChanges(_request: Request, filterID: web3.Quantity): Promise<web3.LogObject[]>;
    eth_getFilterLogs(_request: Request, filterID: web3.Quantity): Promise<web3.LogObject[]>;
    eth_getLogs(_request: Request, filter: web3.FilterOptions): Promise<web3.LogObject[]>;
    eth_getStorageAt(_request: Request, address: web3.Data, key: web3.Quantity, blockNumber: web3.Quantity | web3.Tag): Promise<web3.Data>;
    eth_getTransactionByBlockHashAndIndex(_request: Request, blockHash: web3.Data, transactionIndex: web3.Quantity): Promise<web3.TransactionResult | null>;
    eth_getTransactionByBlockNumberAndIndex(_request: Request, blockNumber: web3.Quantity | web3.Tag, transactionIndex: web3.Quantity): Promise<web3.TransactionResult | null>;
    eth_getTransactionByHash(_request: Request, transactionHash: web3.Data): Promise<web3.TransactionResult | null>;
    eth_getTransactionCount(_request: Request, address: web3.Data, blockNumber: web3.Quantity | web3.Tag): Promise<web3.Quantity>;
    eth_getTransactionReceipt(_request: Request, transactionHash: string): Promise<web3.TransactionReceipt | null>;
    eth_getUncleCountByBlockHash(_request: Request, blockHash: web3.Data): Promise<web3.Quantity | null>;
    eth_getUncleCountByBlockNumber(_request: Request, blockNumber: web3.Quantity | web3.Tag): Promise<web3.Quantity | null>;
    eth_newBlockFilter(_request: Request): Promise<web3.Quantity>;
    eth_newFilter(_request: Request, filter: web3.FilterOptions): Promise<web3.Quantity>;
    eth_newPendingTransactionFilter(_request: Request): Promise<web3.Quantity>;
    eth_sendRawTransaction(request: any, transaction: web3.Data): Promise<web3.Data>;
    eth_subscribe(_request: Request, _subsciptionType: web3.Data, _filter: any): Promise<web3.Data>;
    eth_uninstallFilter(_request: Request, filterID: web3.Quantity): Promise<boolean>;
    eth_unsubscribe(_request: Request, _subsciptionId: web3.Data): Promise<boolean>;
    protected _updatePollBlock(filterID_: Buffer): Promise<void>;
    protected _getFilterChangesEvent(filter: {
        addresses?: string;
        poll_block: web3.Quantity;
        to_block?: web3.Quantity;
        topics?: web3.FilterTopic[];
        type: 'block' | 'event' | 'transaction';
    }, filterID_: Buffer): Promise<web3.LogObject[]>;
    protected _fetchCurrentBlockID(): Promise<bigint>;
    protected _fetchEvents(transactionID: Uint8Array): Promise<unknown[]>;
    protected _fetchTransactions(blockID: bigint | number | Uint8Array, fullObject: boolean): Promise<unknown[] | string[]>;
    protected _getLogs(filter: web3.FilterOptions): Promise<web3.LogObject[]>;
}