Skip to content

Commit cca2272

Browse files
committed
Fixes #31 - Added an endpoint to serve validator list
1 parent 50898d0 commit cca2272

File tree

8 files changed

+129
-1
lines changed

8 files changed

+129
-1
lines changed

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
# Default: info
99
LOG_LEVEL = info
1010

11+
# Update validator registry every Nth ledger.
12+
#
13+
# Default: 256 (ledgers)
14+
# VALIDATOR_SAMPLE_INTERVAL = 256
15+
1116
# Enable Validation message ingress and storage.
1217
# If disabled, Hermes will only serve requests and won't store any new
1318
# validation messages

public/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ <h2>Explore</h2>
9494
<ul class="icon-list ps-0">
9595
<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>
9696
<li class="d-flex align-items-start mb-1"><a href="/api/v1/peers" rel="noopener" target="_blank">/api/v1/peers</a></li>
97+
<li class="d-flex align-items-start mb-1"><a href="/api/v1/validators" rel="noopener" target="_blank">/api/v1/validators</a></li>
9798
</ul>
9899
</div>
99100

@@ -108,7 +109,7 @@ <h2>Resources</h2>
108109
</div>
109110
</main>
110111
<footer class="pt-5 my-5 text-muted border-top">
111-
Hermes server &middot; &copy; 2022, The XRPScan Project
112+
Hermes server &middot; &copy; 2023, The XRPScan Project
112113
</footer>
113114
</div>
114115
</body>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import express, { Request, Response } from 'express'
2+
import Validator from '../models/Validator'
3+
4+
const ValidatorController = express.Router()
5+
6+
// All validators
7+
ValidatorController.route('/')
8+
.get((req: Request, res: Response) => {
9+
Validator
10+
.find()
11+
.select('-_id -__v')
12+
.sort({ledger_index: -1})
13+
.exec((error, validators) => {
14+
if (error) {
15+
return res.status(500).send('Error')
16+
} else if (validators) {
17+
return res.json(validators)
18+
} else {
19+
return res.status(404).send('Not found')
20+
}
21+
})
22+
})
23+
24+
// Validator identified by master_key
25+
ValidatorController.route('/:master_key')
26+
.get((req: Request, res: Response) => {
27+
Validator
28+
.find({master_key: req.params?.master_key})
29+
.select('-_id -__v')
30+
.exec((error, validator) => {
31+
if (error) {
32+
return res.status(500).send('Error')
33+
} else if (validator) {
34+
return res.json(validator)
35+
} else {
36+
return res.status(404).send('Not found')
37+
}
38+
})
39+
})
40+
41+
export default ValidatorController

src/http/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import http from 'node:http'
55
import fs from 'node:fs'
66
import express, { Express, Request, Response, NextFunction } from 'express'
77
import ValidationController from '../controllers/ValidationController'
8+
import ValidatorController from '../controllers/ValidatorController'
89
import PeerController from '../controllers/PeerController'
910
import ServerController from '../controllers/ServerController'
1011
import PingController from '../controllers/PingController'
@@ -24,6 +25,8 @@ export const startExpressServer = () => {
2425
// Routes
2526
app.use('/api/v1/validation', ValidationController)
2627
app.use('/api/v1/validations', ValidationController)
28+
app.use('/api/v1/validator', ValidatorController)
29+
app.use('/api/v1/validators', ValidatorController)
2730
app.use('/api/v1/peers', PeerController)
2831
app.use('/api/v1/server', ServerController)
2932
app.use('/api/v1/ping', PingController)

src/lib/ENV.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ class ENV {
99
return 'info'
1010
}
1111
}
12+
public static get VALIDATOR_SAMPLE_INTERVAL(): number {
13+
return process.env.VALIDATOR_SAMPLE_INTERVAL ? Number(process.env.VALIDATOR_SAMPLE_INTERVAL) : 256
14+
}
1215
public static get INGRESS_ENABLED(): boolean {
1316
return process.env.INGRESS_ENABLED === 'true' ? true : false
1417
}

src/models/Validator.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Schema, model } from 'mongoose'
2+
3+
export interface IValidator {
4+
master_key: string;
5+
ephemeral_key: string;
6+
domain: string;
7+
server_version: string|undefined;
8+
manifest: string|undefined;
9+
seq: number;
10+
ledger_index: number;
11+
}
12+
13+
const ValidatorSchema = new Schema<IValidator>({
14+
master_key: { type: String, required: true, index: { unique: true } },
15+
ephemeral_key: { type: String, required: true, index: true },
16+
domain: { type: String },
17+
server_version: { type: String },
18+
manifest: { type: String },
19+
seq: { type: Number },
20+
ledger_index: { type: Number },
21+
})
22+
23+
export default model<IValidator>('Validator', ValidatorSchema)

src/processors/ValidationMessage.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { verify_validation } from '../lib/VerifyValidation'
33
import Validation, { IValidation } from '../models/Validation'
44
import logger from '../logger'
55
import { MongoServerError } from 'mongodb'
6+
import ValidatorRegistry from './ValidatorRegistry'
7+
import ENV from '../lib/ENV'
8+
69

710
const LOGPREFIX = '[validation-manager]'
811

@@ -19,11 +22,16 @@ class ValidationMessage {
1922
if (this.verify()) {
2023
Validation.create(this._validation, (error) => {
2124
if (error instanceof MongoServerError && error.code === 11000) {
25+
logger.verbose(LOGPREFIX, `${error}`)
2226
}
2327
else if (error) {
2428
logger.error(LOGPREFIX, `${error}`)
2529
}
2630
})
31+
if (this._validation.ledger_index % ENV.VALIDATOR_SAMPLE_INTERVAL === 0) {
32+
const registry = new ValidatorRegistry(this._validation)
33+
registry.refresh()
34+
}
2735
return true
2836
}
2937
else {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import xrplclient from '../services/xrpl'
2+
import Validator, { IValidator } from '../models/Validator'
3+
import { IValidation } from '../models/Validation'
4+
import logger from '../logger'
5+
6+
const LOGPREFIX = '[validator-registry]'
7+
8+
class ValidatorRegistry {
9+
private _validation: IValidation
10+
11+
constructor(validation: IValidation) {
12+
this._validation = validation
13+
}
14+
15+
async refresh() {
16+
if (!this._validation.master_key) {
17+
logger.verbose(LOGPREFIX, `MasterKey missing: ${this._validation.validation_public_key}`)
18+
return
19+
}
20+
try {
21+
const manifest = await xrplclient.request({ command: "manifest", public_key: this._validation.master_key })
22+
if (manifest?.result?.details?.master_key === this._validation.master_key) {
23+
const newValidator: IValidator = {
24+
ledger_index: this._validation.ledger_index,
25+
server_version: this._validation.server_version,
26+
manifest: manifest.result.manifest,
27+
...manifest.result.details,
28+
}
29+
Validator.findOneAndUpdate(
30+
{ master_key: this._validation.master_key },
31+
newValidator,
32+
{ upsert: true, new: true }
33+
)
34+
.then(validator => {
35+
logger.verbose(LOGPREFIX, `Updated validator: ${validator.master_key}`)
36+
})
37+
}
38+
} catch {
39+
logger.verbose(LOGPREFIX, `Error fetching manifest for ${this._validation.master_key}`)
40+
}
41+
}
42+
}
43+
44+
export default ValidatorRegistry

0 commit comments

Comments
 (0)