import { andThen, cond, filter, map, pick, pipeWith, reduceBy } from "ramda"; import deepMerge from "deepmerge"; import { getBlockHeight, updateBlockHeight } from "~/off-chain-storage/chainMetadata"; import { fetchRequestsByChainIdAndStatus, saveRequests, updateRequest } from "~/off-chain-storage/requests"; import { Status } from "~/on-chain-api/home-chain/entities"; import * as P from "~/shared/promise"; const asyncPipe = pipeWith((f, res) => andThen(f, P.resolve(res))); const mergeOnChainOntoOffChain = ([offChainRequest, onChainRequest]) => deepMerge(offChainRequest, onChainRequest); export default async function checkNotifiedRequests({ homeChainApi }) { const chainId = await homeChainApi.getChainId(); await checkUntrackedNotifiedRequests(); await checkCurrentNotifiedRequests(); async function checkUntrackedNotifiedRequests() { const [fromBlock, toBlock] = await P.all([getBlockHeight("NOTIFIED_REQUESTS"), homeChainApi.getBlockNumber()]); const untrackedNotifiedRequests = filter( ({ status }) => status !== Status.None, await homeChainApi.getNotifiedRequests({ fromBlock, toBlock }) ); await saveRequests(untrackedNotifiedRequests); const blockHeight = toBlock + 1; await updateBlockHeight({ key: "NOTIFIED_REQUESTS", blockHeight }); console.info({ blockHeight }, "Set NOTIFIED_REQUESTS block height"); const stats = { data: map(pick(["questionId", "chainId", "status"]), untrackedNotifiedRequests), fromBlock, toBlock, }; console.info(stats, "Found new notified arbitration requests"); return stats; } async function checkCurrentNotifiedRequests() { const requestNotified = ([_, onChainArbitration]) => onChainArbitration.status === Status.Notified; const handleNotifiedRequest = asyncPipe([ mergeOnChainOntoOffChain, homeChainApi.handleNotifiedRequest, (request) => ({ action: "NOTIFIED_REQUEST_HANDLED", payload: request, }), ]); const requestStatusChanged = ([offChainRequest, onChainRequest]) => offChainRequest.status != onChainRequest.status; const updateOffChainRequest = asyncPipe([ mergeOnChainOntoOffChain, updateRequest, (request) => ({ action: "STATUS_CHANGED", payload: request, }), ]); const noop = asyncPipe([ mergeOnChainOntoOffChain, (request) => ({ action: "NO_OP", payload: request, }), ]); const pipeline = asyncPipe([ fetchOnChainCounterpart, cond([ [requestNotified, handleNotifiedRequest], [requestStatusChanged, updateOffChainRequest], [() => true, noop], ]), ]); const notifiedRequests = await fetchRequestsByChainIdAndStatus({ status: Status.Notified, chainId }); console.info({ data: map(pick(["questionId", "requester"]), notifiedRequests) }, "Fetched notified requests"); const results = await P.allSettled(map(pipeline, notifiedRequests)); const groupQuestionsOrErrorMessage = (acc, r) => { if (r.status === "rejected") { return [...acc, r.reason?.message]; } const questionId = r.value?.payload?.questionId ?? "<not set>"; const requester = r.value?.payload?.requester ?? "<not set>"; return [...acc, { questionId, requester }]; }; const toTag = (r) => (r.status === "rejected" ? "FAILURE" : r.value?.action); const stats = reduceBy(groupQuestionsOrErrorMessage, [], toTag, results); console.info(stats, "Processed notified requests"); return stats; } async function fetchOnChainCounterpart(offChainRequest) { const { questionId, requester } = offChainRequest; const onChainRequest = await homeChainApi.getRequest({ questionId, requester }); return [offChainRequest, onChainRequest]; } }