Skip to content

Commit

Permalink
Fixes #31 - Added an endpoint to serve validator list
Browse files Browse the repository at this point in the history
  • Loading branch information
pkcs8 committed Dec 28, 2023
1 parent 50898d0 commit cca2272
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ <h2>Explore</h2>
<ul class="icon-list ps-0">
<li class="d-flex align-items-start mb-1"><a href="/api/v1/server/config" rel="noopener" target="_blank">/api/v1/server/config</a></li>
<li class="d-flex align-items-start mb-1"><a href="/api/v1/peers" rel="noopener" target="_blank">/api/v1/peers</a></li>
<li class="d-flex align-items-start mb-1"><a href="/api/v1/validators" rel="noopener" target="_blank">/api/v1/validators</a></li>
</ul>
</div>

Expand All @@ -108,7 +109,7 @@ <h2>Resources</h2>
</div>
</main>
<footer class="pt-5 my-5 text-muted border-top">
Hermes server &middot; &copy; 2022, The XRPScan Project
Hermes server &middot; &copy; 2023, The XRPScan Project
</footer>
</div>
</body>
Expand Down
41 changes: 41 additions & 0 deletions src/controllers/ValidatorController.ts
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions src/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/lib/ENV.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
23 changes: 23 additions & 0 deletions src/models/Validator.ts
Original file line number Diff line number Diff line change
@@ -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<IValidator>({
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<IValidator>('Validator', ValidatorSchema)
8 changes: 8 additions & 0 deletions src/processors/ValidationMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]'

Expand All @@ -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 {
Expand Down
44 changes: 44 additions & 0 deletions src/processors/ValidatorRegistry.ts
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit cca2272

Please sign in to comment.