Skip to content

Commit

Permalink
Merge pull request #3 from onmax/onmax/new-validators-just-in-current…
Browse files Browse the repository at this point in the history
…-epoch

Onmax/new validators just in current epoch
  • Loading branch information
onmax authored Aug 16, 2024
2 parents 91717a2 + d3e0adc commit fe363fd
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 37 deletions.
17 changes: 13 additions & 4 deletions packages/nimiq-vts/src/score.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export function getLiveness({ activeEpochStates, weightFactor }: ScoreParams['li
let weightedSum = 0
let weightTotal = 0

console.log(activeEpochStates.length)
for (const [i, state] of activeEpochStates.entries()) {
const weight = 1 - (weightFactor * i) / activeEpochStates.length
weightedSum += weight * state
Expand All @@ -34,23 +33,33 @@ export function getLiveness({ activeEpochStates, weightFactor }: ScoreParams['li

export function getReliability({ inherentsPerEpoch, weightFactor, curveCenter }: ScoreParams['reliability']) {
if (!inherentsPerEpoch || !weightFactor || !curveCenter) throw new Error(`Invalid params: ${JSON.stringify({ inherentsPerEpoch, weightFactor, curveCenter })}`)

let numerator = 0, denominator = 0
const length = Object.keys(inherentsPerEpoch).length

for( const [epochIndex, inherents] of Object.entries(inherentsPerEpoch) ) {
const {rewarded,missed} = inherents
const totalBlocks = rewarded + missed

if (totalBlocks === 0) continue

const r = rewarded / totalBlocks
const weight = 1 - weightFactor * Number(epochIndex) / length

numerator += weight * r
denominator += weight
}
const reliability = numerator / denominator

// 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(isNaN(reliability)) return 0
if(Number.isNaN(reliability)) return 0

// 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

// Plot into the curve
return -curveCenter + 1 - Math.sqrt(-(reliability ** 2) + 2 * curveCenter * reliability + (curveCenter - 1) ** 2);
return -curveCenter + 1 - Math.sqrt(discriminant)
}


Expand All @@ -64,7 +73,7 @@ const defaultScoreParams: ScoreParams = {

export function computeScore(params: ScoreParams) {
const computeScoreParams = defu(params, defaultScoreParams)

const size = getSize(computeScoreParams.size)
const liveness = getLiveness(computeScoreParams.liveness)
const reliability = getReliability(computeScoreParams.reliability)
Expand Down
9 changes: 8 additions & 1 deletion server/database/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,15 @@ type ValidatorParams = Record<

const validatorParams: ValidatorParams = {};
for (const { address, balance } of validators) {
const validatorId = await useDrizzle().select({ id: tables.validators.id }).from(tables.validators).where(eq(tables.validators.address, address)).get().then(r => r?.id);
if (!validatorId) throw new Error(`Validator ${address} not found in database`);

const validatorActivities = activities.filter(a => a.address === address);
const validatorId = validatorActivities[0]!.validatorId;
if (validatorActivities.length === 0) {
validatorParams[address] = { validatorId, balance, activeEpochStates: Array(epochCount).fill(0), inherentsPerEpoch: {} };
continue
}

const activeEpochStates = Array(epochCount).fill(0);
const inherentsPerEpoch: Record<number, {rewarded: number, missed: number}> = {};
validatorActivities.forEach(activity => {
Expand Down
77 changes: 45 additions & 32 deletions server/tasks/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { NimiqRPCClient } from 'nimiq-rpc-client-ts'
import type { ValidatorsActivities} from 'nimiq-vts';
import type { ValidatorsActivities} from 'nimiq-vts'
import { fetchValidatorsActivities, getRange } from 'nimiq-vts'
import { consola } from 'consola'
import { getMissingEpochs, storeActivities } from '../database/utils'
import { getMissingEpochs, getMissingValidators, storeActivities, storeValidator } from '../database/utils'

const EPOCHS_IN_PARALLEL = 3

Expand All @@ -17,37 +17,50 @@ export default defineTask({
// TODO it would be nice to catch some errors tbh

const client = new NimiqRPCClient(new URL(useRuntimeConfig().rpcUrl))
const epochBlockNumbers = await fetchEpochs(client)

// The range that we will consider
const range = await getRange(client)
consola.info(`Fetching data for range: ${JSON.stringify(range)}`)

// Only fetch the missing epochs that are not in the database
const epochBlockNumbers = await getMissingEpochs(range)
consola.info(`Fetching data for epochs: ${JSON.stringify(epochBlockNumbers)}`)
if (epochBlockNumbers.length === 0)
return { success: 'No epochs to fetch. Database is up to date' }

const activitiesGenerator = fetchValidatorsActivities(client, epochBlockNumbers);

// We fetch epochs 3 by 3 in parallel and store them in the database
while (true) {
const start = globalThis.performance.now()
const epochsActivities: ValidatorsActivities = new Map()
for (let i = 0; i < EPOCHS_IN_PARALLEL; i++) {
const { value: pair, done } = await activitiesGenerator.next();
if (done) break;
epochsActivities.set(pair.key, pair.activity)
}

const end = globalThis.performance.now()
const seconds = (end - start) / 1000
consola.info(`Fetched ${epochsActivities.size} epochs in ${seconds} seconds`)

if (epochsActivities.size === 0) break;
await storeActivities(epochsActivities);
}
// We need to fetch the data of the active validators that are active in the current epoch
// but we don't have the data yet.
const { data: activeValidators, error: errorActiveValidators } = await client.blockchain.getActiveValidators()
if (errorActiveValidators || !activeValidators) throw new Error(errorActiveValidators.message || 'No active validators')
const addressesCurrentValidators = activeValidators.map(v => v.address)
const missingValidators = await getMissingValidators(addressesCurrentValidators)
await Promise.all(missingValidators.map(missingValidator => storeValidator(missingValidator)))

return { result: `${epochBlockNumbers.length} epochs fetched` }
return { result: `New ${epochBlockNumbers.length} epochs fetched and ${missingValidators.length} validators of the current epoch stored ${JSON.stringify(addressesCurrentValidators)}\n\n${JSON.stringify(missingValidators)}` }
},
})

async function fetchEpochs(client: Client) {
// The range that we will consider
const range = await getRange(client)
consola.info(`Fetching data for range: ${JSON.stringify(range)}`)

// Only fetch the missing epochs that are not in the database
const epochBlockNumbers = await getMissingEpochs(range)
consola.info(`Fetching data for epochs: ${JSON.stringify(epochBlockNumbers)}`)
if (epochBlockNumbers.length === 0)
return []

const activitiesGenerator = fetchValidatorsActivities(client, epochBlockNumbers);

// We fetch epochs 3 by 3 in parallel and store them in the database
while (true) {
const start = globalThis.performance.now()
const epochsActivities: ValidatorsActivities = new Map()
for (let i = 0; i < EPOCHS_IN_PARALLEL; i++) {
const { value: pair, done } = await activitiesGenerator.next();
if (done) break;
epochsActivities.set(pair.key, pair.activity)
}

const end = globalThis.performance.now()
const seconds = (end - start) / 1000
consola.info(`Fetched ${epochsActivities.size} epochs in ${seconds} seconds`)

if (epochsActivities.size === 0) break;
await storeActivities(epochsActivities);
}

return epochBlockNumbers
}

0 comments on commit fe363fd

Please sign in to comment.