From 4c106903df6464fa23c1b74978e08f9eddc878a2 Mon Sep 17 00:00:00 2001 From: Maxime Cyr <2829180+Redm4x@users.noreply.github.com> Date: Thu, 9 Nov 2023 14:29:25 -0500 Subject: [PATCH] Add prediction endpoints (#46) * Add prediction endpoints * . --- api/src/db/blocksProvider.ts | 69 ++++++++++++++++++++++++++++-------- api/src/routers/apiRouter.ts | 57 +++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 17 deletions(-) diff --git a/api/src/db/blocksProvider.ts b/api/src/db/blocksProvider.ts index 0775bfed8..f60ac6d4e 100644 --- a/api/src/db/blocksProvider.ts +++ b/api/src/db/blocksProvider.ts @@ -1,7 +1,6 @@ import { AkashBlock as Block, AkashMessage as Message } from "@shared/dbSchemas/akash"; import { Transaction, Validator } from "@shared/dbSchemas/base"; -import { averageBlockTime } from "@src/utils/constants"; -import { add } from "date-fns"; +import { addSeconds, differenceInSeconds } from "date-fns"; export async function getBlocks(limit: number) { const _limit = Math.min(limit, 100); @@ -32,14 +31,6 @@ export async function getBlocks(limit: number) { } export async function getBlock(height: number) { - // const latestBlock = await Block.findOne({ - // order: [["height", "DESC"]] - // }); - - // if (height > latestBlock.height) { - // return getFutureBlockEstimate(height, latestBlock); - // } - const block = await Block.findOne({ where: { height: height @@ -87,9 +78,57 @@ export async function getBlock(height: number) { }; } -async function getFutureBlockEstimate(height: number, latestBlock: Block) { - return { - height: height, - expectedDate: add(latestBlock.datetime, { seconds: (height - latestBlock.height) * averageBlockTime }) - }; +/** + * Calculate the estimated block time + * @param latestBlock Block to calculate the average from + * @param blockCount Block interval for calculating the average + * @returns Average block time in seconds + */ +async function calculateAverageBlockTime(latestBlock: Block, blockCount: number) { + if (blockCount <= 1) throw new Error("blockCount must be greater than 1"); + + const earlierBlock = await Block.findOne({ + where: { + height: Math.max(latestBlock.height - blockCount, 1) + } + }); + + const realBlockCount = latestBlock.height - earlierBlock.height; + + return differenceInSeconds(latestBlock.datetime, earlierBlock.datetime) / realBlockCount; +} + +/** + * Get the predicted height at a given date + * @param date Date to predict the height of + * @param blockWindow Block interval for calculating the average + * @returns Predicted height at the given date + */ +export async function getPredictedDateHeight(date: Date, blockWindow: number) { + const latestBlock = await Block.findOne({ order: [["height", "DESC"]] }); + + if (date <= latestBlock.datetime) throw new Error("Date must be in the future"); + + const averageBlockTime = await calculateAverageBlockTime(latestBlock, blockWindow); + + const dateDiff = differenceInSeconds(date, latestBlock.datetime); + + return Math.floor(latestBlock.height + dateDiff / averageBlockTime); +} + +/** + * Get the predicted date at a given height + * @param height Height to predict the date of + * @param blockWindow Block window for calculating the average + * @returns Predicted date at the given height + */ +export async function getPredictedBlockDate(height: number, blockWindow: number) { + const latestBlock = await Block.findOne({ order: [["height", "DESC"]] }); + + if (height <= latestBlock.height) throw new Error("Height must be in the future"); + + const averageBlockTime = await calculateAverageBlockTime(latestBlock, blockWindow); + + const heightDiff = height - latestBlock.height; + return addSeconds(latestBlock.datetime, heightDiff * averageBlockTime); } diff --git a/api/src/routers/apiRouter.ts b/api/src/routers/apiRouter.ts index c2bac03f2..bde04863f 100644 --- a/api/src/routers/apiRouter.ts +++ b/api/src/routers/apiRouter.ts @@ -1,5 +1,5 @@ import express from "express"; -import { getBlock, getBlocks } from "@src/db/blocksProvider"; +import { getBlock, getBlocks, getPredictedBlockDate, getPredictedDateHeight } from "@src/db/blocksProvider"; import { getTemplateGallery } from "@src/providers/templateReposProvider"; import { getTransaction, getTransactionByAddress, getTransactions } from "@src/db/transactionsProvider"; import { @@ -65,6 +65,59 @@ apiRouter.get( }) ); +apiRouter.get( + "/predicted-block-date/:height/:blockWindow?", + asyncHandler(async (req, res) => { + const height = parseInt(req.params.height); + const blockWindow = req.params.blockWindow ? parseInt(req.params.blockWindow) : 10_000; + + if (isNaN(height)) { + res.status(400).send("Invalid height."); + return; + } + + if (isNaN(blockWindow)) { + res.status(400).send("Invalid block window."); + return; + } + + const date = await getPredictedBlockDate(height, blockWindow); + + res.send({ + predictedDate: date, + height: height, + blockWindow: blockWindow + }); + }) +); + +apiRouter.get( + "/predicted-date-height/:timestamp/:blockWindow?", + asyncHandler(async (req, res) => { + const timestamp = parseInt(req.params.timestamp); + const blockWindow = req.params.height ? parseInt(req.params.blockWindow) : 10_000; + + if (isNaN(timestamp)) { + res.status(400).send("Invalid timestamp."); + return; + } + + if (isNaN(blockWindow)) { + res.status(400).send("Invalid block window."); + return; + } + + const date = new Date(timestamp * 1000); + const height = await getPredictedDateHeight(date, blockWindow); + + res.send({ + predictedHeight: height, + date: date, + blockWindow: blockWindow + }); + }) +); + apiRouter.get( "/transactions", asyncHandler(async (req, res) => { @@ -265,7 +318,7 @@ apiRouter.get( const chainStats = { height: latestBlocks[0].height, transactionCount: latestBlocks[0].totalTransactionCount, - ...(await chainStatsQuery) + ...chainStatsQuery }; res.send({