UNPKG

envio

Version:

A latency and sync speed optimized, developer friendly blockchain data indexer.

359 lines (352 loc) • 14.2 kB
// Generated by ReScript, PLEASE EDIT WITH CARE import * as Utils from "../Utils.res.mjs"; import * as Hrtime from "../bindings/Hrtime.res.mjs"; import * as Source from "./Source.res.mjs"; import * as Logging from "../Logging.res.mjs"; import * as HyperSync from "./HyperSync.res.mjs"; import * as Prometheus from "../Prometheus.res.mjs"; import * as EventRouter from "./EventRouter.res.mjs"; import * as LogSelection from "../LogSelection.res.mjs"; import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js"; import * as ErrorHandling from "../ErrorHandling.res.mjs"; import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js"; import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js"; import * as HyperSyncClient from "./HyperSyncClient.res.mjs"; import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; import * as HyperSyncHeightStream from "./HyperSyncHeightStream.res.mjs"; function getSelectionConfig(selection, chain) { let capitalizedBlockFields = new Set(); let capitalizedTransactionFields = new Set(); let staticTopicSelectionsByContract = {}; let dynamicEventFiltersByContract = {}; let dynamicWildcardEventFiltersByContract = {}; let noAddressesTopicSelections = []; let contractNames = new Set(); selection.eventConfigs.forEach(param => { let contractName = param.contractName; Array.from(param.selectedBlockFields).forEach(name => { capitalizedBlockFields.add(Utils.$$String.capitalize(name)); }); Array.from(param.selectedTransactionFields).forEach(name => { capitalizedTransactionFields.add(Utils.$$String.capitalize(name)); }); let eventFilters = param.getEventFiltersOrThrow(chain); if (param.dependsOnAddresses) { contractNames.add(contractName); if (eventFilters.TAG === "Static") { return Utils.Dict.pushMany(staticTopicSelectionsByContract, contractName, eventFilters._0); } else { return Utils.Dict.push(param.isWildcard ? dynamicWildcardEventFiltersByContract : dynamicEventFiltersByContract, contractName, eventFilters._0); } } let tmp; tmp = eventFilters.TAG === "Static" ? eventFilters._0 : eventFilters._0([]); noAddressesTopicSelections.push(...tmp); }); let fieldSelection_block = Array.from(capitalizedBlockFields); let fieldSelection_transaction = Array.from(capitalizedTransactionFields); let fieldSelection_log = [ "Address", "Data", "LogIndex", "Topic0", "Topic1", "Topic2", "Topic3" ]; let fieldSelection = { block: fieldSelection_block, transaction: fieldSelection_transaction, log: fieldSelection_log }; let noAddressesLogSelection = LogSelection.make([], noAddressesTopicSelections); let getLogSelectionOrThrow = addressesByContractName => { let logSelections = []; if (!Utils.$$Array.isEmpty(noAddressesLogSelection.topicSelections)) { logSelections.push(noAddressesLogSelection); } contractNames.forEach(contractName => { let addresses = addressesByContractName[contractName]; if (addresses === undefined) { return; } if (addresses.length === 0) { return; } let topicSelections = staticTopicSelectionsByContract[contractName]; if (topicSelections !== undefined) { logSelections.push(LogSelection.make(addresses, topicSelections)); } let fns = dynamicEventFiltersByContract[contractName]; if (fns !== undefined) { logSelections.push(LogSelection.make(addresses, fns.flatMap(fn => fn(addresses)))); } let fns$1 = dynamicWildcardEventFiltersByContract[contractName]; if (fns$1 !== undefined) { logSelections.push(LogSelection.make([], fns$1.flatMap(fn => fn(addresses)))); return; } }); return logSelections; }; return { getLogSelectionOrThrow: getLogSelectionOrThrow, fieldSelection: fieldSelection }; } function memoGetSelectionConfig(chain) { return Utils.$$WeakMap.memoize(selection => getSelectionConfig(selection, chain)); } function isUnauthorizedError(message) { return message.includes("401 Unauthorized"); } function make(param) { let apiToken = param.apiToken; let eventRouter = param.eventRouter; let endpointUrl = param.endpointUrl; let chain = param.chain; let name = "HyperSync"; let getSelectionConfig = memoGetSelectionConfig(chain); let apiToken$1 = apiToken !== undefined ? apiToken : Stdlib_JsError.throwWithMessage(`An Envio API token is required for using HyperSync as a data-source. Set the ENVIO_API_TOKEN environment variable in your .env file. Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`); let client; try { client = HyperSyncClient.make(endpointUrl, apiToken$1, param.clientTimeoutMillis, param.allEventParams, !param.lowercaseAddresses, param.serializationFormat, param.enableQueryCaching, undefined, undefined, undefined, param.logLevel); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); client = ErrorHandling.mkLogAndRaise(undefined, "Failed to instantiate the hypersync client, please double check your ABI", exn); } let UndefinedValue = /* @__PURE__ */Primitive_exceptions.create("UndefinedValue"); let makeEventBatchQueueItem = (item, params, eventConfig) => { let block = item.block; let logIndex = item.logIndex; return { kind: 0, eventConfig: eventConfig, timestamp: block.timestamp, chain: chain, blockNumber: block.number, blockHash: block.hash, logIndex: logIndex, event: { contractName: eventConfig.contractName, eventName: eventConfig.name, params: params, chainId: chain, srcAddress: item.srcAddress, logIndex: logIndex, transaction: item.transaction, block: block } }; }; let getItemsOrThrow = async (fromBlock, toBlock, addressesByContractName, indexingAddresses, knownHeight, param, selection, retry, logger) => { let totalTimeRef = Hrtime.makeTimer(); let selectionConfig = getSelectionConfig(selection); let logSelections; try { logSelections = selectionConfig.getLogSelectionOrThrow(addressesByContractName); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); logSelections = ErrorHandling.mkLogAndRaise(logger, "Failed getting log selection for the query", exn); } let startFetchingBatchTimeRef = Hrtime.makeTimer(); Prometheus.SourceRequestCount.increment(name, chain, "getLogs"); let pageUnsafe; try { pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, logSelections, selectionConfig.fieldSelection); } catch (raw_error) { let error = Primitive_exceptions.internalToException(raw_error); if (error.RE_EXN_ID === HyperSync.GetLogs.$$Error) { let error$1 = error._1; let tmp; if (typeof error$1 !== "object") { let backoffMillis = retry !== 0 ? 500 * retry | 0 : 100; tmp = { TAG: "WithBackoff", message: `Block #` + fromBlock.toString() + ` not found in HyperSync. HyperSync has multiple instances and it's possible that they drift independently slightly from the head. Indexing should continue correctly after retrying the query in ` + backoffMillis.toString() + `ms.`, backoffMillis: backoffMillis }; } else { tmp = { TAG: "ImpossibleForTheQuery", message: `Source returned invalid data with missing required fields: ` + error$1.missingParams.join(", ") }; } throw { RE_EXN_ID: Source.GetItemsError, _1: { TAG: "FailedGettingItems", exn: null, attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight), retry: tmp }, Error: new Error() }; } if (error.RE_EXN_ID === Source.RateLimited) { throw error; } throw { RE_EXN_ID: Source.GetItemsError, _1: { TAG: "FailedGettingItems", exn: error, attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight), retry: { TAG: "WithBackoff", message: `Unexpected issue while fetching events from HyperSync client. Attempt a retry.`, backoffMillis: retry !== 0 ? 1000 * retry | 0 : 500 } }, Error: new Error() }; } let pageFetchTime = Hrtime.toSecondsFloat(Hrtime.timeSince(startFetchingBatchTimeRef)); let knownHeight$1 = pageUnsafe.archiveHeight; let heighestBlockQueried = pageUnsafe.nextBlock - 1 | 0; let parsingTimeRef = Hrtime.makeTimer(); let parsedQueueItems = []; pageUnsafe.items.forEach(item => { let maybeEventConfig = EventRouter.get(eventRouter, EventRouter.getEvmEventId(item.topic0, item.topicCount), item.srcAddress, item.block.number, indexingAddresses); if (maybeEventConfig === undefined) { return; } let params = Stdlib_Option.flatMap(Primitive_option.fromNullable(item.params), __x => __x[maybeEventConfig.contractName]); if (params !== undefined) { parsedQueueItems.push(makeEventBatchQueueItem(item, Primitive_option.valFromOption(params), maybeEventConfig)); return; } else { let logIndex = item.logIndex; let blockNumber = item.block.number; let exn = { RE_EXN_ID: UndefinedValue }; if (maybeEventConfig.isWildcard) { return; } let msg = `Event ` + maybeEventConfig.name + ` was unexpectedly parsed as undefined`; let logger$1 = Logging.createChildFrom(logger, { chainId: chain, blockNumber: blockNumber, logIndex: logIndex, decoder: "hypersync-client" }); return ErrorHandling.mkLogAndRaise(logger$1, msg, exn); } }); let parsingTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(parsingTimeRef)); let blockHashes = []; pageUnsafe.items.forEach(param => { let block = param.block; let match = block.number; let match$1 = block.hash; if (match !== undefined && match$1 !== undefined) { blockHashes.push({ blockHash: match$1, blockNumber: match }); return; } }); let match = pageUnsafe.rollbackGuard; if (match !== undefined) { blockHashes.push({ blockHash: match.hash, blockNumber: match.blockNumber }); blockHashes.push({ blockHash: match.firstParentHash, blockNumber: match.firstBlockNumber - 1 | 0 }); } let match$1 = pageUnsafe.rollbackGuard; let latestFetchedBlockTimestamp; if (match$1 !== undefined) { latestFetchedBlockTimestamp = match$1.timestamp; } else { let match$2 = pageUnsafe.items[pageUnsafe.items.length - 1 | 0]; if (match$2 !== undefined) { let block = match$2.block; latestFetchedBlockTimestamp = block.number === heighestBlockQueried ? block.timestamp : 0; } else { latestFetchedBlockTimestamp = 0; } } let totalTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(totalTimeRef)); let stats_parsing$unknowntime$unknown$lpars$rpar = parsingTimeElapsed; let stats_page$unknownfetch$unknowntime$unknown$lpars$rpar = pageFetchTime; let stats = { "total time elapsed (s)": totalTimeElapsed, "parsing time (s)": stats_parsing$unknowntime$unknown$lpars$rpar, "page fetch time (s)": stats_page$unknownfetch$unknowntime$unknown$lpars$rpar }; return { knownHeight: knownHeight$1, blockHashes: blockHashes, parsedQueueItems: parsedQueueItems, fromBlockQueried: fromBlock, latestFetchedBlockNumber: heighestBlockQueried, latestFetchedBlockTimestamp: latestFetchedBlockTimestamp, stats: stats }; }; let getBlockHashes = (blockNumbers, logger) => HyperSync.queryBlockDataMulti(client, blockNumbers, name, chain, logger).then(HyperSync.mapExn); return { name: name, sourceFor: "Sync", chain: chain, poweredByHyperSync: true, pollingInterval: 100, getBlockHashes: getBlockHashes, getHeightOrThrow: async () => { let timerRef = Hrtime.makeTimer(); let result; try { result = await client.getHeight(); } catch (raw_e) { let e = Primitive_exceptions.internalToException(raw_e); if (e.RE_EXN_ID === "JsExn") { let e$1 = e._1; let message = Stdlib_JsExn.message(e$1); if (message !== undefined) { if (message.includes("401 Unauthorized")) { Logging.error(`Your ENVIO_API_TOKEN was rejected by HyperSync (401 Unauthorized). The indexer will not be able to fetch events. Update the token and try again using 'envio start' or 'envio dev'. For more info: https://docs.envio.dev/docs/HyperSync/api-tokens`); await new Promise((param, param$1) => {}); result = 0; } else { throw { RE_EXN_ID: "JsExn", _1: e$1, Error: new Error() }; } } else { throw { RE_EXN_ID: "JsExn", _1: e$1, Error: new Error() }; } } else { throw e; } } let seconds = Hrtime.toSecondsFloat(Hrtime.timeSince(timerRef)); Prometheus.SourceRequestCount.increment(name, chain, "getHeight"); Prometheus.SourceRequestCount.addSeconds(name, chain, "getHeight", seconds); return result; }, getItemsOrThrow: getItemsOrThrow, createHeightSubscription: onHeight => HyperSyncHeightStream.subscribe(endpointUrl, apiToken$1, chain, onHeight) }; } export { getSelectionConfig, memoGetSelectionConfig, isUnauthorizedError, make, } /* Utils Not a pure module */