Skip to content

Commit 3c4c556

Browse files
authored
feat: Use network_id in the Validation subscripton stream (#396)
## High Level Overview of Change Use `network_id` value from the Validation subscription stream. This PR obviates the need for complex `chains` assignment logic in the `chains.updateChains` method. <!-- Please include a summary/list of the changes. If too broad, please consider splitting into multiple PRs. If a relevant Asana task, please link it here. --> ### Context of Change <!-- Please include the context of a change. If a bug fix, when was the bug introduced? What was the behavior? If a new feature, why was this architecture chosen? What were the alternatives? If a refactor, how is this better than the previous implementation? If there is a design document for this feature, please link it here. --> <!-- Please check relevant options, delete irrelevant ones. --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [x] Refactor (non-breaking change that only restructures code) - [ ] Tests (You added tests for code that already exists, or your new feature included in this PR) - [ ] Documentation Updates - [ ] Release ## Before / After There are no changes to the externally visible APIs. <!-- If just refactoring / back-end changes, this can be just an in-English description of the change at a technical level. If a UI change, screenshots should be included. --> ## Test Plan Existing unit tests pass with this refactor. Additional unit tests have also been added to test the logic of chains. <!-- Please describe the tests that you ran to verify your changes and provide instructions so that others can reproduce. --> <!-- ## Future Tasks For future tasks related to PR. -->
1 parent 14f60ff commit 3c4c556

24 files changed

+1246
-195
lines changed

src/api/routes/v1/get-network.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,14 @@ async function getNetworkFromPublicKey(
190190
* @param url - The URL endpoint of the node.
191191
* @param unl - The UNL of the node.
192192
* @param port - The peer port of the node.
193+
* @param network_id - The network ID which uniquely identifies the XRPL network.
193194
* @returns The ID of the new network.
194195
*/
195196
async function addNode(
196197
url: string,
197198
unl: string | null,
198199
port: number,
200+
network_id: number,
199201
): Promise<string> {
200202
const newNetwork = (maxNetwork + 1).toString()
201203
maxNetwork += 1
@@ -205,6 +207,7 @@ async function addNode(
205207
entry: url,
206208
port,
207209
unls: unl ? [unl] : [],
210+
network_id,
208211
}
209212
await query('networks').insert({
210213
...network,
@@ -248,7 +251,7 @@ export default async function getNetworkOrAdd(
248251
}
249252

250253
// check if node public key is already recorded
251-
const { public_key } = crawl.this_node
254+
const { public_key, network_id } = crawl.this_node
252255
const publicKeyNetwork = await getNetworkFromPublicKey(public_key)
253256
if (publicKeyNetwork != null) {
254257
return res.status(200).send({
@@ -257,7 +260,7 @@ export default async function getNetworkOrAdd(
257260
})
258261
}
259262
// add node to networks list
260-
const newNetwork = await addNode(entryUrl, node_unl, port)
263+
const newNetwork = await addNode(entryUrl, node_unl, port, network_id)
261264

262265
return res.status(200).send({
263266
result: 'success',

src/connection-manager/agreement.ts

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-lines -- This file contains useful logging statements and complex logic. */
12
import {
23
getAgreementScores,
34
saveHourlyAgreement,
@@ -18,6 +19,7 @@ import {
1819
ValidatorKeys,
1920
Ballot,
2021
Chain,
22+
LedgerHashIndex,
2123
} from '../shared/types'
2224
import { getLists, overlaps } from '../shared/utils'
2325
import logger from '../shared/utils/logger'
@@ -98,18 +100,48 @@ async function updateAgreementScores(
98100
async function updateDailyAgreement(
99101
validator_keys: ValidatorKeys,
100102
): Promise<void> {
101-
const start = new Date()
102-
start.setHours(0, 0, 0, 0)
103-
const end = new Date()
104-
end.setHours(23, 59, 59, 999)
105-
106-
const agreement = await getAgreementScores(validator_keys, start, end)
103+
interface DailyAgreementDate {
104+
start: Date
105+
end: Date
106+
}
107+
const datesForDailyAgreementComputation: DailyAgreementDate[] = []
108+
const todayStart = new Date()
109+
todayStart.setHours(0, 0, 0, 0)
110+
const todayEnd = new Date()
111+
todayEnd.setHours(23, 59, 59, 999)
112+
113+
datesForDailyAgreementComputation.push({ start: todayStart, end: todayEnd })
114+
115+
// if this method is invoked over the last hour of a given day, save the agreement scores for the previous day.
116+
// Without this additional logic, the agreement scores for the last hour (over VHSs' 24 hour time period) will not be saved.
117+
// Background Context: VHS retroactively measures agreement scores for the validations received over the previous hour.
118+
if (new Date().getHours() === 0) {
119+
const yesterdayStart = new Date()
120+
yesterdayStart.setDate(yesterdayStart.getDate() - 1)
121+
yesterdayStart.setHours(0, 0, 0, 0)
122+
123+
const yesterdayEnd = new Date()
124+
yesterdayEnd.setHours(23, 59, 59, 999)
125+
126+
datesForDailyAgreementComputation.push({
127+
start: yesterdayStart,
128+
end: yesterdayEnd,
129+
})
130+
// Note: saveDailyAgreement method is idempotent. There is no harm in invoking it for today and previous day.
131+
}
107132

108-
await saveDailyAgreement({
109-
main_key: validator_keys.master_key ?? validator_keys.signing_key,
110-
day: start,
111-
agreement,
112-
})
133+
for (const date of datesForDailyAgreementComputation) {
134+
const agreement = await getAgreementScores(
135+
validator_keys,
136+
date.start,
137+
date.end,
138+
)
139+
await saveDailyAgreement({
140+
main_key: validator_keys.master_key ?? validator_keys.signing_key,
141+
day: date.start,
142+
agreement,
143+
})
144+
}
113145
}
114146

115147
/**
@@ -129,7 +161,7 @@ function isPreceedingFlagLedger(ledger_index: string): boolean {
129161
* @returns String.
130162
*/
131163
async function getNetworkNameFromChainId(chain: Chain): Promise<string> {
132-
let id = chain.id
164+
let networkID: number | string = chain.network_id
133165
const lists = await getLists().catch((err) => {
134166
log.error('Error getting validator lists', err)
135167
return undefined
@@ -138,12 +170,12 @@ async function getNetworkNameFromChainId(chain: Chain): Promise<string> {
138170
if (lists != null) {
139171
Object.entries(lists).forEach(([network, set]) => {
140172
if (overlaps(chain.validators, set)) {
141-
id = network
173+
networkID = network
142174
}
143175
})
144176
}
145177

146-
return id
178+
return networkID.toString()
147179
}
148180

149181
/**
@@ -182,7 +214,7 @@ class Agreement {
182214
const networkName = await getNetworkNameFromChainId(chain)
183215

184216
log.info(
185-
`Agreement: ${chain.id}:${networkName}:${Array.from(
217+
`Agreement: ${chain.network_id}:${networkName}:${Array.from(
186218
chain.validators,
187219
).join(',')}`,
188220
)
@@ -254,7 +286,7 @@ class Agreement {
254286
validator.server_version = serverVersion
255287
}
256288

257-
chains.updateLedgers(validation)
289+
await chains.updateLedgers(validation)
258290
await saveValidator(validator)
259291
}
260292
}
@@ -268,7 +300,7 @@ class Agreement {
268300
*/
269301
private async calculateValidatorAgreement(
270302
signing_key: string,
271-
ledger_hashes: Set<string>,
303+
ledger_hashes: Set<LedgerHashIndex>,
272304
incomplete: boolean,
273305
): Promise<void> {
274306
const master_key = await signingToMaster(signing_key)
@@ -289,19 +321,30 @@ class Agreement {
289321
*
290322
* @param validator_keys - Signing keys of validations for one validator.
291323
* @param validations - Set of ledger_hashes validated by signing_key.
292-
* @param ledgers - Set of ledger_hashes validated by network.
324+
* @param ledgerHashIndexMap - Set of pairs of (ledger_hash, ledger_index) ledgers validated by the network.
293325
* @param incomplete - Is this agreement score incomplete.
294326
* @returns Void.
295327
*/
296328
private async calculateHourlyAgreement(
297329
validator_keys: ValidatorKeys,
298330
validations: Map<string, number>,
299-
ledgers: Set<string>,
331+
ledgerHashIndexMap: Set<LedgerHashIndex>,
300332
incomplete: boolean,
301333
): Promise<void> {
334+
// obtain ledger_hashes validated by the network, strip out the ledger_index info for agreement calculation purposes
335+
const ledgers = new Set<string>()
336+
for (const value of ledgerHashIndexMap) {
337+
ledgers.add(value.ledger_hash)
338+
}
302339
const missed = setDifference(ledgers, validations)
303340
const validated = setIntersection(ledgers, validations)
304341

342+
log.trace(
343+
`Tracking information from validator with Master-Key: ${validator_keys.master_key ?? ''}, Signing-Key: ${validator_keys.signing_key}`,
344+
)
345+
log.trace(`Missed ledgers: ${JSON.stringify(Array.from(missed))}`)
346+
log.trace(`Validated ledgers: ${JSON.stringify(Array.from(validated))}`)
347+
305348
const agreement: AgreementScore = {
306349
validated: validated.size,
307350
missed: missed.size,
@@ -312,6 +355,9 @@ class Agreement {
312355
start: this.reported_at,
313356
agreement,
314357
})
358+
log.info(
359+
`Saving hourly agreement for validator with Master-Key: ${validator_keys.master_key ?? ''}, Signing-Key: ${validator_keys.signing_key} indexed by time: ${this.reported_at.toISOString()}; Current time: ${new Date().toISOString()}; Agreement: ${JSON.stringify(agreement)}`,
360+
)
315361

316362
await update1HourValidatorAgreement(validator_keys, agreement)
317363
await updateAgreementScores(validator_keys)
@@ -361,3 +407,4 @@ function getAgreementInstance(): Agreement {
361407
}
362408

363409
export default getAgreementInstance()
410+
/* eslint-enable max-lines */

0 commit comments

Comments
 (0)