Skip to content

Commit

Permalink
fix: update reliability return values to -1 (#50)
Browse files Browse the repository at this point in the history
* fix: update reliability return values to -1 for NaN and negative discriminant cases

* Convert validators endpoint to Drizzle query API

* refactor: reorganize validator relations and clean up imports in schema and utils

* fix: update validators db handling

* fix: update fetched validator structure and adjust activity query parameters

---------

Co-authored-by: Sören <[email protected]>
  • Loading branch information
onmax and sisou authored Nov 23, 2024
1 parent 724e80b commit 3e955cd
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 63 deletions.
4 changes: 2 additions & 2 deletions packages/nimiq-validators-trustscore/src/score.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ export function getReliability({ inherentsPerEpoch, weightFactor = 0.5, curveCen

// Could be the case that the division is NaN, so we return 0 in that case. That means there's no inherents, so no blocks, so not reliable because there's no data
if (Number.isNaN(reliability))
return 0
return -1

// Ensure the expression under the square root is non-negative
const discriminant = -(reliability ** 2) + 2 * curveCenter * reliability + (curveCenter - 1) ** 2
if (discriminant < 0)
return 0
return -1

// Plot into the curve
return Math.max(-curveCenter + 1 - Math.sqrt(discriminant), 1)
Expand Down
21 changes: 20 additions & 1 deletion server/database/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sql } from 'drizzle-orm'
import { relations, sql } from 'drizzle-orm'
import { check, index, integer, primaryKey, real, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'
import { PayoutType } from '../utils/types'

Expand Down Expand Up @@ -38,6 +38,13 @@ export const scores = sqliteTable('scores', {
compositePrimaryKey: primaryKey({ columns: [table.validatorId, table.epochNumber] }),
}))

export const scoresRelations = relations(scores, ({ one }) => ({
validator: one(validators, {
fields: [scores.validatorId],
references: [validators.id],
}),
}))

export const activity = sqliteTable('activity', {
validatorId: integer('validator_id').notNull().references(() => validators.id, { onDelete: 'cascade' }),
epochNumber: integer('epoch_number').notNull(),
Expand All @@ -51,3 +58,15 @@ export const activity = sqliteTable('activity', {
idxElectionBlock: index('idx_election_block').on(table.epochNumber),
compositePrimaryKey: primaryKey({ columns: [table.validatorId, table.epochNumber] }),
}))

export const activityRelations = relations(activity, ({ one }) => ({
validator: one(validators, {
fields: [activity.validatorId],
references: [validators.id],
}),
}))

export const validatorRelations = relations(validators, ({ many }) => ({
scores: many(scores),
activity: many(activity),
}))
118 changes: 58 additions & 60 deletions server/utils/validators.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { SQLWrapper } from 'drizzle-orm'
import type { Validator } from './drizzle'
import type { Score, Validator } from './drizzle'
import type { ValidatorJSON } from './schemas'
import type { Result, ValidatorScore } from './types'
import { readdir, readFile } from 'node:fs/promises'
import path from 'node:path'
import { consola } from 'consola'
import { desc, inArray, isNotNull } from 'drizzle-orm'
import { desc, inArray } from 'drizzle-orm'
import { handleValidatorLogo } from './logo'
import { defaultValidatorJSON, validatorSchema } from './schemas'

Expand Down Expand Up @@ -126,8 +126,8 @@ export type FetchValidatorsOptions = Zod.infer<typeof mainQuerySchema> & { addre
type FetchedValidator = Omit<Validator, 'logo' | 'contact'> & {
logo?: string
score?: { total: number | null, availability: number | null, reliability: number | null, dominance: number | null }
dominanceRatioViaBalance?: number
dominanceRatioViaSlots?: number
dominanceRatio?: number
balance?: number
}

export async function fetchValidators(params: FetchValidatorsOptions): Result<FetchedValidator[]> {
Expand All @@ -142,64 +142,62 @@ export async function fetchValidators(params: FetchValidatorsOptions): Result<Fe
filters.push(sql`lower(${tables.validators.name}) NOT LIKE lower('%Unknown validator%')`)

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,
logo: tables.validators.logo,
logoPath: tables.validators.logo,
hasDefaultLogo: tables.validators.hasDefaultLogo,
accentColor: tables.validators.accentColor,
dominanceRatioViaBalance: tables.activity.dominanceRatioViaBalance,
dominanceRatioViaSlots: tables.activity.dominanceRatioViaSlots,
score: {
total: tables.scores.total,
availability: tables.scores.availability,
reliability: tables.scores.reliability,
dominance: tables.scores.dominance,
const dbValidators = await useDrizzle().query.validators.findMany({
where: and(...filters),
with: {
scores: {
where: eq(tables.scores.epochNumber, epochNumber),
limit: 1,
columns: {
total: true,
availability: true,
dominance: true,
reliability: true,
},
},
})
.from(tables.validators)
.where(and(...filters))
.leftJoin(
tables.scores,
and(
eq(tables.validators.id, tables.scores.validatorId),
eq(tables.scores.epochNumber, epochNumber),
isNotNull(tables.scores.total),
),
)
.leftJoin(
tables.activity,
and(
eq(tables.validators.id, tables.activity.validatorId),
eq(tables.activity.epochNumber, epochNumber),
),
)
// .orderBy(desc(tables.scores.total))
.all() as FetchedValidator[]

if (!withIdenticons)
validators.filter(v => v.hasDefaultLogo).forEach(v => delete v.logo)

// Don't return score if any of the values not [0, 1]
validators.forEach((v) => {
if (!v.score)
return
if (
(v.score.dominance === null || v.score.dominance < 0)
|| (v.score.reliability === null || v.score.reliability < 0)
|| (v.score.availability === null || v.score.availability < 0)
) {
v.score.total = null
activity: {
where: eq(tables.scores.epochNumber, epochNumber + 1),
columns: {
dominanceRatioViaBalance: true,
dominanceRatioViaSlots: true,
balance: true,
},
limit: 1,
},
},
})

const validators = dbValidators.map((validator) => {
const { scores, logo, contact, activity, hasDefaultLogo, ...rest } = validator

const score = scores[0]
if (score) {
const { availability, dominance, reliability, total } = scores[0]
const score = { total, availability, dominance, reliability } as Record<keyof Score, number | null>
if (reliability === -1 || reliability === null) {
score.reliability = null
score.total = null
}
if (availability === -1 || availability === null) {
score.availability = null
score.total = null
}
if (dominance === -1 || dominance === null) {
score.dominance = null
score.total = null
}
}

const { dominanceRatioViaBalance, dominanceRatioViaSlots, balance } = activity?.[0] || {}

return {
...rest,
score,
hasDefaultLogo,
logo: !withIdenticons && hasDefaultLogo ? undefined : logo,
dominanceRatio: dominanceRatioViaBalance || dominanceRatioViaSlots,
balance,
} satisfies FetchedValidator
})

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

0 comments on commit 3e955cd

Please sign in to comment.