Fix charts

This commit is contained in:
jejolare 2024-09-25 20:54:50 +07:00
parent 77cbf455f2
commit 9bb0c0ef03
5 changed files with 344 additions and 264 deletions

View file

@ -0,0 +1,81 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
up: async (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(async (transaction) => {
const allCharts = (await queryInterface.sequelize.query(
`
SELECT "id", "actual_timestamp"
FROM "charts"
`,
{ transaction }
));
const chartsArray = allCharts[0];
for (let i = 0; i < chartsArray.length; i++) {
const chart = chartsArray[i];
console.log('Processing chart', i);
const { actual_timestamp } = chart;
const newTimestamp = +new Date(actual_timestamp) * 1000 * 1000;
await queryInterface.sequelize.query(
`
UPDATE "charts"
SET "actual_timestamp" = :newTimestamp
WHERE "id" = :id
`,
{
transaction,
replacements: {
id: chart.id,
newTimestamp: new Date(newTimestamp),
},
}
);
}
});
},
down: async (queryInterface, Sequelize) => {
// Revert back to the original misinterpreted timestamps
return queryInterface.sequelize.transaction(async (transaction) => {
const allCharts = (await queryInterface.sequelize.query(
`
SELECT "id", "actual_timestamp"
FROM "charts"
`,
{ transaction }
));
const chartsArray = allCharts[0];
for (let i = 0; i < chartsArray.length; i++) {
const chart = chartsArray[i];
console.log('Processing chart', i);
const { actual_timestamp } = chart;
const newTimestamp = +new Date(actual_timestamp) / 1000 / 1000;
await queryInterface.sequelize.query(
`
UPDATE "charts"
SET "actual_timestamp" = :newTimestamp
WHERE "id" = :id
`,
{
transaction,
replacements: {
id: chart.id,
newTimestamp: new Date(newTimestamp),
},
}
);
}
});
},
};

View file

