Skip to content

Commit

Permalink
chore: fixed endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
onmax committed Nov 18, 2024
1 parent f6623c9 commit d5eb49d
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 64 deletions.
2 changes: 2 additions & 0 deletions packages/nimiq-validators-score/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ interface GetRangeOptions {

export const DEFAULT_WINDOW_IN_DAYS = 30 * 9
export const DEFAULT_WINDOW_IN_MS = DEFAULT_WINDOW_IN_DAYS * 24 * 60 * 60 * 1000
const EPOCHS_PER_DAY = 2
export const DEFAULT_WINDOW_IN_EPOCHS = DEFAULT_WINDOW_IN_DAYS * EPOCHS_PER_DAY

/**
* Given the amount of milliseconds we want to consider, it returns an object with the epoch range we will consider.
Expand Down
12 changes: 2 additions & 10 deletions server/api/[version]/validators/index.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,11 @@ export default defineCachedEventHandler(async (event) => {
addresses = activeValidators.map(v => v.address)
}

let epochNumber = -1
if (params['with-scores']) {
const { data: currentEpoch, error: errorCurrentEpoch } = await getRpcClient().blockchain.getEpochNumber()
if (errorCurrentEpoch)
return createError(errorCurrentEpoch)
epochNumber = currentEpoch - 1
}

const { data: validators, error: errorValidators } = await fetchValidators({ ...params, addresses, epochNumber })
const { data: validators, error: errorValidators } = await fetchValidators({ ...params, addresses })
if (errorValidators || !validators)
throw createError(errorValidators)

return validators
}, {
maxAge: import.meta.dev ? 1 : 60 * 10, // 10 minutes
maxAge: import.meta.dev ? 0 : 60 * 10, // 10 minutes
})
6 changes: 3 additions & 3 deletions server/database/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ export const validators = sqliteTable('validators', {
),
}))

// The scores only for the default window size
export const scores = sqliteTable('scores', {
validatorId: integer('validator_id').notNull().references(() => validators.id, { onDelete: 'cascade' }),
fromEpoch: integer('from_epoch').notNull(),
toEpoch: integer('to_epoch').notNull(),
epochNumber: integer('epoch_number').notNull(),
total: real('total').notNull(),
liveness: real('liveness').notNull(),
size: real('size').notNull(),
reliability: real('reliability').notNull(),
reason: text('reason', { mode: 'json' }).notNull(),
}, table => ({
idxValidatorId: index('idx_validator_id').on(table.validatorId),
compositePrimaryKey: primaryKey({ columns: [table.validatorId, table.fromEpoch, table.toEpoch] }),
compositePrimaryKey: primaryKey({ columns: [table.validatorId, table.epochNumber] }),
}))

export const activity = sqliteTable('activity', {
Expand Down
1 change: 0 additions & 1 deletion server/utils/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,4 @@ export const mainQuerySchema = z.object({
'only-active': z.literal('true').or(z.literal('false')).default('false').transform(v => v === 'true'),
'only-known': z.literal('true').or(z.literal('false')).default('true').transform(v => v === 'true'),
'with-identicons': z.literal('true').or(z.literal('false')).default('false').transform(v => v === 'true'),
'with-scores': z.literal('true').or(z.literal('false')).default('false').transform(v => v === 'true'),
})
16 changes: 8 additions & 8 deletions server/utils/scores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { NewScore } from './drizzle'
import type { Result, ValidatorScore } from './types'
import { consola } from 'consola'
import { gte, inArray, lte } from 'drizzle-orm'
import { computeScore } from 'nimiq-validators-score'
import { computeScore, DEFAULT_WINDOW_IN_DAYS } from 'nimiq-validators-score'
import { findMissingEpochs } from './activities'
import { fetchValidatorsScoreByIds } from './validators'

Expand Down Expand Up @@ -93,12 +93,15 @@ export async function calculateScores(range: Range): Result<GetScoresResult> {
}

const score = computeScore({ liveness, size, reliability })
const newScore: NewScore = { validatorId: Number(validatorId), fromEpoch: range.fromEpoch, toEpoch: range.toEpoch, ...score, reason }
const newScore: NewScore = { validatorId: Number(validatorId), epochNumber: range.toEpoch, ...score, reason }
return newScore
})

// TODO only store the scores that uses default window size to save space
await persistScores(scores)
// If the range is the default window size or the range starts at the PoS fork block, we persist the scores
// TODO Once the chain is older than 9 months, we should remove range.fromBlockNumber === 1
if (range.toEpoch - range.fromEpoch + 1 === DEFAULT_WINDOW_IN_DAYS || range.fromBlockNumber === 1)
await persistScores(scores)

const { data: validators, error: errorValidators } = await fetchValidatorsScoreByIds(scores.map(s => s.validatorId))
if (errorValidators || !validators)
return { error: errorValidators, data: undefined }
Expand All @@ -124,10 +127,7 @@ export async function checkIfScoreExistsInDb(range: Range) {
const scoreAlreadyInDb = await useDrizzle()
.select({ validatorId: tables.scores.validatorId })
.from(tables.scores)
.where(and(
eq(tables.scores.toEpoch, range.toEpoch),
eq(tables.scores.fromEpoch, range.fromEpoch),
))
.where(eq(tables.scores.epochNumber, range.toEpoch))
.get()
.then(r => Boolean(r?.validatorId))
return scoreAlreadyInDb
Expand Down
76 changes: 34 additions & 42 deletions server/utils/validators.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { ScoreValues } from '~~/packages/nimiq-validators-score/src'
import type { SQLWrapper } from 'drizzle-orm'
import type { Validator } from './drizzle'
import type { ValidatorJSON } from './schemas'
Expand Down Expand Up @@ -123,13 +122,16 @@ export async function fetchValidatorsScoreByIds(validatorIds: number[]): Result<
return { data: validators, error: undefined }
}

export type FetchValidatorsOptions = Zod.infer<typeof mainQuerySchema> & { addresses: string[], epochNumber?: number }
export type FetchValidatorsOptions = Zod.infer<typeof mainQuerySchema> & { addresses: string[] }

type FetchedValidator = Omit<Validator, 'icon' | 'contact'> & { icon?: string } & { score?: ScoreValues | null, sizeRatio?: number }
type FetchedValidator = Omit<Validator, 'icon' | 'contact'> & {
icon?: string
score?: { total: number, liveness: number, size: number, reliability: number }
}

export async function fetchValidators(params: FetchValidatorsOptions): Result<FetchedValidator[]> {
// This function is a mess. It should be refactored and create better API
const { 'payout-type': payoutType, addresses = [], 'only-known': onlyKnown = false, 'with-identicons': withIdenticons = false, 'with-scores': withScores = false, epochNumber = -1 } = params
const { 'payout-type': payoutType, addresses = [], 'only-known': onlyKnown = false, 'with-identicons': withIdenticons = false } = params

const filters: SQLWrapper[] = [isNotNull(tables.scores.validatorId)]
if (payoutType)
filters.push(eq(tables.validators.payoutType, payoutType))
Expand All @@ -138,49 +140,39 @@ export async function fetchValidators(params: FetchValidatorsOptions): Result<Fe
if (onlyKnown)
filters.push(sql`lower(${tables.validators.name}) NOT LIKE lower('%Unknown validator%')`)

const baseColumns = {
id: tables.validators.id,
name: tables.validators.name,
address: tables.validators.address,
fee: tables.validators.fee,
payoutType: tables.validators.payoutType,
payoutSchedule: tables.validators.payoutSchedule,
description: tables.validators.description,
icon: tables.validators.icon,
accentColor: tables.validators.accentColor,
isMaintainedByNimiq: tables.validators.isMaintainedByNimiq,
hasDefaultIcon: tables.validators.hasDefaultIcon,
website: tables.validators.website,
balance: tables.activity.balance,
}

const columns = withScores
? {
...baseColumns,
sizeRatio: tables.activity.sizeRatio,
try {
const validators = await useDrizzle()
.select({
id: tables.validators.id,
name: tables.validators.name,
address: tables.validators.address,
description: tables.validators.description,
fee: tables.validators.fee,
payoutType: tables.validators.payoutType,
payoutSchedule: tables.validators.payoutSchedule,
isMaintainedByNimiq: tables.validators.isMaintainedByNimiq,
website: tables.validators.website,
icon: tables.validators.icon,
hasDefaultIcon: tables.validators.hasDefaultIcon,
accentColor: tables.validators.accentColor,
score: {
liveness: tables.scores.liveness,
total: tables.scores.total,
size: tables.scores.size,
liveness: tables.scores.liveness,
reliability: tables.scores.reliability,
},
}
: baseColumns

try {
let query = useDrizzle().select(columns).from(tables.validators).where(and(...filters)).$dynamic()

if (withScores) {
consola.info(`Fetching validators with scores for epoch ${epochNumber}`)
query = query
.leftJoin(tables.scores, and(
isNotNull(tables.scores.total),
})
.from(tables.validators)
.where(and(...filters))
.leftJoin(
tables.scores,
and(
eq(tables.validators.id, tables.scores.validatorId),
))
}
query.leftJoin(tables.activity, and(eq(tables.validators.id, tables.activity.validatorId), eq(tables.activity.epochNumber, epochNumber)))

const validators = await query.orderBy(desc(tables.scores.total)).all() satisfies FetchedValidator[] as FetchedValidator[]
isNotNull(tables.scores.total),
),
)
.orderBy(desc(tables.scores.total))
.all() as FetchedValidator[]

if (!withIdenticons)
validators.filter(v => v.hasDefaultIcon).forEach(v => delete v.icon)
Expand Down

0 comments on commit d5eb49d

Please sign in to comment.