envio
Version:
A latency and sync speed optimized, developer friendly blockchain data indexer.
558 lines (545 loc) • 16.9 kB
JavaScript
// 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 */