@ -17,7 +17,7 @@ import Block, { IBlock } from "./schemes/Block";
import Alias from "./schemes/Alias";
import Chart, { IChart } from "./schemes/Chart";
import { get_all_pool_tx_list, get_alt_blocks_details, get_blocks_details, get_info, get_out_info, get_pool_txs_details, get_tx_details } from "./utils/zanod";
import { Op } from "sequelize";
import { col, fn, literal, Op } from "sequelize";
import Pool from "./schemes/Pool";
import Asset, { IAsset } from "./schemes/Asset";
import { ITransaction } from "./schemes/Transaction";
@ -38,10 +38,10 @@ export const io = new Server(server, { transports: ['websocket', 'polling'] });
const requestsLimiter = rateLimit({
windowMs: 10 * 1000,
limit: 1,
standardHeaders: 'draft-7',
legacyHeaders: false,
windowMs: 10 * 1000,
limit: 1,
standardHeaders: 'draft-7',
legacyHeaders: false,
});
(async () => {
@ -95,8 +95,8 @@ const requestsLimiter = rateLimit({
url: config.api,
data: {
method: 'getinfo',
params: {
"address": address,
params: {
"address": address,
"viewkey": viewkey,
"blocks_limit": limit
}
@ -338,12 +338,12 @@ const requestsLimiter = rateLimit({
}
app.get(
'/api/get_assets_count',
'/api/get_assets_count',
exceptionHandler(async (req, res) => {
const whitelistedAssets = await fetchWhitelistedAssets();
const whitelistedAssetsAmount = whitelistedAssets.length + 1;
const assetsAmount = await Asset.count();
const assetsAmount = await Asset.count();
return res.json({ assetsAmount, whitelistedAssetsAmount });
}),
@ -384,256 +384,248 @@ const requestsLimiter = rateLimit({
count?: number;
d?: number;
}
app.get(
'/api/get_chart/:chart/:period',
exceptionHandler(async (req, res) => {
const { chart } = req.params;
const now = Math.round(new Date().getTime() / 1000);
const period24HoursAgo = now - 24 * 3600;
const period48HoursAgo = now - 48 * 3600;
try {
if (chart === 'all') {
// Fetch all raw chart data for the 'all' case
const arrayAll = await Chart.findAll({
attributes: ['actual_timestamp', 'block_cumulative_size', 'tr_count', 'difficulty', 'type'],
where: {
actual_timestamp: {
[Op.gt]: period24HoursAgo,
},
},
order: [['actual_timestamp', 'DESC']],
});
// Fetch raw data to calculate dailySum
const dailyData = await Chart.findAll({
attributes: ['actual_timestamp', 'tr_count'],
order: [['actual_timestamp', 'ASC']],
});
// Manually calculate the sum of transactions per day
const dailySum: Record<number, AggregatedData> = dailyData.reduce((acc, row) => {
const day = new Date(row.actual_timestamp).setHours(0, 0, 0, 0);
if (!acc[day]) {
acc[day] = { at: day, sum_trc: 0 };
}
acc[day].sum_trc += parseFloat(row.tr_count.toString());
return acc;
}, {});
// Fetch raw data for hash rate calculations
const hashRateData = await Chart.findAll({
attributes: ['actual_timestamp', 'difficulty120', 'hashrate100', 'hashrate400'],
where: {
type: "1",
actual_timestamp: {
[Op.gt]: period48HoursAgo,
},
},
order: [['actual_timestamp', 'DESC']],
});
res.json({
data: arrayAll,
dailySum: Object.values(dailySum) as AggregatedData[],
hashRateData,
});
} else if (chart === 'AvgBlockSize') {
const blockSizeData = await Chart.findAll({
attributes: ['actual_timestamp', 'block_cumulative_size'],
order: [['actual_timestamp', 'ASC']],
});
// Manually calculate average block size per hour
const blockSizePerHour = blockSizeData.reduce((acc, row) => {
const hour = new Date(row.actual_timestamp).setMinutes(0, 0, 0);
if (!acc[hour]) {
acc[hour] = { at: hour, totalSize: 0, count: 0 };
}
acc[hour].totalSize += parseFloat(row.block_cumulative_size);
acc[hour].count++;
return acc;
}, {}) as Record<number, AggregatedData>;
const result = Object.values(blockSizePerHour).map(hourData => ({
at: hourData.at,
bcs: (hourData?.totalSize || 0) / (hourData.count || 1),
})) as AggregatedData[];
res.json(result);
} else if (chart === 'AvgTransPerBlock') {
const transData = await Chart.findAll({
attributes: ['actual_timestamp', 'tr_count'],
order: [['actual_timestamp', 'ASC']],
});
// Manually calculate average transactions per block per hour
const transPerHour = transData.reduce((acc, row) => {
const hour = new Date(row.actual_timestamp).setMinutes(0, 0, 0);
if (!acc[hour]) {
acc[hour] = { at: hour, totalTrans: 0, count: 0 };
}
acc[hour].totalTrans += parseFloat(row.tr_count.toString());
acc[hour].count++;
return acc;
}, {}) as Record<number, AggregatedData>;
const result = Object.values(transPerHour).map(hourData => ({
at: hourData.at,
trc: (hourData.totalTrans || 0) / (hourData.count || 1),
}));
res.json(result);
} else if (chart === 'hashRate') {
const hashRateData = await Chart.findAll({
attributes: ['actual_timestamp', 'difficulty120', 'hashrate100', 'hashrate400'],
where: { type: "1" },
order: [['actual_timestamp', 'ASC']],
});
// Manually calculate average hash rates per hour
const hashRatePerHour = hashRateData.reduce((acc, row) => {
const hour = new Date(row.actual_timestamp).setMinutes(0, 0, 0);
if (!acc[hour]) {
acc[hour] = { at: hour, totalD120: 0, totalH100: 0, totalH400: 0, count: 0 };
}
acc[hour].totalD120 += parseFloat(row.difficulty120 || "");
acc[hour].totalH100 += parseFloat(row.hashrate100 || "");
acc[hour].totalH400 += parseFloat(row.hashrate400 || "");
acc[hour].count++;
return acc;
}, {}) as Record<number, AggregatedData>;
const result = Object.values(hashRatePerHour).map(hourData => ({
at: hourData.at,
d120: (hourData.totalD120 || 0) / (hourData.count || 1),
h100: (hourData.totalH100 || 0) / (hourData.count || 1),
h400: (hourData.totalH400 || 0) / (hourData.count || 1),
})) as AggregatedData[];
res.json(result);
} else if (chart === 'pos-difficulty' || chart === 'pow-difficulty') {
const type = (chart === 'pos-difficulty' ? 0 : 1).toString();
const difficultyData = await Chart.findAll({
attributes: ['actual_timestamp', 'difficulty'],
where: { type },
order: [['actual_timestamp', 'ASC']],
});
// Manually calculate the max/min/avg difficulty logic per hour
const difficultyPerHour = difficultyData.reduce((acc, row) => {
const hour = new Date(row.actual_timestamp).setMinutes(0, 0, 0);
if (!acc[hour]) {
acc[hour] = { at: hour, maxDiff: parseFloat(row.difficulty), minDiff: parseFloat(row.difficulty), sumDiff: 0, count: 0 };
}
const diff = parseFloat(row.difficulty);
acc[hour].maxDiff = Math.max(acc[hour].maxDiff, diff);
acc[hour].minDiff = Math.min(acc[hour].minDiff, diff);
acc[hour].sumDiff += diff;
acc[hour].count++;
return acc;
}, {}) as Record<number, AggregatedData>;
const aggregatedResult = Object.values(difficultyPerHour).map(hourData => ({
at: hourData.at,
d: (hourData.maxDiff || 0 - (hourData.sumDiff || 0 / (hourData.count || 1)))
>
((hourData.sumDiff || 0 / (hourData.count || 1)) - (hourData.minDiff || 0)) ? hourData.maxDiff : hourData.minDiff,
}));
const detailedResult = difficultyData;
res.json({
aggregated: aggregatedResult,
detailed: detailedResult,
});
} else if (chart === 'ConfirmTransactPerDay') {
const transData = await Chart.findAll({
attributes: ['actual_timestamp', 'tr_count'],
order: [['actual_timestamp', 'ASC']],
});
// Manually calculate the sum of confirmed transactions per day
const transPerDay = transData.reduce((acc, row) => {
const day = new Date(row.actual_timestamp).setHours(0, 0, 0, 0);
if (!acc[day]) {
acc[day] = { at: day, sum_trc: 0 };
}
acc[day].sum_trc += parseFloat(row.tr_count.toString());
return acc;
}, {});
res.json(Object.values(transPerDay));
}
} catch (error) {
console.log(error);
res.status(500).json({ error: error.message });
}
})
);
app.get(
'/api/get_tx_details/:tx_hash',
exceptionHandler(async (req, res) => {
try {
const tx_hash = req.params.tx_hash.toLowerCase();
if (tx_hash) {
// Fetching transaction details with associated block information using Sequelize
const transaction = await Transaction.findOne({
where: { tx_id: tx_hash },
});
const transactionBlock = await Block.findOne({
where: { height: transaction?.keeper_block },
}).catch(() => null);
if (transaction && transactionBlock) {
const response = {
...transaction.toJSON(),
block_hash: transactionBlock?.tx_id,
block_timestamp: transactionBlock?.timestamp,
last_block: lastBlock.height,
};
res.json(response);
} else {
const response = await get_tx_details(tx_hash);
const data = response.data;
if (data?.result?.tx_info) {
if (data.result.tx_info.ins && typeof data.result.tx_info.ins === 'object') {
data.result.tx_info.ins = JSON.stringify(data.result.tx_info.ins);
}
if (data.result.tx_info.outs && typeof data.result.tx_info.outs === 'object') {
data.result.tx_info.outs = JSON.stringify(data.result.tx_info.outs);
}
res.json(data.result.tx_info);
} else {
res.status(500).json({
message: `/get_tx_details/:tx_hash ${JSON.stringify(req.params)}`,
});
}
}
} else {
res.status(500).json({
message: `/get_tx_details/:tx_hash ${JSON.stringify(req.params)}`,
});
}
} catch (error) {
console.log(error);
res.status(500).json({ error: error.message });
}
})
);
async (req, res) => {
const { chart } = req.params;
if (!chart) {
return res.status(400).json({ error: 'Invalid parameters' });
}
const currentTime = Math.round(Date.now() / 1000);
const period = currentTime - 24 * 3600; // 24 hours ago
const period2 = currentTime - 48 * 3600; // 48 hours ago
if (chart === 'all') {
// First query
const arrayAll = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from "actual_timestamp")::BIGINT'
),
'at',
],
[literal('"block_cumulative_size"::REAL'), 'bcs'],
[literal('"tr_count"::REAL'), 'trc'],
[literal('"difficulty"::REAL'), 'd'],
[col('type'), 't'],
],
where: {
actual_timestamp: {
[Op.gt]: new Date(period * 1000), // Convert to milliseconds
},
},
order: [[literal('"at"'), 'ASC']],
});
// Second query
const rows0 = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from date_trunc(\'day\', "actual_timestamp"))::BIGINT'
),
'at',
],
[literal('SUM("tr_count"::REAL)'), 'sum_trc'],
],
group: ['at'],
order: [[literal('"at"'), 'ASC']],
});
// Third query
const rows1 = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from "actual_timestamp")::BIGINT'
),
'at',
],
[literal('"difficulty120"::REAL'), 'd120'],
[literal('"hashrate100"::REAL'), 'h100'],
[literal('"hashrate400"::REAL'), 'h400'],
],
where: {
type: '1',
actual_timestamp: {
[Op.gt]: new Date(period2 * 1000),
},
},
order: [[literal('"at"'), 'ASC']],
});
const arrayAllJson = arrayAll.map((record) => record.toJSON());
const rows0Json = rows0.map((record) => record.toJSON());
const rows1Json = rows1.map((record) => record.toJSON());
const resultArray = [...arrayAllJson];
resultArray[0] = rows0Json;
resultArray[1] = rows1Json;
res.json(resultArray);
} else if (chart === 'AvgBlockSize') {
const result = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from date_trunc(\'hour\', "actual_timestamp"))::BIGINT'
),
'at',
],
[fn('avg', literal('"block_cumulative_size"::REAL')), 'bcs'],
],
group: ['at'],
order: [[literal('"at"'), 'ASC']],
});
res.json(result.map((record) => record.toJSON()));
} else if (chart === 'AvgTransPerBlock') {
const result = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from date_trunc(\'hour\', "actual_timestamp"))::BIGINT'
),
'at',
],
[fn('avg', literal('"tr_count"::REAL')), 'trc'],
],
group: ['at'],
order: [[literal('"at"'), 'ASC']],
});
res.json(result.map((record) => record.toJSON()));
} else if (chart === 'hashRate') {
const result = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from date_trunc(\'hour\', "actual_timestamp"))::BIGINT'
),
'at',
],
[fn('avg', literal('"difficulty120"::REAL')), 'd120'],
[fn('avg', literal('"hashrate100"::REAL')), 'h100'],
[fn('avg', literal('"hashrate400"::REAL')), 'h400'],
],
where: {
type: '1',
},
group: ['at'],
order: [[literal('"at"'), 'ASC']],
});
res.json(result.map((record) => record.toJSON()));
} else if (chart === 'pos-difficulty') {
const result = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from date_trunc(\'hour\', "actual_timestamp"))::BIGINT'
),
'at',
],
[
literal(
`CASE WHEN (MAX("difficulty"::NUMERIC) - AVG("difficulty"::NUMERIC)) > (AVG("difficulty"::NUMERIC) - MIN("difficulty"::NUMERIC)) THEN MAX("difficulty"::NUMERIC) ELSE MIN("difficulty"::NUMERIC) END`
),
'd',
],
],
where: {
type: '0',
},
group: ['at'],
order: [[literal('"at"'), 'ASC']],
});
const result1 = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from "actual_timestamp")::BIGINT'
),
'at',
],
[literal('"difficulty"::REAL'), 'd'],
],
where: {
type: '0',
},
order: [[literal('"at"'), 'ASC']],
});
res.json({
aggregated: result.map((record) => record.toJSON()),
detailed: result1.map((record) => record.toJSON()),
});
} else if (chart === 'pow-difficulty') {
const result = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from date_trunc(\'hour\', "actual_timestamp"))::BIGINT'
),
'at',
],
[
literal(
`CASE WHEN (MAX("difficulty"::NUMERIC) - AVG("difficulty"::NUMERIC)) > (AVG("difficulty"::NUMERIC) - MIN("difficulty"::NUMERIC)) THEN MAX("difficulty"::NUMERIC) ELSE MIN("difficulty"::NUMERIC) END`
),
'd',
],
],
where: {
type: '1',
},
group: ['at'],
order: [[literal('"at"'), 'ASC']],
});
const result1 = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from "actual_timestamp")::BIGINT'
),
'at',
],
[literal('"difficulty"::REAL'), 'd'],
],
where: {
type: '1',
},
order: [[literal('"at"'), 'ASC']],
});
res.json({
aggregated: result.map((record) => record.toJSON()),
detailed: result1.map((record) => record.toJSON()),
});
} else if (chart === 'ConfirmTransactPerDay') {
const result = await Chart.findAll({
attributes: [
[
literal(
'extract(epoch from date_trunc(\'day\', "actual_timestamp"))::BIGINT'
),
'at',
],
[literal('SUM("tr_count"::REAL)'), 'sum_trc'],
],
group: ['at'],
order: [[literal('"at"'), 'ASC']],
});
res.json(result.map((record) => record.toJSON()));
} else {
res.status(400).json({ error: 'Invalid chart type' });
}
}
);
app.get(
"/api/get_tx_by_keyimage/:id",
@ -1126,10 +1118,10 @@ const requestsLimiter = rateLimit({
if (bl.tr_out === undefined) bl.tr_out = [];
let localTr: any;
while (!!(localTr = bl.transactions_details.splice(0, 1)[0])) {
let response = await get_tx_details(localTr.id);
let tx_info = response.data.result.tx_info;
for (let item of tx_info.extra) {
@ -1194,7 +1186,7 @@ const requestsLimiter = rateLimit({
chartInserts.push({
height: bl.height,
actual_timestamp: bl.actual_timestamp,
actual_timestamp: new Date(bl.actual_timestamp * 1000),
block_cumulative_size: bl.block_cumulative_size,
cumulative_diff_precise: bl.cumulative_diff_precise,
difficulty: bl.difficulty,
@ -1566,10 +1558,10 @@ const requestsLimiter = rateLimit({
txs.forEach(tx => {
zanoBurnedBig = zanoBurnedBig.plus(new BigNumber(tx.fee));
});
console.log('ZANO BURNED: ', zanoBurnedBig.toString());
const zanoBurned = zanoBurnedBig.div(new BigNumber(10).pow(12)).toNumber();

View file

@ -52,12 +52,12 @@ function Header(props: HeaderProps) {
>
Assets
</Link>
{/* <Link
<Link
className={page === "Charts" ? "selected" : undefined}
href="/charts"
>
Charts
</Link> */}
</Link>
<Link
className={page === "API" ? "selected" : undefined}
href="/zano_api"

View file

@ -23,6 +23,7 @@ function Charts() {
"confirm-trans-per-day": undefined
}
);
const [loaded, setLoaded] = useState(false);
@ -40,11 +41,17 @@ function Charts() {
];
const chartPeriod = 7 * 24 * 60 * 60 * 1e3;
const now = Date.now();
const now = +new Date();
await Promise.all(titles.map(async title => {
const result = await Utils.fetchChartInfo(title);
if (title === "hash-rate") {
console.log("hash-rate", result);
}
console.log(result);
if (!result) return;
setChartsSeries(prev => ({
...prev,
[title]: result.map(

View file

@ -120,7 +120,7 @@ class Utils {
if (chartId === "difficulty-pow" || chartId === "difficulty-pos") {
const dataDetailed = result.detailed;
if (!dataDetailed || typeof dataDetailed !== "object") return;
return [