Adding table tbl_fee

This table used to store majority fee of monero nettype.
By calculating majority fee via "cron" every 300s, the function to
get majority fee for nettypes can be done with single query.

The frontend majority static data in the frontend removed and
now use `/api/v1/fees` endpoint to get majority fee value.

Note: Don't know if it works well with `onload` method or not. Let see.
This commit is contained in:
ditatompel 2024-05-31 16:28:21 +07:00
parent 55f6af1f22
commit 48fe09c1cb
No known key found for this signature in database
GPG key ID: 31D3D06D77950979
6 changed files with 159 additions and 72 deletions

View file

@ -6,26 +6,6 @@ export async function load() {
title: 'Public Monero Remote Nodes List',
description: 'List of public Monero remote nodes that you can use with your favourite Monero wallet. You can filter by country, protocol, or CORS capable nodes.',
keywords: 'monero remote nodes,public monero nodes,monero public nodes,monero wallet,tor monero node,monero cors rpc'
},
/**
* Array containing network fees.
* For now, I use static data to reduce the amount of API calls.
* See the values from `/api/v1/fees`
* @type {{ nettype: string, estimate_fee: number }[]}
*/
netFees: [
{
nettype: 'mainnet',
estimate_fee: 20000
},
{
nettype: 'stagenet',
estimate_fee: 56000
},
{
nettype: 'testnet',
estimate_fee: 20000
}
]
}
};
}

View file

