UNPKG

envio

Version:

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

558 lines (545 loc) • 16.9 kB
// Generated by ReScript, PLEASE EDIT WITH CARE import * as Hrtime from "../bindings/Hrtime.res.mjs"; import * as Source from "./Source.res.mjs"; import * as Prometheus from "../Prometheus.res.mjs"; import * as EventRouter from "./EventRouter.res.mjs"; import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js"; import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js"; import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js"; import * as SvmHyperSyncClient from "./SvmHyperSyncClient.res.mjs"; import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js"; function buildInstructionSelections(eventConfigs) { return eventConfigs.flatMap(cfg => { let programIdString = cfg.programId; if (programIdString === "") { return []; } let match = cfg.discriminator; let match$1 = cfg.discriminatorByteLen; let match$2; if (match !== undefined) { switch (match$1) { case 1 : match$2 = [ [match], undefined, undefined, undefined ]; break; case 2 : match$2 = [ undefined, [match], undefined, undefined ]; break; case 4 : match$2 = [ undefined, undefined, [match], undefined ]; break; case 8 : match$2 = [ undefined, undefined, undefined, [match] ]; break; default: match$2 = [ undefined, undefined, undefined, undefined ]; } } else { match$2 = [ undefined, undefined, undefined, undefined ]; } let d8 = match$2[3]; let d4 = match$2[2]; let d2 = match$2[1]; let d1 = match$2[0]; let gs = cfg.accountFilters; let groups = gs.length !== 0 ? gs : [[]]; return groups.map(group => { let pick = position => Stdlib_Array.filterMap(group, f => { if (f.position === position) { return f.values; } })[0]; return { programId: [programIdString], d1: d1, d2: d2, d4: d4, d8: d8, a0: pick(0), a1: pick(1), a2: pick(2), a3: pick(3), a4: pick(4), a5: pick(5), isInner: cfg.isInner }; }); }); } function synthLogIndex(instr) { let tx = instr.transactionIndex; let addrSum = Stdlib_Array.reduce(instr.instructionAddress, 0, (acc, n) => ((acc << 10) + n | 0) + 1 | 0); return (tx << 16) + addrSum | 0; } function serializeInstructionAddress(addr) { return addr.map(n => n.toString()).join(","); } function buildProgramSchemas(eventConfigs) { let descriptorsByProgram = {}; eventConfigs.forEach(ec => { let programIdString = ec.programId; if (programIdString === "") { return; } let hasSchema = ec.accounts.length !== 0 || ec.args !== null; let discriminator = Stdlib_Option.getOr(ec.discriminator, ""); if (!(hasSchema && discriminator !== "")) { return; } let other = ec.definedTypes; let definedTypes; definedTypes = other === null ? ({}) : other; let existing = descriptorsByProgram[programIdString]; let descriptor = existing !== undefined ? Primitive_option.valFromOption(existing) : ({ programId: programIdString, definedTypes: definedTypes, instructions: [] }); let instruction = { name: ec.name, discriminator: discriminator, accounts: ec.accounts, args: ec.args }; descriptorsByProgram[programIdString] = { programId: descriptor.programId, definedTypes: descriptor.definedTypes, instructions: descriptor.instructions.concat([instruction]) }; }); return Object.values(descriptorsByProgram).map(descriptor => JSON.stringify(descriptor)); } function parseDecoded(d) { let args; try { args = JSON.parse(d.argsJson); } catch (exn) { args = {}; } let accounts; try { accounts = JSON.parse(d.accountsJson); } catch (exn$1) { accounts = {}; } return { name: d.name, args: args, accounts: accounts, extraAccounts: d.extraAccounts }; } function toSvmInstruction(instr, programName, instructionName, transaction, logs, block) { return { programName: programName, instructionName: instructionName, programId: instr.programId, data: instr.data, accounts: instr.accounts, instructionAddress: instr.instructionAddress, isInner: instr.isInner, d1: instr.d1, d2: instr.d2, d4: instr.d4, d8: instr.d8, params: Stdlib_Option.map(instr.decoded, parseDecoded), transaction: transaction, logs: logs, block: block }; } function toSvmTransaction(tx) { return { signatures: tx.signatures, feePayer: Stdlib_Option.map(tx.feePayer, prim => prim), success: tx.success, err: tx.err, fee: Stdlib_Option.map(tx.fee, prim => BigInt(prim)), computeUnitsConsumed: Stdlib_Option.map(tx.computeUnitsConsumed, prim => BigInt(prim)), accountKeys: tx.accountKeys, recentBlockhash: tx.recentBlockhash, version: tx.version }; } function toSvmTokenBalance(tb) { return { account: Stdlib_Option.map(tb.account, prim => prim), mint: Stdlib_Option.map(tb.mint, prim => prim), owner: Stdlib_Option.map(tb.owner, prim => prim), preAmount: tb.preAmount, postAmount: tb.postAmount }; } function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, indexingAddresses) { let probe = dN => { let tag = EventRouter.getSvmEventId(programId, dN); return EventRouter.get(router, tag, contractAddress, instr.slot, indexingAddresses); }; let result = Stdlib_Array.reduce(byteLengthsDesc, undefined, (acc, len) => { if (acc !== undefined) { return acc; } let candidate; switch (len) { case 1 : candidate = instr.d1; break; case 2 : candidate = instr.d2; break; case 4 : candidate = instr.d4; break; case 8 : candidate = instr.d8; break; default: candidate = undefined; } if (candidate !== undefined) { return probe(candidate); } }); if (result !== undefined) { return result; } else { return probe(undefined); } } function make(param) { let eventConfigs = param.eventConfigs; let chain = param.chain; let name = "SvmHyperSync"; let programSchemas = buildProgramSchemas(eventConfigs); let client = SvmHyperSyncClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, undefined, undefined, undefined, programSchemas.length !== 0 ? programSchemas : undefined); let match = EventRouter.fromSvmEventConfigsOrThrow(eventConfigs, chain); let eventRouter = match[0]; let orderingByProgram = {}; match[1].forEach(o => { orderingByProgram[o.programId] = o.byteLengthsDesc; }); let needsTransactions = eventConfigs.some(cfg => cfg.includeTransaction); let needsLogs = eventConfigs.some(cfg => cfg.includeLogs); let needsTokenBalances = eventConfigs.some(cfg => cfg.includeTokenBalances); let getItemsOrThrow = async (fromBlock, toBlock, param, indexingAddresses, knownHeight, param$1, param$2, retry, logger) => { let totalTimeRef = Hrtime.makeTimer(); let pageFetchRef = Hrtime.makeTimer(); let instructionSelections = buildInstructionSelections(eventConfigs); let fields_block = [ "slot", "blockhash", "block_time" ]; let fields_transaction = needsTransactions ? [ "slot", "transaction_index", "signatures", "fee_payer", "success", "err", "fee", "compute_units_consumed", "account_keys", "recent_blockhash", "version" ] : undefined; let fields_log = needsLogs ? [ "slot", "transaction_index", "instruction_address", "kind", "message" ] : undefined; let fields_tokenBalance = needsTokenBalances ? [ "slot", "transaction_index", "account", "mint", "owner", "pre_amount", "post_amount" ] : undefined; let fields = { block: fields_block, transaction: fields_transaction, log: fields_log, tokenBalance: fields_tokenBalance }; let query_toSlot = Stdlib_Option.map(toBlock, toBlock => toBlock + 1 | 0); let query_instructions = instructionSelections; let query_fields = fields; let query = { fromSlot: fromBlock, toSlot: query_toSlot, instructions: query_instructions, fields: query_fields }; Prometheus.SourceRequestCount.increment(name, chain, "getInstructions"); let resp; try { resp = await client.get(query); } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); throw { RE_EXN_ID: Source.GetItemsError, _1: { TAG: "FailedGettingItems", exn: exn, attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight), retry: { TAG: "WithBackoff", message: `Unexpected issue while fetching instructions from SVM HyperSync. Attempt a retry.`, backoffMillis: retry !== 0 ? 1000 * retry | 0 : 500 } }, Error: new Error() }; } let pageFetchTime = Hrtime.toSecondsFloat(Hrtime.timeSince(pageFetchRef)); let parsingRef = Hrtime.makeTimer(); let blockTimeBySlot = {}; resp.data.blocks.forEach(b => { let t = b.blockTime; if (t !== undefined) { blockTimeBySlot[b.slot.toString()] = t; return; } }); let txByKey = {}; resp.data.transactions.forEach(tx => { let key = tx.slot.toString() + ":" + tx.transactionIndex.toString(); txByKey[key] = tx; }); let logsByKey = {}; resp.data.logs.forEach(log => { let match = log.transactionIndex; let match$1 = log.instructionAddress; if (match === undefined) { return; } if (match$1 === undefined) { return; } let key = log.slot.toString() + ":" + match.toString() + ":" + serializeInstructionAddress(match$1); let existing = logsByKey[key]; if (existing !== undefined) { existing.push(log); } else { logsByKey[key] = [log]; } }); let tokenBalancesByTx = {}; if (needsTokenBalances) { resp.data.tokenBalances.forEach(tb => { let txIdx = tb.transactionIndex; if (txIdx === undefined) { return; } let key = tb.slot.toString() + ":" + txIdx.toString(); let existing = tokenBalancesByTx[key]; if (existing !== undefined) { existing.push(tb); } else { tokenBalancesByTx[key] = [tb]; } }); } let parsedQueueItems = []; resp.data.instructions.forEach(instr => { let programId = instr.programId; let byteLengths = Stdlib_Option.getOr(orderingByProgram[instr.programId], []); let contractAddress = instr.programId; let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, indexingAddresses); if (maybeConfig !== undefined) { let txKey = instr.slot.toString() + ":" + instr.transactionIndex.toString(); let maybeTx = Stdlib_Option.map(txByKey[txKey], toSvmTransaction); let maybeTx$1 = maybeConfig.includeTokenBalances ? Stdlib_Option.map(maybeTx, tx => { let maybeBalances = Stdlib_Option.map(tokenBalancesByTx[txKey], bals => bals.map(toSvmTokenBalance)); let newrecord = {...tx}; newrecord.tokenBalances = maybeBalances; return newrecord; }) : maybeTx; let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress); let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({ kind: Stdlib_Option.getOr(log.kind, ""), message: Stdlib_Option.getOr(log.message, "") }))); let slotKey = instr.slot.toString(); let blockTime = blockTimeBySlot[slotKey]; let payload = toSvmInstruction(instr, maybeConfig.contractName, maybeConfig.name, maybeConfig.includeTransaction ? maybeTx$1 : undefined, maybeConfig.includeLogs ? maybeLogs : undefined, { slot: instr.slot, time: Stdlib_Option.getOr(blockTime, 0), hash: "" }); parsedQueueItems.push({ kind: 0, eventConfig: maybeConfig, timestamp: Stdlib_Option.getOr(blockTime, 0), chain: chain, blockNumber: instr.slot, blockHash: "", logIndex: synthLogIndex(instr), event: payload }); } }); let parsingTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(parsingRef)); let highestSlot = resp.nextSlot - 1 | 0; let latestBlockTime = Stdlib_Option.getOr(blockTimeBySlot[highestSlot.toString()], 0); let blockHashes = resp.data.blocks.map(b => ({ blockHash: b.blockhash, blockNumber: b.slot })); let totalTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(totalTimeRef)); return { knownHeight: knownHeight, blockHashes: blockHashes, parsedQueueItems: parsedQueueItems, fromBlockQueried: fromBlock, latestFetchedBlockNumber: highestSlot, latestFetchedBlockTimestamp: latestBlockTime, stats: { "total time elapsed (s)": totalTimeElapsed, "parsing time (s)": parsingTimeElapsed, "page fetch time (s)": pageFetchTime } }; }; let queryBlockDataRange = async (fromSlot, toSlot) => { let blockDatas = []; let fromRef = fromSlot; let keepGoing = true; while (keepGoing) { let query_toSlot = toSlot + 1 | 0; let query_includeAllBlocks = true; let query_fields = { block: [ "slot", "blockhash", "block_time" ] }; let query_maxNumBlocks = 1000; let query = { fromSlot: fromRef, toSlot: query_toSlot, includeAllBlocks: query_includeAllBlocks, fields: query_fields, maxNumBlocks: query_maxNumBlocks }; Prometheus.SourceRequestCount.increment(name, chain, "getBlockHashes"); let resp = await client.get(query); resp.data.blocks.forEach(b => { blockDatas.push({ blockHash: b.blockhash, blockNumber: b.slot, blockTimestamp: Stdlib_Option.getOr(b.blockTime, 0) }); }); if (resp.nextSlot > toSlot || resp.nextSlot <= fromRef) { keepGoing = false; } else { fromRef = resp.nextSlot; } }; return blockDatas; }; let getBlockHashes = async (blockNumbers, param) => { let firstSlot = blockNumbers[0]; if (firstSlot === undefined) { return { TAG: "Ok", _0: [] }; } try { let minSlot = { contents: firstSlot }; let maxSlot = { contents: firstSlot }; let requested = new Set(); blockNumbers.forEach(slot => { if (slot < minSlot.contents) { minSlot.contents = slot; } if (slot > maxSlot.contents) { maxSlot.contents = slot; } requested.add(slot); }); let blockDatas = await queryBlockDataRange(minSlot.contents, maxSlot.contents); return { TAG: "Ok", _0: blockDatas.filter(data => requested.delete(data.blockNumber)) }; } catch (raw_exn) { let exn = Primitive_exceptions.internalToException(raw_exn); return { TAG: "Error", _0: exn }; } }; return { name: name, sourceFor: "Sync", chain: chain, poweredByHyperSync: true, pollingInterval: 1000, getBlockHashes: getBlockHashes, getHeightOrThrow: async () => { let timer = Hrtime.makeTimer(); let h = await client.getHeight(); let seconds = Hrtime.toSecondsFloat(Hrtime.timeSince(timer)); Prometheus.SourceRequestCount.increment(name, chain, "getHeight"); Prometheus.SourceRequestCount.addSeconds(name, chain, "getHeight", seconds); return h; }, getItemsOrThrow: getItemsOrThrow }; } export { buildInstructionSelections, synthLogIndex, serializeInstructionAddress, buildProgramSchemas, parseDecoded, toSvmInstruction, toSvmTransaction, toSvmTokenBalance, probeRouter, make, } /* Prometheus Not a pure module */