diff --git a/.env.example b/.env.example index b739154..dda6f7a 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,11 @@ # Default: info LOG_LEVEL = info +# Update validator registry every Nth ledger. +# +# Default: 256 (ledgers) +# VALIDATOR_SAMPLE_INTERVAL = 256 + # Enable Validation message ingress and storage. # If disabled, Hermes will only serve requests and won't store any new # validation messages diff --git a/public/index.html b/public/index.html index e1f255a..ac296f6 100644 --- a/public/index.html +++ b/public/index.html @@ -94,6 +94,7 @@

Explore

@@ -108,7 +109,7 @@

Resources

diff --git a/src/controllers/ValidatorController.ts b/src/controllers/ValidatorController.ts new file mode 100644 index 0000000..d967735 --- /dev/null +++ b/src/controllers/ValidatorController.ts @@ -0,0 +1,41 @@ +import express, { Request, Response } from 'express' +import Validator from '../models/Validator' + +const ValidatorController = express.Router() + +// All validators +ValidatorController.route('/') +.get((req: Request, res: Response) => { + Validator + .find() + .select('-_id -__v') + .sort({ledger_index: -1}) + .exec((error, validators) => { + if (error) { + return res.status(500).send('Error') + } else if (validators) { + return res.json(validators) + } else { + return res.status(404).send('Not found') + } + }) +}) + +// Validator identified by master_key +ValidatorController.route('/:master_key') +.get((req: Request, res: Response) => { + Validator + .find({master_key: req.params?.master_key}) + .select('-_id -__v') + .exec((error, validator) => { + if (error) { + return res.status(500).send('Error') + } else if (validator) { + return res.json(validator) + } else { + return res.status(404).send('Not found') + } + }) +}) + +export default ValidatorController \ No newline at end of file diff --git a/src/http/index.ts b/src/http/index.ts index 5bb1e9b..45d3934 100644 --- a/src/http/index.ts +++ b/src/http/index.ts @@ -5,6 +5,7 @@ import http from 'node:http' import fs from 'node:fs' import express, { Express, Request, Response, NextFunction } from 'express' import ValidationController from '../controllers/ValidationController' +import ValidatorController from '../controllers/ValidatorController' import PeerController from '../controllers/PeerController' import ServerController from '../controllers/ServerController' import PingController from '../controllers/PingController' @@ -24,6 +25,8 @@ export const startExpressServer = () => { // Routes app.use('/api/v1/validation', ValidationController) app.use('/api/v1/validations', ValidationController) + app.use('/api/v1/validator', ValidatorController) + app.use('/api/v1/validators', ValidatorController) app.use('/api/v1/peers', PeerController) app.use('/api/v1/server', ServerController) app.use('/api/v1/ping', PingController) diff --git a/src/lib/ENV.ts b/src/lib/ENV.ts index f9c0eb4..122c8ad 100644 --- a/src/lib/ENV.ts +++ b/src/lib/ENV.ts @@ -9,6 +9,9 @@ class ENV { return 'info' } } + public static get VALIDATOR_SAMPLE_INTERVAL(): number { + return process.env.VALIDATOR_SAMPLE_INTERVAL ? Number(process.env.VALIDATOR_SAMPLE_INTERVAL) : 256 + } public static get INGRESS_ENABLED(): boolean { return process.env.INGRESS_ENABLED === 'true' ? true : false } diff --git a/src/models/Validator.ts b/src/models/Validator.ts new file mode 100644 index 0000000..8422c87 --- /dev/null +++ b/src/models/Validator.ts @@ -0,0 +1,23 @@ +import { Schema, model } from 'mongoose' + +export interface IValidator { + master_key: string; + ephemeral_key: string; + domain: string; + server_version: string|undefined; + manifest: string|undefined; + seq: number; + ledger_index: number; +} + +const ValidatorSchema = new Schema({ + master_key: { type: String, required: true, index: { unique: true } }, + ephemeral_key: { type: String, required: true, index: true }, + domain: { type: String }, + server_version: { type: String }, + manifest: { type: String }, + seq: { type: Number }, + ledger_index: { type: Number }, +}) + +export default model('Validator', ValidatorSchema) \ No newline at end of file diff --git a/src/processors/ValidationMessage.ts b/src/processors/ValidationMessage.ts index 51d358d..29becf5 100644 --- a/src/processors/ValidationMessage.ts +++ b/src/processors/ValidationMessage.ts @@ -3,6 +3,9 @@ import { verify_validation } from '../lib/VerifyValidation' import Validation, { IValidation } from '../models/Validation' import logger from '../logger' import { MongoServerError } from 'mongodb' +import ValidatorRegistry from './ValidatorRegistry' +import ENV from '../lib/ENV' + const LOGPREFIX = '[validation-manager]' @@ -19,11 +22,16 @@ class ValidationMessage { if (this.verify()) { Validation.create(this._validation, (error) => { if (error instanceof MongoServerError && error.code === 11000) { + logger.verbose(LOGPREFIX, `${error}`) } else if (error) { logger.error(LOGPREFIX, `${error}`) } }) + if (this._validation.ledger_index % ENV.VALIDATOR_SAMPLE_INTERVAL === 0) { + const registry = new ValidatorRegistry(this._validation) + registry.refresh() + } return true } else { diff --git a/src/processors/ValidatorRegistry.ts b/src/processors/ValidatorRegistry.ts new file mode 100644 index 0000000..3eb583a --- /dev/null +++ b/src/processors/ValidatorRegistry.ts @@ -0,0 +1,44 @@ +import xrplclient from '../services/xrpl' +import Validator, { IValidator } from '../models/Validator' +import { IValidation } from '../models/Validation' +import logger from '../logger' + +const LOGPREFIX = '[validator-registry]' + +class ValidatorRegistry { + private _validation: IValidation + + constructor(validation: IValidation) { + this._validation = validation + } + + async refresh() { + if (!this._validation.master_key) { + logger.verbose(LOGPREFIX, `MasterKey missing: ${this._validation.validation_public_key}`) + return + } + try { + const manifest = await xrplclient.request({ command: "manifest", public_key: this._validation.master_key }) + if (manifest?.result?.details?.master_key === this._validation.master_key) { + const newValidator: IValidator = { + ledger_index: this._validation.ledger_index, + server_version: this._validation.server_version, + manifest: manifest.result.manifest, + ...manifest.result.details, + } + Validator.findOneAndUpdate( + { master_key: this._validation.master_key }, + newValidator, + { upsert: true, new: true } + ) + .then(validator => { + logger.verbose(LOGPREFIX, `Updated validator: ${validator.master_key}`) + }) + } + } catch { + logger.verbose(LOGPREFIX, `Error fetching manifest for ${this._validation.master_key}`) + } + } +} + +export default ValidatorRegistry \ No newline at end of file