@ -1,7 +1,7 @@
<script>
import { DataHandler } from '@vincjo/datatables/remote';
import { format, formatDistance } from 'date-fns';
import { loadData, loadCountries } from './api-handler';
import { loadData, loadFees, loadCountries } from './api-handler';
import { onMount } from 'svelte';
import {
DtSrRowsPerPage,
@ -30,28 +30,34 @@
/** @type {{total_nodes: number, cc: string, name: string}[]} */
let countries = [];
let fees = [];
const handler = new DataHandler([], { rowsPerPage: 10, totalRows: 0 });
let rows = handler.getRows();
/** @type {Object.<string, number>} */
let majorityFee = data.netFees.reduce(
/**
* @param {Object.<string, number>} o
* @param {{ nettype: string, estimate_fee: number }} key
* @returns {Object.<string, number>}
*/
(o, key) => ({
...o,
[key.nettype]: key.estimate_fee
}),
{}
);
let majorityFee;
onMount(() => {
loadFees().then((data) => {
fees = data;
majorityFee = fees.reduce(
/**
* @param {Object.<string, number>} o
* @param {{ nettype: string, estimate_fee: number }} key
* @returns {Object.<string, number>}
*/
(o, key) => ({
...o,
[key.nettype]: key.estimate_fee
}),
{}
);
});
loadCountries().then((data) => {
countries = data;
});
handler.onChange((state) => loadData(state));
handler.invalidate();
});

View file

@ -1,6 +1,9 @@
import { apiUri } from '$lib/utils/common';
/** @param {import('@vincjo/datatables/remote/state')} state */
/**
* @typedef {import('@vincjo/datatables/remote').State} State
* @param {State} state - The state object from the data table.
*/
export const loadData = async (state) => {
const response = await fetch(apiUri(`/api/v1/nodes?${getParams(state)}`));
const json = await response.json();
@ -14,6 +17,13 @@ export const loadCountries = async () => {
return json.data ?? [];
};
export const loadFees = async () => {
const response = await fetch(apiUri('/api/v1/fees'));
const json = await response.json();
return json.data ?? [];
};
/** @param {State} state - The state object from the data table. */
const getParams = ({ pageNumber, rowsPerPage, sort, filters }) => {
let params = `page=${pageNumber}&limit=${rowsPerPage}`;

View file

@ -147,6 +147,9 @@ func (r *CronRepo) execCron(slug string) {
case "delete_old_probe_logs":
slog.Info(fmt.Sprintf("[CRON] Start running task: %s", slug))
r.deleteOldProbeLogs()
case "calculate_majority_fee":
slog.Info(fmt.Sprintf("[CRON] Start running task: %s", slug))
r.calculateMajorityFee()
}
}
@ -159,3 +162,48 @@ func (r *CronRepo) deleteOldProbeLogs() {
slog.Error(fmt.Sprintf("[CRON] Failed to delete old probe logs: %s", err))
}
}
func (r *CronRepo) calculateMajorityFee() {
netTypes := [3]string{"mainnet", "stagenet", "testnet"}
for _, net := range netTypes {
row, err := r.db.Query(`
SELECT
COUNT(id) AS node_count,
nettype,
estimate_fee
FROM
tbl_node
WHERE
nettype = ?
GROUP BY
estimate_fee
ORDER BY
node_count DESC
LIMIT 1`, net)
if err != nil {
slog.Error(fmt.Sprintf("[CRON] Failed to calculate majority fee: %s", err))
}
defer row.Close()
var (
nettype string
estimateFee int
nodeCount int
)
for row.Next() {
err = row.Scan(&nodeCount, &nettype, &estimateFee)
if err != nil {
slog.Error(fmt.Sprintf("[CRON] Failed to calculate majority fee: %s", err))
continue
}
query := `UPDATE tbl_fee SET estimate_fee = ?, node_count = ? WHERE nettype = ?`
_, err = r.db.Exec(query, estimateFee, nodeCount, nettype)
if err != nil {
slog.Error(fmt.Sprintf("[CRON] Failed to update majority fee: %s", err))
continue
}
}
}
}

View file

@ -7,7 +7,7 @@ import (
type migrateFn func(*DB) error
var dbMigrate = [...]migrateFn{v1}
var dbMigrate = [...]migrateFn{v1, v2}
func MigrateDb(db *DB) error {
version := getSchemaVersion(db)
@ -193,3 +193,61 @@ func v1(db *DB) error {
return nil
}
func v2(db *DB) error {
slog.Debug("[DB] Migrating database schema version 2")
// table: tbl_fee
slog.Debug("[DB] Creating table: tbl_fee")
_, err := db.Exec(`
CREATE TABLE tbl_fee (
nettype VARCHAR(100) NOT NULL DEFAULT '',
estimate_fee INT(9) UNSIGNED NOT NULL DEFAULT 0,
node_count INT(9) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (nettype)
)`)
if err != nil {
return err
}
slog.Debug("[DB] Adding default fee to table: tbl_fee")
_, err = db.Exec(`
INSERT INTO tbl_fee (
nettype,
estimate_fee,
node_count
) VALUES (
'mainnet',
0,
0
), (
'stagenet',
0,
0
), (
'testnet',
0,
0
);`)
if err != nil {
return err
}
slog.Debug("[DB] Adding majority fee cron jobs to table: tbl_cron")
_, err = db.Exec(`
INSERT INTO tbl_cron (
title,
slug,
description,
run_every
) VALUES (
'Calculate majority fee',
'calculate_majority_fee',
'Calculate majority Monero fee',
300
);`)
if err != nil {
return err
}
return nil
}

View file

@ -19,7 +19,7 @@ type MoneroRepository interface {
Node(id int) (Node, error)
Add(protocol string, host string, port uint) error
Nodes(QueryNodes) (Nodes, error)
NetFees() []NetFee
NetFees() []*NetFee
Countries() ([]Countries, error)
GiveJob(acceptTor int) (Node, error)
ProcessJob(report ProbeReport, proberId int64) error
@ -80,13 +80,6 @@ func (r *MoneroRepo) Node(id int) (Node, error) {
return node, err
}
// Nodes represents a list of nodes
type Nodes struct {
TotalRows int `json:"total_rows"`
RowsPerPage int `json:"rows_per_page"`
Items []*Node `json:"items"`
}
// QueryNodes represents database query parameters
type QueryNodes struct {
Host string
@ -160,6 +153,13 @@ func (q QueryNodes) toSQL() (args []interface{}, where, sortBy, sortDirection st
return args, where, sortBy, sortDirection
}
// Nodes represents a list of nodes
type Nodes struct {
TotalRows int `json:"total_rows"`
RowsPerPage int `json:"rows_per_page"`
Items []*Node `json:"items"`
}
// Get nodes from database
func (r *MoneroRepo) Nodes(q QueryNodes) (Nodes, error) {
args, where, sortBy, sortDirection := q.toSQL()
@ -310,35 +310,20 @@ type NetFee struct {
NodeCount int `json:"node_count" db:"node_count"`
}
// Get majority net fee from database
func (r *MoneroRepo) NetFees() []NetFee {
// TODO: Create in-memory cache for this
netTypes := [3]string{"mainnet", "stagenet", "testnet"}
netFees := []NetFee{}
for _, net := range netTypes {
fees := NetFee{}
err := r.db.Get(&fees, `
SELECT
COUNT(id) AS node_count,
nettype,
estimate_fee
FROM
tbl_node
WHERE
nettype = ?
GROUP BY
estimate_fee
ORDER BY
node_count DESC
LIMIT 1`, net)
if err != nil {
fmt.Println("WARN:", err.Error())
continue
}
netFees = append(netFees, fees)
// Get majority net fee from table tbl_fee
func (r *MoneroRepo) NetFees() []*NetFee {
var netFees []*NetFee
err := r.db.Select(&netFees, `
SELECT
nettype,
estimate_fee,
node_count
FROM
tbl_fee
`)
if err != nil {
slog.Error(fmt.Sprintf("[MONERO] Failed to get net fees: %s", err))
}
return netFees
}