Skip to content

Commit

Permalink
chore: return score in /validators endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
onmax committed Nov 16, 2024
1 parent bf1f3e0 commit fd7c9c7
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 23 deletions.
22 changes: 10 additions & 12 deletions server/api/[version]/validators/index.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,30 @@ import { mainQuerySchema } from '~~/server/utils/schemas'
import { fetchValidators } from '~~/server/utils/validators'

export default defineCachedEventHandler(async (event) => {
const { payoutType, onlyActive, onlyKnown, withIdenticon } = await getValidatedQuery(event, mainQuerySchema.parse)
const params = await getValidatedQuery(event, mainQuerySchema.parse)

let addresses: string[] = []
let activeValidators: Validator[] = []
if (onlyActive) {
if (params.onlyActive) {
const { data: _activeValidators, error: errorActiveValidators } = await getRpcClient().blockchain.getActiveValidators()
if (errorActiveValidators)
return createError(errorActiveValidators)
activeValidators = _activeValidators
addresses = activeValidators.map(v => v.address)
}

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

for (const validator of validators) {
// @ts-expect-error this is a hack to add the balance to the validator object
// A better solution would be to add a balance field to the Validator type
// and update the fetchValidators function to include the balance
validator.balance = activeValidators.find(v => v.address === validator.address)?.balance
}
// @ts-expect-error this is a hack to sort the validators by balance
validators.sort((a, b) => b.balance - a.balance)
// for (const validator of validators) {
// // @ts-expect-error this is a hack to add the balance to the validator object
// // A better solution would be to add a balance field to the Validator type
// // and update the fetchValidators function to include the balance
// validator.balance = activeValidators.find(v => v.address === validator.address)?.balance
// }

return validators
}, {
maxAge: 60 * 10, // 10 minutes
maxAge: import.meta.dev ? 1 : 60 * 10, // 10 minutes
})
2 changes: 1 addition & 1 deletion server/database/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const validators = sqliteTable('validators', {
description: text('description'),
fee: real('fee').default(-1),
payoutType: text('payout_type').default(PayoutType.None),
payoutSchedule: text('payout_schedule').default(''),
payoutSchedule: text('payout_schedule'),
isMaintainedByNimiq: integer('is_maintained_by_nimiq', { mode: 'boolean' }).default(false),
icon: text('icon').notNull(),
hasDefaultIcon: integer('has_default_icon', { mode: 'boolean' }).notNull().default(true),
Expand Down
5 changes: 2 additions & 3 deletions server/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ export enum PayoutType {
}

export type ValidatorScore =
Pick<Validator, 'id' | 'name' | 'address' | 'fee' | 'payoutType' | 'description' | 'icon' | 'isMaintainedByNimiq' | 'website'>
Omit<Validator, 'hasDefaultIcon' | 'accentColor' | 'contact'>
& Pick<Score, 'total' | 'liveness' | 'size' | 'reliability' | 'reason'>

export enum HealthFlag {
MissingEpochs = 'missing-epochs',
NoValidators = 'no-validators',
// TOD
O,
// TODO,
// ScoreNotComputed = 'score-not-computed',
}

Expand Down
44 changes: 37 additions & 7 deletions server/utils/validators.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { ScoreValues } from '~~/packages/nimiq-validators-score/src'
import type { NewValidator, Validator } from './drizzle'
import type { PayoutType, Result, ValidatorScore } from './types'
import { readdir, readFile } from 'node:fs/promises'
import path from 'node:path'
import { consola } from 'consola'
import { desc, inArray, not } from 'drizzle-orm'
import { desc, inArray, isNotNull, not } from 'drizzle-orm'
import { createIdenticon, getIdenticonsParams } from 'identicons-esm'
import { optimize } from 'svgo'
import { validatorSchema } from './schemas'
Expand Down Expand Up @@ -78,6 +79,7 @@ export async function storeValidator(
// if (!rest.accentColor)
// throw new Error(`The validator ${address} does have an icon but not an accent color`)
const { data: icon } = optimize(rest.icon, { plugins: [{ name: 'preset-default' }] })
consola.info(`Optimized icon for validator ${address}`)
return { icon, accentColor: rest.accentColor!, hasDefaultIcon: false }
}
const icon = await createIdenticon(address, { format: 'image/svg+xml' })
Expand All @@ -86,6 +88,7 @@ export async function storeValidator(
}

const brandingParameters = await getBrandingParameters()
consola.info(`Generated branding parameters for validator ${address}`)
if (validatorId) {
await useDrizzle()
.update(tables.validators)
Expand Down Expand Up @@ -137,11 +140,14 @@ export interface FetchValidatorsOptions {
addresses?: string[]
onlyKnown?: boolean
withIdenticon?: boolean
withScores?: boolean
}

type FetchedValidator = Omit<Validator, 'icon'> & { icon?: string }
type FetchedValidator = Omit<Validator, 'icon' | 'contact'> & { icon?: string } & { score?: ScoreValues | null }

export async function fetchValidators({ payoutType, addresses = [], onlyKnown = false, withIdenticon = false }: FetchValidatorsOptions): Result<FetchedValidator[]> {
export async function fetchValidators(params: FetchValidatorsOptions): Result<FetchedValidator[]> {
const { payoutType, addresses = [], onlyKnown = false, withIdenticon = false, withScores = false } = params
consola.info(`Fetching validators with params: ${JSON.stringify(params)}`)
const filters = []
if (payoutType)
filters.push(eq(tables.validators.payoutType, payoutType))
Expand All @@ -151,14 +157,38 @@ export async function fetchValidators({ payoutType, addresses = [], onlyKnown =
filters.push(not(eq(tables.validators.name, 'Unknown validator')))

const validators = await useDrizzle()
.select()
.select({
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,
score: {
liveness: tables.scores.liveness,
total: tables.scores.total,
size: tables.scores.size,
reliability: tables.scores.reliability,
},
})
.from(tables.validators)
.where(and(...filters))
.groupBy(tables.validators.id)
.all() as FetchedValidator[]
.leftJoin(tables.scores, eq(tables.validators.id, tables.scores.validatorId))
.where(isNotNull(tables.scores.validatorId))
// .groupBy(tables.validators.id)
.orderBy(desc(tables.scores.total))
.all() satisfies FetchedValidator[] as FetchedValidator[]
consola.info(`Fetched ${validators.length} validators`)

if (!withIdenticon)
validators.filter(v => v.hasDefaultIcon).forEach(v => delete v.icon)
if (!withScores)
validators.forEach(v => delete v.score)

return { data: validators, error: undefined }
}
Expand Down

0 comments on commit fd7c9c7

Please sign in to comment.