Skip to content

Commit

Permalink
add price history base report
Browse files Browse the repository at this point in the history
  • Loading branch information
DZariusz committed Feb 19, 2024
1 parent 2680942 commit 3821758
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 32 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## Unreleased

## [5.10.0] - 2024-02-15
### Added
- add price history base report

## [5.9.16] - 2024-02-13
### Added
- add `linea` to list of chains
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sanctuary",
"version": "5.9.16",
"version": "5.10.0",
"repository": {
"type": "git",
"url": "git+https://github.com/umbrella-network/sanctuary.git"
Expand Down
128 changes: 109 additions & 19 deletions src/controllers/OnChainReportsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ChainsIds } from '../types/ChainsIds';
import { IUpdateTx } from '../models/UpdateTx';
import { PriceDataRepository } from '../repositories/PriceDataRepository';
import { ValidatorWalletsRepository } from '../repositories/ValidatorWalletsRepository';
import { IPriceDataRaw } from '../models/PriceData';

type UpdateTxData = {
failed: number;
Expand All @@ -30,27 +31,11 @@ export class OnChainReportsController {
setup(): void {
this.router = Router()
.get('/monthly-expenses/:chainId/:year/:month', this.monthlyExpenses)
.get('/price-history/:key', this.priceHistory)
.get('/price-history/:chainId/:key', this.priceHistory);
// .get('/price-history/:key', this.priceHistory)
.get('/price-history/:chainId/:key', this.priceHistory)
.get('/price-history/:chainId/:key/last/:days/days', this.priceHistory);
}

priceHistory = async (request: Request, response: Response): Promise<void> => {
const chainId = request.params.chainId as ChainsIds;
const key = request.params.key;

try {
const prices = await this.priceDataRepository.lastPrices(chainId, key, 7);
this.logger.info(`[priceHistory][${chainId}] got ${prices.length} records`);

response
// .type('application/text')
.send(prices);
} catch (e) {
this.logger.error(e);
response.status(500).send(e.message);
}
};

monthlyExpenses = async (request: Request, response: Response): Promise<void> => {
const chainId = request.params.chainId as ChainsIds;

Expand Down Expand Up @@ -93,6 +78,111 @@ export class OnChainReportsController {
}
};

priceHistory = async (request: Request, response: Response): Promise<void> => {
const chainId = request.params.chainId as ChainsIds;
const key = request.params.key;
let days = parseInt(request.params.days || '3');
const daysLimit = 30;

if (days > daysLimit) {
this.logger.warning(`[priceHistory][${chainId}] days limit is ${daysLimit}`);
days = daysLimit;
}

try {
const prices = await this.priceDataRepository.lastPrices(chainId, key, days);
this.logger.info(`[priceHistory][${chainId}] got ${prices.length} records`);
const history = await this.printOnChainHistory(chainId, key, prices);

response
// .type('application/text')
.send(history.join('<br/>\n'));
} catch (e) {
this.logger.error(e);
response.status(500).send(e.message);
}
};

private printOnChainHistory = async (
chainId: ChainsIds,
key: string,
feedsHistory: IPriceDataRaw[]
): Promise<string[]> => {
const results: string[] = [];
const separator = ';';

results.push(
[
'key',
'timestamp of the feed',
'how long it took from start of consensus to mint tx',
'tx hash',
'price',
'price difference',
'previous heartbeat',
'[%] of heartbeat used',
'rounds left: how many rounds was left till the end of heartbeat',
'overshoot: if we not deliver in time, overshoot time is presented in negative [seconds]',
].join(separator)
);

let prev = {
...feedsHistory[feedsHistory.length > 0 ? 1 : 0],
};

const txMap = await this.getTxMap(chainId, feedsHistory);

feedsHistory.forEach((p, i) => {
const txTimestamp = txMap[p.tx];
const price = BigInt(p.value);
const prevPrice = BigInt(prev.value);

const priceDiff = Number(((price - prevPrice) * 10000n) / prevPrice) / 100;
const hDiffSec = p.timestamp - prev.timestamp;
const hDiff = Math.round((hDiffSec * 10000) / prev.heartbeat) / 100;
const hDiffRounds = hDiff <= 100 ? Math.floor((prev.heartbeat - hDiffSec) / 60) : -1;
const mintTime = txTimestamp ? txTimestamp - p.timestamp : '';
const overshoot = hDiffRounds < 0 ? `${prev.heartbeat - hDiffSec}` : '';

results.push(
[
key,
new Date(p.timestamp * 1000).toISOString(),
mintTime.toString(),
p.tx,
Number(p.value) / 1e8,
`${priceDiff}%`,
prev.heartbeat,
`${hDiff}%`,
overshoot,
`${hDiffRounds < 0 ? '- !!! -' : hDiffRounds}`,
].join(';')
);

prev = {
...(feedsHistory[i + 2] || feedsHistory[i + 1]),
};
});

return results;
};

private getTxMap = async (chainId: ChainsIds, feedsHistory: IPriceDataRaw[]): Promise<Record<string, number>> => {
const txs = await this.updateTxRepository.find(
chainId,
feedsHistory.map((k) => k.tx),
{ txTimestamp: 1 }
);

const txMap: Record<string, number> = {};

txs.forEach((tx) => {
txMap[tx._id] = Math.trunc(tx.txTimestamp.getTime() / 1000);
});

return txMap;
};

/*
[
'wallet: who submit',
Expand Down
2 changes: 0 additions & 2 deletions src/factories/ForeignChainContractFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ export class ForeignChainContractFactory {
return new ForeignChainContract(<BaseChainContractProps>props);
}

console.log(`[${props.blockchain.chainId}] provider: ${!!props.blockchain.provider}`);
console.log(`[${props.blockchain.chainId}] registry: ${props.blockchain.getContractRegistryAddress()}`);
return null;
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/lib/Blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ export class Blockchain {
this.isHomeChain = chainId === settings.blockchain.homeChain.chainId;
this.settings = (<Record<string, BlockchainSettings>>settings.blockchain.multiChains)[chainId];

console.log(`[${chainId}] providerUrl: ${this.settings.providerUrl}`);

if (!this.settings.providerUrl) {
return;
}
Expand Down
10 changes: 10 additions & 0 deletions src/models/PriceData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ export interface IPriceData extends Document {
data: string;
}

export interface IPriceDataRaw {
tx: string;
chainId: string;
key: string;
value: bigint;
heartbeat: number;
timestamp: number;
data: string;
}

const PriceData: Schema = new Schema({
_id: { type: String, required: true },
tx: { type: String, required: true },
Expand Down
5 changes: 3 additions & 2 deletions src/models/UpdateTx.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mongoose, { Schema, Document } from 'mongoose';
import mongoose, { Schema } from 'mongoose';

export interface IUpdateTx extends Document {
export interface IUpdateTx {
_id: string;
txTimestamp: Date;
chainId: string;
feedsAddress: string;
Expand Down
24 changes: 18 additions & 6 deletions src/repositories/PriceDataRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Logger } from 'winston';

import { ChainsIds } from '../types/ChainsIds';
import { FeedsPriceData } from '../types/UpdateInput';
import PriceData, { IPriceData } from '../models/PriceData';
import PriceData, { IPriceData, IPriceDataRaw } from '../models/PriceData';

@injectable()
export class PriceDataRepository {
Expand All @@ -15,17 +15,29 @@ export class PriceDataRepository {
await PriceData.deleteMany({ tx: txHash });
}

async lastPrices(chainId: ChainsIds | undefined, key: string, days: number): Promise<IPriceData[]> {
const from = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
const filter: FilterQuery<IPriceData> = { key, timestamp: { $gte: from.getTime() / 1000 } };
async lastPrices(chainId: ChainsIds | undefined, key: string, lastDays: number): Promise<IPriceDataRaw[]> {
const from = new Date(Date.now() - lastDays * 24 * 60 * 60 * 1000);
const filter: FilterQuery<IPriceData> = { key, timestamp: { $gte: Math.trunc(from.getTime() / 1000) } };

if (chainId) {
filter['chainId'] = chainId;
}

this.logger.info(`[PriceDataRepository.priceHistory][${chainId}] ${filter}`);
this.logger.info(`[PriceDataRepository.priceHistory][${chainId}] ${JSON.stringify(filter)}`);

return PriceData.find(filter).sort({ timestamp: 1 }).exec();
const records = await PriceData.find(filter).sort({ timestamp: -1 }).exec();

return records.map((r) => {
return {
timestamp: r.timestamp,
data: r.data,
key: r.key,
tx: r.tx,
chainId: r.chainId,
heartbeat: r.heartbeat,
value: BigInt(r.value),
};
});
}

async save(chainId: ChainsIds, txHash: string, key: string, data: FeedsPriceData): Promise<boolean> {
Expand Down
4 changes: 4 additions & 0 deletions src/repositories/UpdateTxRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ export class UpdateTxRepository {
return UpdateTx.find({ chainId, txTimestamp: { $gte: from, $lt: to } }).exec();
}

async find(chainId: ChainsIds, txHashes: string[], projection?: Record<string, number>): Promise<IUpdateTx[]> {
return UpdateTx.find({ chainId, _id: { $in: txHashes } }, projection).exec();
}

private toUpdateData(data: UpdateInput): UpdateData {
return {
keys: data[0],
Expand Down

0 comments on commit 3821758

Please sign in to comment.