diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts index 47704f993..591af3f90 100644 --- a/backend/src/api/mining/mining-routes.ts +++ b/backend/src/api/mining/mining-routes.ts @@ -27,6 +27,7 @@ class MiningRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlockPrediction) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp) ; } @@ -252,6 +253,29 @@ class MiningRoutes { res.status(500).send(e instanceof Error ? e.message : e); } } + + private async $getHeightFromTimestamp(req: Request, res: Response) { + try { + const timestamp = parseInt(req.params.timestamp, 10); + // This will prevent people from entering milliseconds etc. + // Block timestamps are allowed to be up to 2 hours off, so 24 hours + // will never put the maximum value before the most recent block + const nowPlus1day = Math.floor(Date.now() / 1000) + 60 * 60 * 24; + // Prevent non-integers that are not seconds + if (!/^[1-9][0-9]*$/.test(req.params.timestamp) || timestamp > nowPlus1day) { + throw new Error(`Invalid timestamp, value must be Unix seconds`); + } + const result = await BlocksRepository.$getBlockHeightFromTimestamp( + timestamp, + ); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.json(result); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } } export default new MiningRoutes(); diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 40f670833..590e9de37 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -392,6 +392,36 @@ class BlocksRepository { } } + /** + * Get the first block at or directly after a given timestamp + * @param timestamp number unix time in seconds + * @returns The height and timestamp of a block (timestamp might vary from given timestamp) + */ + public async $getBlockHeightFromTimestamp( + timestamp: number, + ): Promise<{ height: number; hash: string; timestamp: number }> { + try { + // Get first block at or after the given timestamp + const query = `SELECT height, hash, blockTimestamp as timestamp FROM blocks + WHERE blockTimestamp <= FROM_UNIXTIME(?) + ORDER BY blockTimestamp DESC + LIMIT 1`; + const params = [timestamp]; + const [rows]: any[][] = await DB.query(query, params); + if (rows.length === 0) { + throw new Error(`No block was found before timestamp ${timestamp}`); + } + + return rows[0]; + } catch (e) { + logger.err( + 'Cannot get block height from timestamp from the db. Reason: ' + + (e instanceof Error ? e.message : e), + ); + throw e; + } + } + /** * Return blocks height */