├── log └── .gitkeep ├── .eslintignore ├── config └── .gitkeep ├── assets ├── online-delete.png ├── hermes-architecture.png ├── hermes-architecture-v2.png ├── online-delete.drawio └── hermes-architecture.drawio ├── .gitignore ├── bin ├── start.sh ├── ingress.sh └── build-protos.sh ├── src ├── lib │ ├── Base58.ts │ ├── Constants.ts │ ├── Helpers.ts │ ├── LocalNode.ts │ ├── CertCache.ts │ ├── Keypair.ts │ ├── ENV.ts │ └── VerifyValidation.js ├── cmd │ ├── ingress │ │ └── index.ts │ ├── online-delete │ │ ├── index.ts │ │ └── CompactCollection.ts │ ├── keypair │ │ ├── index.ts │ │ ├── PublicKey.ts │ │ ├── Fingerprint.ts │ │ ├── GenerateKeypair.ts │ │ └── Certificate.ts │ └── peer │ │ ├── index.ts │ │ ├── RemovePeer.ts │ │ ├── ListPeers.ts │ │ ├── ShowPeer.ts │ │ └── AddPeer.ts ├── types │ └── index.d.ts ├── logger │ └── index.ts ├── controllers │ ├── PingController.ts │ ├── PeerController.ts │ ├── ServerController.ts │ ├── ValidatorController.ts │ └── ValidationController.ts ├── processors │ ├── peersync │ │ ├── PeerManager.ts │ │ ├── index.ts │ │ └── PollService.ts │ ├── Ingress.ts │ ├── OnlineDelete.ts │ ├── ValidatorRegistry.ts │ └── ValidationMessage.ts ├── protos │ ├── ping.proto │ ├── pb │ │ ├── ping_grpc_pb.js │ │ ├── ping_grpc_pb.d.ts │ │ ├── ping_pb.d.ts │ │ ├── validations_grpc_pb.js │ │ ├── validations_grpc_pb.d.ts │ │ ├── validations_pb.d.ts │ │ ├── ping_pb.js │ │ └── validations_pb.js │ └── validations.proto ├── db │ └── index.ts ├── models │ ├── Peer.ts │ ├── Validator.ts │ ├── Validation.ts │ └── PeerConnection.ts ├── services │ └── xrpl.ts ├── index.ts ├── grpc │ ├── server │ │ ├── PingRequest.ts │ │ ├── index.ts │ │ └── ValidationRequest.ts │ └── client │ │ └── client.ts └── http │ └── index.ts ├── .eslintrc.cjs ├── LICENSE.md ├── package.json ├── .env.example ├── public └── index.html ├── README.md └── tsconfig.json /log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /config/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/online-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrpscan/hermes/HEAD/assets/online-delete.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | .vscode/ 3 | dist/ 4 | node_modules/ 5 | .env 6 | .DS_Store 7 | *.log 8 | *.pem 9 | -------------------------------------------------------------------------------- /assets/hermes-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrpscan/hermes/HEAD/assets/hermes-architecture.png -------------------------------------------------------------------------------- /bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export NODE_ENV=production 3 | pm2 start ./dist/index.js --name hermes --time 4 | -------------------------------------------------------------------------------- /assets/hermes-architecture-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrpscan/hermes/HEAD/assets/hermes-architecture-v2.png -------------------------------------------------------------------------------- /bin/ingress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export NODE_ENV=production 3 | pm2 start ./dist/cmd/ingress/index.js --name hermes-ingress --time 4 | -------------------------------------------------------------------------------- /src/lib/Base58.ts: -------------------------------------------------------------------------------- 1 | import BaseX from 'base-x' 2 | import { R_B58_DICT } from './Constants' 3 | export const base58 = BaseX(R_B58_DICT) -------------------------------------------------------------------------------- /src/cmd/ingress/index.ts: -------------------------------------------------------------------------------- 1 | import ingress from '../../processors/Ingress' 2 | import { connectDatabase } from '../../db' 3 | 4 | connectDatabase() 5 | ingress() -------------------------------------------------------------------------------- /src/lib/Constants.ts: -------------------------------------------------------------------------------- 1 | export const R_B58_DICT = 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz' 2 | export const SYSTEM_CONFIG_ENDPOINT = '/api/v1/server/config' 3 | export const LOGGER_HEADING = '[Hermes]' -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | interface IServerInfo { 2 | node_id?: string | void 3 | certificate?: string | void 4 | fingerprint?: string | void 5 | rest_url?: string 6 | grpc_url?: string 7 | version?: string 8 | } -------------------------------------------------------------------------------- /src/logger/index.ts: -------------------------------------------------------------------------------- 1 | import logger from 'npmlog' 2 | import { LOGGER_HEADING } from '../lib/Constants' 3 | import ENV from '../lib/ENV' 4 | 5 | logger.heading = LOGGER_HEADING 6 | logger.level = ENV.LOG_LEVEL 7 | 8 | export default logger -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: [ 5 | '@typescript-eslint', 6 | ], 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /src/controllers/PingController.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express' 2 | 3 | const PingController = express.Router() 4 | 5 | PingController.route('/') 6 | .get((req: Request, res: Response) => { 7 | return res.json('pong') 8 | }) 9 | 10 | export default PingController -------------------------------------------------------------------------------- /src/cmd/online-delete/index.ts: -------------------------------------------------------------------------------- 1 | import yargs from 'yargs' 2 | import CompactCollection from './CompactCollection' 3 | import { connectDatabase } from '../../db' 4 | connectDatabase() 5 | 6 | yargs 7 | .command(CompactCollection) 8 | .demandCommand(1, 'You must enter one of the supported commands') 9 | .help() 10 | .argv -------------------------------------------------------------------------------- /bin/build-protos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | npx grpc_tools_node_protoc \ 3 | --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \ 4 | --ts_out=grpc_js:./src/protos/pb \ 5 | --js_out=import_style=commonjs:./src/protos/pb \ 6 | --grpc_out=grpc_js:./src/protos/pb \ 7 | -I ./src/protos \ 8 | ./src/protos/*.proto 9 | 10 | -------------------------------------------------------------------------------- /src/processors/peersync/PeerManager.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "events" 2 | import { IPeer } from "../../models/Peer" 3 | 4 | class PeerManager extends EventEmitter { 5 | connections: {[key: string]: boolean} = {} 6 | 7 | constructor() { 8 | super() 9 | } 10 | 11 | async poll(peer: IPeer) { 12 | this.emit('poll', peer) 13 | } 14 | } 15 | 16 | export default PeerManager -------------------------------------------------------------------------------- /src/protos/ping.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package ping; 4 | 5 | message PingRequest { 6 | string message = 1; 7 | reserved 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; 8 | 9 | optional string requesting_node = 14; 10 | optional string requesting_host = 15; 11 | } 12 | 13 | message PongResponse { 14 | string message = 1; 15 | string signature = 2; 16 | } 17 | 18 | service Ping { 19 | rpc Ping(PingRequest) returns (PongResponse) {}; 20 | } -------------------------------------------------------------------------------- /src/cmd/keypair/index.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import yargs from 'yargs' 3 | import GenerateKeypair from './GenerateKeypair' 4 | import PublicKey from './PublicKey' 5 | import Fingerprint from './Fingerprint' 6 | import Certificate from './Certificate' 7 | 8 | yargs 9 | .command(GenerateKeypair) 10 | .command(PublicKey) 11 | .command(Certificate) 12 | .command(Fingerprint) 13 | .demandCommand(1, 'You must enter one of the supported commands') 14 | .help() 15 | .argv -------------------------------------------------------------------------------- /src/cmd/peer/index.ts: -------------------------------------------------------------------------------- 1 | import yargs from 'yargs' 2 | import ListPeers from './ListPeers' 3 | import AddPeer from './AddPeer' 4 | import ShowPeer from './ShowPeer' 5 | import RemovePeer from './RemovePeer' 6 | 7 | import { connectDatabase } from '../../db' 8 | connectDatabase() 9 | 10 | yargs 11 | .command(ListPeers) 12 | .command(AddPeer) 13 | .command(ShowPeer) 14 | .command(RemovePeer) 15 | .demandCommand(1, 'You must enter one of the supported commands') 16 | .help() 17 | .argv -------------------------------------------------------------------------------- /src/lib/Helpers.ts: -------------------------------------------------------------------------------- 1 | export const prettyBytes = (num: number, precision = 3, addSpace = true) => { 2 | const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 3 | if (Math.abs(num) < 1) return num + (addSpace ? ' ' : '') + UNITS[0] 4 | const exponent = Math.min(Math.floor(Math.log10(num < 0 ? -num : num) / 3), UNITS.length - 1) 5 | const n = Number(((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision)) 6 | return (num < 0 ? '-' : '') + n + (addSpace ? ' ' : '') + UNITS[exponent] 7 | } 8 | -------------------------------------------------------------------------------- /src/db/index.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import ENV from '../lib/ENV' 3 | import logger from '../logger' 4 | 5 | // Initialize db connection 6 | export const connectDatabase = () => { 7 | const mongodbURI = `mongodb://${ENV.MONGODB_HOST}:${ENV.MONGODB_PORT}/${ENV.MONGODB_DATABASE}` 8 | const db = mongoose.connect(mongodbURI) 9 | .then((connection) => { 10 | logger.info('[mongod]', 'Connected: %s', mongodbURI) 11 | }) 12 | .catch((error) => { 13 | logger.error('[mongod]', 'Error connecting to : ', error.message) 14 | }) 15 | } 16 | 17 | export default connectDatabase -------------------------------------------------------------------------------- /src/cmd/keypair/PublicKey.ts: -------------------------------------------------------------------------------- 1 | import Keypair from '../../lib/Keypair' 2 | 3 | const getPublicKey = (): void => { 4 | if (process.env.SERVER_PRIVATE_KEY) { 5 | const keypair = new Keypair(process.env.SERVER_PRIVATE_KEY) 6 | if (keypair.isValid) { 7 | console.log(keypair.publicKeyPEM) 8 | } else { 9 | console.error(`Error: Private key '${process.env.SERVER_PRIVATE_KEY}' is invalid`) 10 | } 11 | } 12 | } 13 | 14 | const PublicKeyCommand = { 15 | command: 'public', 16 | aliases: ['pub', 'pubkey'], 17 | describe: 'Print public key', 18 | builder: {}, 19 | handler: getPublicKey, 20 | } 21 | export default PublicKeyCommand -------------------------------------------------------------------------------- /src/cmd/keypair/Fingerprint.ts: -------------------------------------------------------------------------------- 1 | import CertCache from '../../lib/CertCache' 2 | 3 | const getFingerprint = (): void => { 4 | if (process.env.SERVER_CERTIFICATE) { 5 | const cert = new CertCache(process.env.SERVER_CERTIFICATE) 6 | if (cert && cert.certificate) { 7 | console.log(`Certificate fingerprint: ${cert.certificate.fingerprint256}`) 8 | } else { 9 | console.error(`Error: Invalid certificate '${process.env.SERVER_CERTIFICATE}'`) 10 | } 11 | } 12 | } 13 | 14 | const FingerprintCommand = { 15 | command: 'fingerprint', 16 | aliases: ['fp'], 17 | describe: 'Print certificate fingerprint', 18 | builder: {}, 19 | handler: getFingerprint, 20 | } 21 | export default FingerprintCommand -------------------------------------------------------------------------------- /src/models/Peer.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model } from 'mongoose' 2 | 3 | export interface IPeer { 4 | node_id: string 5 | rest_url: string 6 | grpc_url: string 7 | certificate: string 8 | fingerprint: string 9 | status: string 10 | enabled: boolean 11 | } 12 | 13 | const PeerSchema = new Schema({ 14 | node_id: { type: String, required: true, index: { unique: true } }, 15 | rest_url: { type: String }, 16 | grpc_url: { type: String }, 17 | certificate: { type: String, required: true }, 18 | fingerprint: { type: String, required: true, index: { unique: true } }, 19 | status: { type: String }, 20 | enabled: { type: Boolean, default: true, index: true}, 21 | }) 22 | 23 | export default model('Peer', PeerSchema) -------------------------------------------------------------------------------- /src/models/Validator.ts: -------------------------------------------------------------------------------- 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({ 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('Validator', ValidatorSchema) -------------------------------------------------------------------------------- /src/services/xrpl.ts: -------------------------------------------------------------------------------- 1 | import { Client } from 'xrpl' 2 | import ENV from '../lib/ENV' 3 | import logger from '../logger' 4 | 5 | const LOGPREFIX = '[xrpl]' 6 | 7 | const xrplclient = new Client(`${ENV.RIPPLED_URL}`) 8 | xrplclient.connect() 9 | .then(() => { 10 | }) 11 | .catch((error) => { 12 | logger.error(LOGPREFIX, `Connection error: ${ENV.RIPPLED_URL}: ${error}`) 13 | }) 14 | 15 | xrplclient.on('connected', () => { 16 | logger.info(LOGPREFIX, `Connected: ${ENV.RIPPLED_URL}`) 17 | }) 18 | xrplclient.on('disconnected', () => { 19 | logger.info(LOGPREFIX, `Disconnected: ${ENV.RIPPLED_URL}`) 20 | }) 21 | xrplclient.on('error', (code, message) => { 22 | logger.info(LOGPREFIX, `Connection error: ${ENV.RIPPLED_URL}: ${code} ${message}`) 23 | }) 24 | 25 | export default xrplclient -------------------------------------------------------------------------------- /src/cmd/peer/RemovePeer.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import chalk from 'chalk' 3 | import Peer from '../../models/Peer' 4 | 5 | const removePeer = async (argv: any): Promise => { 6 | if (argv.node_id) { 7 | Peer.deleteOne({node_id: argv.node_id}, async (error) => { 8 | if (error) { 9 | console.log(`${chalk.red('Error')}: ${error}`) 10 | await mongoose.disconnect() 11 | } else { 12 | console.log(`Peer deleted: ${argv.node_id}`) 13 | await mongoose.disconnect() 14 | } 15 | }) 16 | } 17 | } 18 | 19 | const RemovePeerCommand = { 20 | command: 'remove [node_id]', 21 | aliases: ['delete', 'rm'], 22 | describe: 'Remove peer', 23 | builder: {}, 24 | handler: removePeer, 25 | } 26 | export default RemovePeerCommand -------------------------------------------------------------------------------- /src/controllers/PeerController.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express' 2 | import Peer from '../models/Peer' 3 | import ENV from '../lib/ENV' 4 | 5 | const PeerController = express.Router() 6 | 7 | // All peers 8 | PeerController.route('/') 9 | .get((req: Request, res: Response) => { 10 | if (ENV.PEER_PRIVATE) { 11 | return res.json([]) 12 | } else { 13 | Peer 14 | .find({enabled: true}) 15 | .select('-_id -__v -enabled') 16 | .exec((error, peers) => { 17 | if (error) { 18 | return res.status(500).json('Error') 19 | } else if (peers) { 20 | return res.json(peers) 21 | } else { 22 | return res.status(404).json('Not found') 23 | } 24 | }) 25 | } 26 | }) 27 | 28 | export default PeerController -------------------------------------------------------------------------------- /src/cmd/keypair/GenerateKeypair.ts: -------------------------------------------------------------------------------- 1 | import Keypair from '../../lib/Keypair' 2 | 3 | const generateKeypair = (): void => { 4 | if (process.env.SERVER_PRIVATE_KEY) { 5 | const keypair = new Keypair(process.env.SERVER_PRIVATE_KEY) 6 | if (!keypair.isValid) { 7 | keypair.generateKeys() 8 | console.log(`Keypair created and saved in '${process.env.SERVER_PRIVATE_KEY}'`) 9 | } else { 10 | console.error(`Error: Private key '${process.env.SERVER_PRIVATE_KEY}' exists. Remove the file to generate a new keypair.`) 11 | } 12 | } 13 | } 14 | 15 | const GenerateKeypairCommand = { 16 | command: 'generate', 17 | aliases: ['gen', 'new'], 18 | describe: 'Generate a new keypair and save', 19 | builder: {}, 20 | handler: generateKeypair, 21 | } 22 | export default GenerateKeypairCommand -------------------------------------------------------------------------------- /src/lib/LocalNode.ts: -------------------------------------------------------------------------------- 1 | import ENV from './ENV' 2 | import CertCache from './CertCache' 3 | 4 | class LocalNode { 5 | constructor() { 6 | 7 | } 8 | static get node_id(): string | void { 9 | if (ENV.SERVER_CERTIFICATE) { 10 | const cert = new CertCache(ENV.SERVER_CERTIFICATE) 11 | if (cert && cert.certificate) { 12 | return(cert.node_id) 13 | } 14 | } 15 | } 16 | 17 | static get fingerprint(): string | void { 18 | if (ENV.SERVER_CERTIFICATE) { 19 | const cert = new CertCache(ENV.SERVER_CERTIFICATE) 20 | if (cert && cert.certificate) { 21 | return(cert.certificate.fingerprint256) 22 | } 23 | } 24 | } 25 | 26 | static get host(): string | void { 27 | return ENV.SERVER_HOSTNAME 28 | } 29 | } 30 | 31 | export default LocalNode -------------------------------------------------------------------------------- /src/cmd/keypair/Certificate.ts: -------------------------------------------------------------------------------- 1 | import CertCache from '../../lib/CertCache' 2 | 3 | const getCertificate = (): void => { 4 | if (process.env.SERVER_CERTIFICATE) { 5 | const cert = new CertCache(process.env.SERVER_CERTIFICATE) 6 | if (cert && cert.certificate) { 7 | console.log(`Node ID: ${cert.node_id}`) 8 | console.log(`Fingerprint: ${cert.certificate.fingerprint256}`) 9 | console.log(cert.certificate.subject) 10 | console.log(`Valid from: ${cert.certificate.validFrom}`) 11 | console.log(`Valid till: ${cert.certificate.validTo}`) 12 | } else { 13 | console.error(`Error: Invalid certificate '${process.env.SERVER_CERTIFICATE}'`) 14 | } 15 | } 16 | } 17 | 18 | const CertificateCommand = { 19 | command: 'cert', 20 | aliases: ['certificate', 'x509'], 21 | describe: 'Print X.509 certificate', 22 | builder: {}, 23 | handler: getCertificate, 24 | } 25 | export default CertificateCommand -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import ingress from './processors/Ingress' 2 | import connectPeers from './processors/peersync/index' 3 | import ENV from './lib/ENV' 4 | import { startExpressServer } from './http' 5 | import { startgRPCServer } from './grpc/server' 6 | import { connectDatabase } from './db' 7 | import { scheduleOnlineDelete } from './processors/OnlineDelete' 8 | 9 | // Connect to mongodb 10 | connectDatabase() 11 | 12 | // Start REST services 13 | if (ENV.SERVER_REST_ENABLED) { 14 | startExpressServer() 15 | } 16 | 17 | // Start gRPC services 18 | if (ENV.SERVER_GRPC_ENABLED) { 19 | startgRPCServer() 20 | } 21 | 22 | // Ingress validation messages 23 | if (ENV.INGRESS_ENABLED) { 24 | ingress() 25 | } 26 | 27 | // Sync with peers and fetch validation messages 28 | if (ENV.PEERSYNC_ENABLED) { 29 | connectPeers() 30 | } 31 | 32 | // Run online_delete process if enabled 33 | if (ENV.ONLINE_DELETE) { 34 | scheduleOnlineDelete() 35 | } -------------------------------------------------------------------------------- /src/cmd/online-delete/CompactCollection.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import Validation from '../../models/Validation' 3 | import { prettyBytes } from '../../lib/Helpers' 4 | 5 | const compactCollection = async (): Promise => { 6 | mongoose.connection.on('connected', async () => { 7 | const collection = Validation.collection.name 8 | const result = await mongoose.connection.db.command({ 9 | compact: collection, 10 | comment: 'online-delete compact processor' 11 | }) 12 | if (result.ok && result.bytesFreed) { 13 | console.log(`[online-delete] Compact ${collection} collection: ${prettyBytes(result.bytesFreed)} freed`) 14 | } 15 | await mongoose.disconnect() 16 | }) 17 | } 18 | 19 | const CompactCollectionCommand = { 20 | command: 'compact', 21 | describe: 'Compact MongoDB collection', 22 | builder: {}, 23 | handler: compactCollection, 24 | } 25 | export default CompactCollectionCommand -------------------------------------------------------------------------------- /src/processors/Ingress.ts: -------------------------------------------------------------------------------- 1 | import xrplclient from '../services/xrpl' 2 | import logger from '../logger' 3 | import ENV from '../lib/ENV' 4 | import ValidationMessage from './ValidationMessage' 5 | 6 | const LOGPREFIX = '[ingress]' 7 | 8 | const ingress = async () => { 9 | xrplclient.on('connected', () => { 10 | xrplclient.request({ 11 | command: 'subscribe', 12 | streams: ['validations'], 13 | }) 14 | logger.info(LOGPREFIX, `Ingressing validation messages from ${ENV.RIPPLED_URL}`) 15 | }) 16 | 17 | xrplclient.on('validationReceived', (validation) => { 18 | try { 19 | // Ugly hack, because xrpl.ValidationStream does not have 'data' property. 20 | // https://github.com/XRPLF/xrpl.js/issues/2095 21 | const vm = new ValidationMessage( JSON.parse(JSON.stringify(validation)) ) 22 | vm.create() 23 | } catch (error) { 24 | logger.error(LOGPREFIX, `${error}`) 25 | } 26 | }) 27 | } 28 | 29 | export default ingress -------------------------------------------------------------------------------- /src/cmd/peer/ListPeers.ts: -------------------------------------------------------------------------------- 1 | import Peer, { IPeer } from '../../models/Peer' 2 | import Table from 'cli-table3' 3 | import mongoose from 'mongoose' 4 | 5 | const listPeer = async (): Promise => { 6 | Peer.find({}, '-_id -certificate -fingerprint', async (error, peers) => { 7 | await printPeers(peers) 8 | await mongoose.disconnect() 9 | }) 10 | } 11 | 12 | const printPeers = async (peers: IPeer[]): Promise => { 13 | let table = new Table({ 14 | head: [ 15 | 'node_id', 16 | 'rest_url', 17 | 'grpc_url', 18 | 'enabled', 19 | ], 20 | }) 21 | for (const peer of peers) { 22 | table.push([ 23 | peer.node_id, 24 | peer.rest_url, 25 | peer.grpc_url, 26 | peer.enabled, 27 | ]) 28 | } 29 | console.log(table.toString()) 30 | } 31 | 32 | const ListPeerCommand = { 33 | command: 'list', 34 | aliases: ['ls'], 35 | describe: 'List saved peers', 36 | builder: {}, 37 | handler: listPeer, 38 | } 39 | export default ListPeerCommand -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2022, The XRPScan Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/grpc/server/PingRequest.ts: -------------------------------------------------------------------------------- 1 | import * as grpc from '@grpc/grpc-js' 2 | import crypto from 'node:crypto' 3 | import { sendUnaryData } from '@grpc/grpc-js/build/src/server-call' 4 | import { PingRequest, PongResponse } from '../../protos/pb/ping_pb' 5 | import Keypair from '../../lib/Keypair' 6 | import ENV from '../../lib/ENV' 7 | import logger from '../../logger' 8 | 9 | const LOGPREFIX = '[grpc:ping]' 10 | 11 | export const ping = async ( call: grpc.ServerUnaryCall, callback: sendUnaryData ): Promise => { 12 | const request = call.request as PingRequest 13 | logger.info(LOGPREFIX, `Ping request from ${request.getRequestingNode()} ${request.getRequestingHost()}`) 14 | const pong = new PongResponse() 15 | pong.setMessage(request.getMessage()) 16 | try { 17 | const kp = new Keypair(ENV.SERVER_PRIVATE_KEY) 18 | if (kp.privateKey) { 19 | const signature = crypto.sign('SHA256', Buffer.from(request.getMessage()), kp.privateKey) 20 | pong.setSignature(signature.toString('hex')) 21 | } 22 | } catch { 23 | } 24 | callback(null, pong) 25 | } 26 | -------------------------------------------------------------------------------- /src/controllers/ServerController.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express' 2 | import CertCache from '../lib/CertCache' 3 | import ENV from '../lib/ENV' 4 | 5 | const ServerController = express.Router() 6 | 7 | // Service auto configuration can bootstrap a peer 8 | // connection by providing 9 | // - Node's ID 10 | // - Certificate 11 | // - REST/gRPC connection ports 12 | 13 | ServerController.route('/config') 14 | .get((req: Request, res: Response) => { 15 | const info: IServerInfo = {} 16 | if (process.env.SERVER_CERTIFICATE) { 17 | const cert = new CertCache(process.env.SERVER_CERTIFICATE) 18 | info.node_id = cert.node_id 19 | info.certificate = cert.certificateBase64 20 | info.fingerprint = cert.certificate?.fingerprint256 21 | info.rest_url = `${ENV.SERVER_HOSTNAME}:${ENV.SERVER_REST_PORT}` 22 | if (ENV.SERVER_GRPC_ENABLED) { 23 | info.grpc_url = `${ENV.SERVER_HOSTNAME}:${ENV.SERVER_GRPC_PORT}` 24 | } 25 | if (process.env.name && process.env.version) { 26 | info.version = `${process.env.name}-${process.env.version}` 27 | } 28 | } 29 | return res.json(info) 30 | }) 31 | 32 | export default ServerController -------------------------------------------------------------------------------- /src/controllers/ValidatorController.ts: -------------------------------------------------------------------------------- 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/cmd/peer/ShowPeer.ts: -------------------------------------------------------------------------------- 1 | import Table from 'cli-table3' 2 | import chalk from 'chalk' 3 | import Peer, { IPeer } from '../../models/Peer' 4 | import mongoose from 'mongoose' 5 | 6 | const showPeer = async (argv: any): Promise => { 7 | if (argv.node_id) { 8 | const peer = await Peer.findOne({node_id: argv.node_id}).lean() 9 | if (peer) { 10 | await printPeer(peer) 11 | await mongoose.disconnect() 12 | } else { 13 | console.error(`${chalk.red('Error:')} Peer not found: ${argv.node_id}`) 14 | await mongoose.disconnect() 15 | } 16 | } 17 | else { 18 | console.error(`${chalk.red('Error:')} Peer's Node ID is required.`) 19 | await mongoose.disconnect() 20 | } 21 | } 22 | 23 | const printPeer = async (peer: IPeer): Promise => { 24 | let table = new Table() 25 | table.push( 26 | { 'node_id': peer.node_id }, 27 | { 'rest_url': peer.rest_url }, 28 | { 'gpc_url': peer.grpc_url }, 29 | { 'fingerprint': peer.fingerprint }, 30 | { 'enabled': peer.enabled }, 31 | ) 32 | console.log(table.toString()) 33 | } 34 | 35 | const ShowPeerCommand = { 36 | command: 'show [node_id]', 37 | describe: 'Show saved peer info', 38 | builder: {}, 39 | handler: showPeer, 40 | } 41 | export default ShowPeerCommand -------------------------------------------------------------------------------- /assets/online-delete.drawio: -------------------------------------------------------------------------------- 1 | 7Zhdc6IwFIZ/jZe7w6fSS0Bq3artLJ3t9GonhQhMA3FCrHZ//SaQiHxY6bTOOu3eaHhzyCHJk3MODHQ33U4IWMVzHEI00JRwO9DHA03TLkyD/XHlpVRU1VRKJSJJKLRK8JM/UIjSbJ2EMK8ZUowRTVZ1McBZBgNa0wAheFM3W2JU97oCEWwJfgBQW71PQhqXqiVnwfUrmESx9KwqoicF0lgIeQxCvNmTdG+guwRjWrbSrQsRXz25LuV9lwd6dw9GYEb73DBH+VzRp1fLyWLz4Nx7+cOP0TcxyjNAazHha8+7FQ9MX+Qq5JskRSBjV07+BGnAZ6buLnxhSPA6Yj3OEmf0EqQJ4pvugmcIqFB9vCYBN40pZRuombrNftgj8x9ukH+PMI4QBKsk/x7gtOgI8sL0clmOyZrVqO1VkFOChMLtniRWZQJxCilhwyiSUkvskGBUH4rrTbXhhrSJ9zZbakAwFu2G3nn7yZgEGZtQ5c5QGu7MtjvV6HCnN9wBRCHJAIUOXmdhvr/5rLE300oqkHgDHkYLj4E2RHzdH1kj4o1f9mw6tu+mNwtmOPd83554vrRiTneGLajY9nA9pimSOFGCn6CLESZMyXAB3DJBqCEBlEQZuwzYpkOmO3yzE3ZobdGRJmHI3TibOKHQX4ECug2LUUwjfLkgn7bSn2cRmVTrn/P96jnuDb1qmjUKjQ4IRz0g3Ge+Rt9bUdNaqI29mXfnfa1YZBwPRVpXbLDMzx6Khh2hqEFGdbDVA0e/FWtkTtdEoLmXkzS4NQjiNYETfuvYqgS7WEVmYzbC00DTbcW1PK3JZs9TrPXGRWyYZvbDY3iqU2seTxD+nT1jp1j5yERxsgRQBAbhWekI/d1BBoFHiBwQPEXF0I1sVfZiEkLSTG3nl0j6I7iVMeO88oh8cXgNyZvFbLrwfssE0wNBVryveDMgOM8fATkeX+rB6AAhIVyCdeH9MCRnluRaxdCB+HeKwlztWZhrJ4Or/b7Ugsud2dO57RQxbzz1r9mff2u7/zk7W84aRZcx6ll0nQ4z/S3FjnIckq+BhXji9xAh6yqrjYCldCFgnIyBdsHbSFvn+Totv3IpnxU7zfiYqGOOGpXTRUfpJN/r3lk6scvqQ2P5RlZ9r9W9vw== -------------------------------------------------------------------------------- /src/processors/OnlineDelete.ts: -------------------------------------------------------------------------------- 1 | import xrplclient from '../services/xrpl' 2 | import logger from '../logger' 3 | import ENV from '../lib/ENV' 4 | import Validation from '../models/Validation' 5 | 6 | const LOGPREFIX = '[online-delete]' 7 | 8 | export const runOnlineDelete = async () => { 9 | if (xrplclient.isConnected()) { 10 | try { 11 | const ledger = await xrplclient.request({ command: 'ledger', ledger_index: 'validated' }) 12 | const ledger_threshold = ledger.result.ledger_index - ENV.ONLINE_DELETE 13 | const d = await Validation.deleteMany({ledger_index: {$lt: ledger_threshold}}) 14 | if (d.acknowledged) { 15 | logger.info(LOGPREFIX, `Purged ${d.deletedCount} validations from ledgers older than ${ledger_threshold}`) 16 | } 17 | } catch { 18 | logger.error(LOGPREFIX, `Error running online_delete validations`) 19 | } 20 | } else { 21 | logger.error(LOGPREFIX, `Deferring online_delete as we're not connected to XRPL.`) 22 | } 23 | 24 | // Add infinite callback loop 25 | setTimeout(runOnlineDelete, ENV.ONLINE_DELETE_INTERVAL_MS) 26 | } 27 | 28 | export const scheduleOnlineDelete = () => { 29 | logger.info(LOGPREFIX, `Scheduling online_delete (every ${ENV.ONLINE_DELETE_INTERVAL} sec; keep ${ENV.ONLINE_DELETE} ledgers)`) 30 | setTimeout(runOnlineDelete, ENV.ONLINE_DELETE_INTERVAL_MS) 31 | } -------------------------------------------------------------------------------- /src/lib/CertCache.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'node:crypto' 2 | import fs from 'node:fs' 3 | import { base58 } from './Base58' 4 | 5 | const NODE_ID_PREFIX = 'h' 6 | 7 | class CertCache { 8 | private _certificate: crypto.X509Certificate | void 9 | 10 | constructor(certificate_file: string) { 11 | if (fs.existsSync(certificate_file)) { 12 | try { 13 | const data = fs.readFileSync(certificate_file) 14 | this._certificate = new crypto.X509Certificate(data) 15 | } catch (error) { 16 | if (error instanceof Error) { 17 | console.error(`Error: Cannot read certificate '${certificate_file}' ${error.message}`) 18 | } 19 | } 20 | } 21 | } 22 | 23 | get certificate(): crypto.X509Certificate | void { 24 | return this._certificate 25 | } 26 | 27 | get certificatePEM(): string | void { 28 | return this._certificate?.toString() 29 | } 30 | 31 | get certificateBase64(): string | void { 32 | if (this.certificatePEM) { 33 | return Buffer.from(this.certificatePEM).toString('base64') 34 | } 35 | } 36 | 37 | get node_id(): string | void { 38 | if (this._certificate?.fingerprint256) { 39 | const fp = Buffer.from(this._certificate.fingerprint256.replace(/\:/g,''), 'hex') 40 | return `${NODE_ID_PREFIX}${base58.encode(fp)}` 41 | } 42 | } 43 | } 44 | 45 | export default CertCache 46 | -------------------------------------------------------------------------------- /src/protos/pb/ping_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- DO NOT EDIT! 2 | 3 | 'use strict'; 4 | var grpc = require('@grpc/grpc-js'); 5 | var ping_pb = require('./ping_pb.js'); 6 | 7 | function serialize_ping_PingRequest(arg) { 8 | if (!(arg instanceof ping_pb.PingRequest)) { 9 | throw new Error('Expected argument of type ping.PingRequest'); 10 | } 11 | return Buffer.from(arg.serializeBinary()); 12 | } 13 | 14 | function deserialize_ping_PingRequest(buffer_arg) { 15 | return ping_pb.PingRequest.deserializeBinary(new Uint8Array(buffer_arg)); 16 | } 17 | 18 | function serialize_ping_PongResponse(arg) { 19 | if (!(arg instanceof ping_pb.PongResponse)) { 20 | throw new Error('Expected argument of type ping.PongResponse'); 21 | } 22 | return Buffer.from(arg.serializeBinary()); 23 | } 24 | 25 | function deserialize_ping_PongResponse(buffer_arg) { 26 | return ping_pb.PongResponse.deserializeBinary(new Uint8Array(buffer_arg)); 27 | } 28 | 29 | 30 | var PingService = exports.PingService = { 31 | ping: { 32 | path: '/ping.Ping/Ping', 33 | requestStream: false, 34 | responseStream: false, 35 | requestType: ping_pb.PingRequest, 36 | responseType: ping_pb.PongResponse, 37 | requestSerialize: serialize_ping_PingRequest, 38 | requestDeserialize: deserialize_ping_PingRequest, 39 | responseSerialize: serialize_ping_PongResponse, 40 | responseDeserialize: deserialize_ping_PongResponse, 41 | }, 42 | }; 43 | 44 | exports.PingClient = grpc.makeGenericClientConstructor(PingService); 45 | -------------------------------------------------------------------------------- /src/models/Validation.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model } from 'mongoose' 2 | 3 | export interface IValidation { 4 | cookie: string; 5 | data: string; 6 | type: string; 7 | base_fee: number; 8 | load_fee: number; 9 | flags: number; 10 | full: boolean; 11 | ledger_hash: string; 12 | ledger_index: number; 13 | master_key: string; 14 | reserve_base: number; 15 | reserve_inc: number; 16 | server_version: string; 17 | signature: string; 18 | signing_time: number; 19 | validated_hash: string; 20 | validation_public_key: string; 21 | } 22 | 23 | const ValidationSchema = new Schema({ 24 | cookie: { type: String }, 25 | data: { type: String }, 26 | type: { type: String, required: true }, 27 | base_fee: { type: Number }, 28 | load_fee: { type: Number }, 29 | flags: { type: Number, required: true }, 30 | full: { type: Boolean, required: true }, 31 | ledger_hash: { type: String, required: true }, 32 | ledger_index: { type: Number, required: true, index: true }, 33 | master_key: { type: String, index: true }, 34 | reserve_base: { type: Number }, 35 | reserve_inc: { type: Number }, 36 | server_version: { type: String }, 37 | signature: { type: String, required: true }, 38 | signing_time: { type: Number, required: true }, 39 | validated_hash: { type: String }, 40 | validation_public_key: { type: String, required: true, index: true }, 41 | }) 42 | 43 | ValidationSchema.index({ validation_public_key: 1, ledger_index: -1 }, { unique: true }) 44 | 45 | export default model('Validation', ValidationSchema) -------------------------------------------------------------------------------- /src/processors/ValidatorRegistry.ts: -------------------------------------------------------------------------------- 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 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hermes", 3 | "version": "0.4.1", 4 | "description": "Project Hermes: XRPL Validation message service", 5 | "main": "index.js", 6 | "author": "Anurag", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@grpc/grpc-js": "^1.6.7", 10 | "@grpc/proto-loader": "^0.6.13", 11 | "chalk": "^4.1.2", 12 | "cli-table3": "^0.6.2", 13 | "dotenv": "^16.0.1", 14 | "express": "^4.18.1", 15 | "grpc-tools": "^1.12.4", 16 | "mongodb": "^4.8.1", 17 | "mongoose": "^6.4.4", 18 | "morgan": "^1.10.0", 19 | "node-fetch": "^2.6.7", 20 | "npmlog": "^6.0.2", 21 | "selfsigned": "^2.0.1", 22 | "xrpl": "^2.3.1", 23 | "yargs": "^17.5.1" 24 | }, 25 | "devDependencies": { 26 | "@types/express": "^4.17.13", 27 | "@types/node": "^18.0.3", 28 | "@types/node-fetch": "^2.6.2", 29 | "@types/npmlog": "^4.1.4", 30 | "@types/yargs": "^17.0.10", 31 | "@typescript-eslint/eslint-plugin": "^5.30.6", 32 | "@typescript-eslint/parser": "^5.30.6", 33 | "concurrently": "^7.2.2", 34 | "eslint": "^8.19.0", 35 | "grpc_tools_node_protoc_ts": "^5.3.2", 36 | "nodemon": "^2.0.19", 37 | "typescript": "^4.7.4" 38 | }, 39 | "scripts": { 40 | "lint": "eslint .", 41 | "build": "npm run proto:build && npx tsc -b", 42 | "start": "node dist/index.js", 43 | "ingress": "node dist/cmd/ingress/index.js", 44 | "proto:build": "./bin/build-protos.sh", 45 | "dev": "concurrently 'npm run proto:build' 'npx tsc --watch' 'nodemon -q dist/index.js' ", 46 | "peer": "node dist/cmd/peer/index.js", 47 | "keypair": "node dist/cmd/keypair/index.js", 48 | "online-delete": "node dist/cmd/online-delete/index.js" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/protos/validations.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package validations; 4 | 5 | message LedgerRequest { 6 | uint32 ledger_index = 1; 7 | reserved 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; 8 | 9 | optional string requesting_node = 14; 10 | optional string requesting_host = 15; 11 | } 12 | 13 | message LedgerRangeRequest { 14 | uint32 ledger_index_min = 1; 15 | uint32 ledger_index_max = 2; 16 | reserved 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; 17 | 18 | optional string requesting_node = 14; 19 | optional string requesting_host = 15; 20 | } 21 | 22 | message MasterKeyRequest { 23 | string master_key = 1; 24 | reserved 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; 25 | 26 | optional string requesting_node = 14; 27 | optional string requesting_host = 15; 28 | } 29 | 30 | message ValidationResponse { 31 | string cookie = 1; 32 | string type = 2; 33 | uint32 flags = 3; 34 | bool full = 4; 35 | string ledger_hash = 5; 36 | uint32 ledger_index = 6; 37 | string master_key = 7; 38 | string signature = 8; 39 | uint32 signing_time = 9; 40 | string validated_hash = 10; 41 | string validation_public_key = 11; 42 | string data = 12; 43 | 44 | reserved 13, 14, 15; 45 | 46 | string server_version = 16; 47 | int32 base_fee = 17; 48 | int32 load_fee = 18; 49 | uint32 reserve_base = 19; 50 | uint32 reserve_inc = 20; 51 | } 52 | 53 | service Validations { 54 | rpc GetValidation(LedgerRequest) returns (ValidationResponse) {}; 55 | rpc GetValidationsByLedger(LedgerRequest) returns (stream ValidationResponse) {}; 56 | rpc GetValidationsByLedgerRange(LedgerRangeRequest) returns (stream ValidationResponse) {}; 57 | rpc GetValidationsByMasterKey(MasterKeyRequest) returns (stream ValidationResponse) {}; 58 | } -------------------------------------------------------------------------------- /src/processors/peersync/index.ts: -------------------------------------------------------------------------------- 1 | import PeerManager from './PeerManager' 2 | import PollService from './PollService' 3 | import logger from '../../logger' 4 | import Peer, { IPeer } from '../../models/Peer' 5 | import xrplclient from '../../services/xrpl' 6 | import ENV from '../../lib/ENV' 7 | 8 | const LOGPREFIX = '[peersync]' 9 | 10 | const pollAllPeers = async (peerManager: PeerManager): Promise => { 11 | for await (const peer of Peer 12 | .find({ 13 | enabled: true, 14 | }) 15 | .lean() 16 | ) { 17 | if (peerManager.connections[peer.node_id]) { 18 | peerManager.emit('error', new Error(`Connection to peer ${peer.node_id} ${peer.grpc_url} exists`)) 19 | } else { 20 | peerManager.connections[peer.node_id] = true 21 | await peerManager.poll(peer) 22 | } 23 | } 24 | 25 | // induce callback loop 26 | setTimeout(pollAllPeers, ENV.PEERSYNC_POLL_INTERVAL_MS, peerManager) 27 | } 28 | 29 | const peerSync = async () => { 30 | const peerManager = new PeerManager() 31 | 32 | peerManager.on('poll', async (peer: IPeer) => { 33 | const ledgerIndexMax = await xrplclient.getLedgerIndex() 34 | const ledgerIndexMin = ledgerIndexMax - ENV.PEERSYNC_FETCH_DEPTH 35 | logger.info(LOGPREFIX, `Polling ${peer.node_id} ${peer.grpc_url} [${ledgerIndexMin}..${ledgerIndexMax}]`) 36 | const pollService = new PollService(peer, peerManager) 37 | await pollService.fetch(ledgerIndexMin, ledgerIndexMax) 38 | }) 39 | 40 | peerManager.on('error', (error: any) => { 41 | logger.error(LOGPREFIX, `${error}`) 42 | }) 43 | 44 | await pollAllPeers(peerManager) 45 | } 46 | 47 | const connectPeers = () => { 48 | xrplclient.on('connected', async () => { 49 | await peerSync() 50 | }) 51 | } 52 | 53 | export default connectPeers -------------------------------------------------------------------------------- /src/controllers/ValidationController.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express' 2 | import Validation from '../models/Validation' 3 | 4 | const ValidationController = express.Router() 5 | 6 | // All validations 7 | ValidationController.route('/') 8 | .get((req: Request, res: Response) => { 9 | Validation 10 | .find() 11 | .select('-_id -__v') 12 | .sort({ledger_index: 'desc'}) 13 | .limit(1024) 14 | .exec((error, validations) => { 15 | if (error) { 16 | return res.status(500).send('Error') 17 | } else if (validations) { 18 | return res.json(validations) 19 | } else { 20 | return res.status(404).send('Not found') 21 | } 22 | }) 23 | }) 24 | 25 | // Validations for a specifc ledger 26 | ValidationController.route('/ledger/:ledger_index') 27 | .get((req: Request, res: Response) => { 28 | Validation 29 | .find({ledger_index: req.params?.ledger_index}) 30 | .select('-_id -__v') 31 | .exec((error, validations) => { 32 | if (error) { 33 | return res.status(500).send('Error') 34 | } else if (validations) { 35 | return res.json(validations) 36 | } else { 37 | return res.status(404).send('Not found') 38 | } 39 | }) 40 | }) 41 | 42 | // Validations from a specfic validator 43 | ValidationController.route('/validator/:master_key') 44 | .get((req: Request, res: Response) => { 45 | Validation 46 | .find({master_key: req.params?.master_key}) 47 | .select('-_id -__v') 48 | .sort({ledger_index: 'desc'}) 49 | .limit(1024) 50 | .exec((error, validations) => { 51 | if (error) { 52 | return res.status(500).send('Error') 53 | } else if (validations) { 54 | return res.json(validations) 55 | } else { 56 | return res.status(404).send('Not found') 57 | } 58 | }) 59 | }) 60 | 61 | export default ValidationController -------------------------------------------------------------------------------- /src/grpc/server/index.ts: -------------------------------------------------------------------------------- 1 | import * as grpc from '@grpc/grpc-js' 2 | import fs from 'node:fs' 3 | import chalk from 'chalk' 4 | import { ValidationsService } from '../../protos/pb/validations_grpc_pb' 5 | import { PingService } from '../../protos/pb/ping_grpc_pb' 6 | import { 7 | getValidationsByLedger, 8 | getValidationsByLedgerRange, 9 | getValidationsByMasterKey 10 | } from './ValidationRequest' 11 | import { ping } from './PingRequest' 12 | import ENV from '../../lib/ENV' 13 | import logger from '../../logger' 14 | 15 | export const startgRPCServer = () => { 16 | const server = new grpc.Server() 17 | server.addService(ValidationsService, { 18 | getValidationsByLedger, 19 | getValidationsByLedgerRange, 20 | getValidationsByMasterKey 21 | }) 22 | server.addService(PingService, { ping }) 23 | const credentials = getServerCredentials() 24 | server.bindAsync( 25 | `${process.env.SERVER_GRPC_ADDRESS}:${process.env.SERVER_GRPC_PORT}`, 26 | credentials, 27 | (error, port) => { 28 | if (error) { 29 | logger.error('[gRPC]', error.message) 30 | } else { 31 | server.start() 32 | logger.info('[gRPC]', `${credentials._isSecure() ? chalk.green('Secure') : chalk.red('Insecure')} service started on ${ENV.SERVER_HOSTNAME}:${port}`) 33 | } 34 | }) 35 | } 36 | 37 | const getServerCredentials = (): grpc.ServerCredentials => { 38 | if ( 39 | process.env.SERVER_PRIVATE_KEY && 40 | process.env.SERVER_CERTIFICATE && 41 | fs.existsSync(process.env.SERVER_PRIVATE_KEY) && 42 | fs.existsSync(process.env.SERVER_CERTIFICATE) 43 | ) { 44 | const private_key = fs.readFileSync(process.env.SERVER_PRIVATE_KEY) 45 | const cert_chain = fs.readFileSync(process.env.SERVER_CERTIFICATE) 46 | return grpc.ServerCredentials.createSsl( 47 | null, 48 | [{ 49 | private_key: private_key, 50 | cert_chain: cert_chain 51 | }], 52 | ) 53 | } 54 | return grpc.ServerCredentials.createInsecure() 55 | } -------------------------------------------------------------------------------- /src/http/index.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import chalk from 'chalk' 3 | import https from 'node:https' 4 | import http from 'node:http' 5 | import fs from 'node:fs' 6 | import express, { Express, Request, Response, NextFunction } from 'express' 7 | import ValidationController from '../controllers/ValidationController' 8 | import ValidatorController from '../controllers/ValidatorController' 9 | import PeerController from '../controllers/PeerController' 10 | import ServerController from '../controllers/ServerController' 11 | import PingController from '../controllers/PingController' 12 | import ENV from '../lib/ENV' 13 | import logger from '../logger' 14 | 15 | export const startExpressServer = () => { 16 | const app: Express = express() 17 | 18 | // Enable CORS 19 | app.use((req: Request, res: Response, next: NextFunction) => { 20 | res.header('Access-Control-Allow-Origin', '*') 21 | res.header('Access-Control-Allow-Methods', 'GET,OPTIONS') 22 | next() 23 | }) 24 | 25 | // Routes 26 | app.use('/api/v1/validation', ValidationController) 27 | app.use('/api/v1/validations', ValidationController) 28 | app.use('/api/v1/validator', ValidatorController) 29 | app.use('/api/v1/validators', ValidatorController) 30 | app.use('/api/v1/peers', PeerController) 31 | app.use('/api/v1/server', ServerController) 32 | app.use('/api/v1/ping', PingController) 33 | app.use(express.static('public')) 34 | 35 | // Start https server if server certificate and private key is present. 36 | // Otherwise start http service 37 | if (fs.existsSync(ENV.SERVER_PRIVATE_KEY) && fs.existsSync(ENV.SERVER_CERTIFICATE)) { 38 | const options = { 39 | key: fs.readFileSync(ENV.SERVER_PRIVATE_KEY), 40 | cert: fs.readFileSync(ENV.SERVER_CERTIFICATE) 41 | } 42 | const tlsserver = https 43 | .createServer(options, app) 44 | .listen(ENV.SERVER_REST_PORT, () => { 45 | logger.info('[REST]', `${chalk.green('Secure')} service started on https://${ENV.SERVER_HOSTNAME}:${ENV.SERVER_REST_PORT}`) 46 | }) 47 | } else { 48 | const httpserver = http 49 | .createServer({}, app) 50 | .listen(ENV.SERVER_REST_PORT, () => { 51 | logger.info('[REST]', `${chalk.red('Insecure')} service started on http://${ENV.SERVER_HOSTNAME}:${ENV.SERVER_REST_PORT}`) 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/protos/pb/ping_grpc_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: ping 2 | // file: ping.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as grpc from "@grpc/grpc-js"; 8 | import * as ping_pb from "./ping_pb"; 9 | 10 | interface IPingService extends grpc.ServiceDefinition { 11 | ping: IPingService_IPing; 12 | } 13 | 14 | interface IPingService_IPing extends grpc.MethodDefinition { 15 | path: "/ping.Ping/Ping"; 16 | requestStream: false; 17 | responseStream: false; 18 | requestSerialize: grpc.serialize; 19 | requestDeserialize: grpc.deserialize; 20 | responseSerialize: grpc.serialize; 21 | responseDeserialize: grpc.deserialize; 22 | } 23 | 24 | export const PingService: IPingService; 25 | 26 | export interface IPingServer extends grpc.UntypedServiceImplementation { 27 | ping: grpc.handleUnaryCall; 28 | } 29 | 30 | export interface IPingClient { 31 | ping(request: ping_pb.PingRequest, callback: (error: grpc.ServiceError | null, response: ping_pb.PongResponse) => void): grpc.ClientUnaryCall; 32 | ping(request: ping_pb.PingRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: ping_pb.PongResponse) => void): grpc.ClientUnaryCall; 33 | ping(request: ping_pb.PingRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: ping_pb.PongResponse) => void): grpc.ClientUnaryCall; 34 | } 35 | 36 | export class PingClient extends grpc.Client implements IPingClient { 37 | constructor(address: string, credentials: grpc.ChannelCredentials, options?: Partial); 38 | public ping(request: ping_pb.PingRequest, callback: (error: grpc.ServiceError | null, response: ping_pb.PongResponse) => void): grpc.ClientUnaryCall; 39 | public ping(request: ping_pb.PingRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: ping_pb.PongResponse) => void): grpc.ClientUnaryCall; 40 | public ping(request: ping_pb.PingRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: ping_pb.PongResponse) => void): grpc.ClientUnaryCall; 41 | } 42 | -------------------------------------------------------------------------------- /assets/hermes-architecture.drawio: -------------------------------------------------------------------------------- 1 | 7VzbcqM4EP0aP2YKJCDkcZxkJrWVrU1NZi/zNCWDjNnIyCPLjr1fvxJI3AQ2ia9JcB4CjRDQfU6rdZA9gNfT1VeGZpPfaYjJAFjhagBvBgAAywPin7SsM4ttO35miVgcKltheIz/w8poKesiDvG80pBTSng8qxoDmiQ44BUbYow+V5uNKaledYYibBgeA0RM699xyCf6MSyrOHCH42iiLu276sAIBU8Ro4tEXS+hCc6OTJHuRjWdT1BIn0smeDuA14xSnm1NV9eYSL9qj2XnfWk5mt8ywwnvcgL/8TXmy4ennz9//Rn89ssbrn5EF6qXJSIL5YoB8IjobzgRh7xIbt1hNhXBUfYR03ZtEZcsGqtn5WvtWo5XaXd8SoTBFptzzugTvqaEMmFJPQaH45iQmmk+Q0GcRMLgFnvf6UwYLsSzwuHzJOb4UdjlpZ4FNIWNLjEbk9TPkzgMcSJsaXywdIOV36FoJu6s1ZV2HiABekynmLO1aKJOcKCK6boK4+cSdjRCJiXY6NOQgmuU91zETWyo0L0kjF5DHGuBkDCdbXp+RS400mdYL/ZL1S2+6RYIG9wCoLe7X+CVcDn2HX9su75jBQ3QTkTiEhbbcI10RiyywWcSRwIwN1yCbIjUHsFjXgegXxju08M3oLB8U49mp7YJmsnLBItRCuos99kSiWHMhL9jKi8ypwvpo+GYJvxR3ZlT5U0Vxnk6kTsBncaB2iZohMkwz0t1UqXk00nOVlf8gqYxkVH7C7MQJUjfiLpZYAJhMwzr4DIRsxUT4FBcAQYuJirB1VBR9XdTsikFp5y/BgCGCPvjwEh24ogX+Hg07pqHNri5lYW2t5WGNmhwuXcoj3uGa3Eohl+1Sxmf0IgmiNwW1mGwYMvU99K5RZN7mtJPGv/FnK8VQtGC01o8yqi+w2SJJcPTgCDGP8uaoUwKYfsSy6dKY42TULcICJrPJbekUTWx8+jJ53hN7IQz6IIFeENDqMogxCK8qUPQjAWGCeLxsnp3e48sbC0fpP9lJYbUQ3q/FjQrJnQocpMuHVg8mxER86KsyDppKSwKetod6NkGhxpvx678UyeU7Nmnkc/p55B8BlU6O55BZ/+YtYa7vdTYTu99MrqSKir0LshbZrhdZbjKAQW9rWPQ23kL9HaMUE9pEtFwZEQ8r3LWJBasZHA7JUcZf+9HuSGvWf5YcNEN1iFVVYj7Ah6HLvZDp4mvPhjBA/PVrfDVBiZhLxsI6x+KsJcdCNs04L2Ye1nlSZmIf8nhVvrZmVMbS8imyZa2daaKusIDjdOhS4Pi6qoaTqcWp4zq6qzy/LvWEbCqedyY9WS5wOhIBAatS81mssHcQEXumB1mkZaBlG+3j98b0/u9jHUVJHq+FIhwYhH8YX1aNRUT8jT7Myw4reaZEhnqkUTn7nDg3mwCXCtru8xe2+nRSmbrk227XjVwu+FKN6Hj8Rzzg8TRzNs947szHjiXe2K8VkHPl/FNxVxWfofxUpfe0beH61JVXjryzlNDRqT3lRs6VAOZUriLNzfLhJ51Qp2w2Su+4RUlFpre6cXCTmLhqwrrlrH4dGKhfWUA482phZsLnLNTC/P3Zb1cWKTrrXpC/tp0m6CQIfpUigJof+HYK4Z74/SZKYa6xOglw90o3vWVwIkpbr4U6FXDDpw9M9UQvHkRYXsx2cuG+5hSAlNE+ACyIXh/2gDosIqop3wr5T+QbgiaVKReN6wS6V3lBmgKIUYQD60bQmNeo2qU0wmHwHSLEg6h4Z5eODyYcAhbZjw6E5u19JF1RH21N6wjtvm4RUe8Mn1+XB0RNqkOvdS057DXUrL+JsippCbYtCKxl5pqg/j2xaddl6dBqxkdR1p+2i9QexVna1KTY5L2qFIT7LKk1KDEvied3bm5NyaebKZaz7+dZ6bQbhzkz2ZiCk0J4wNoURmB3td882WrVvucsHNOuHxtTrg895xgLu7pxao6095X8jC/4vlxl5B0L/pVXM+86HdMKeU7W8y5EXJd8o8JXinnNvu5FMQt40G3GI8IDZ66Rcw2Y9P2Rebtr/39U44fbl2cfe144l7WOqqLci0Dyr6Sh9O+RKk0KCjIdRovjgnEShbDq5j/U9r+IbPNJ9vy1P7NSqcfubMu7TxgFgtHymGsa02zTyiDk5ZCBpTrGuWbgbIpgZ4+VVYRmognlBC9sD5ZlqstFaBKQ4HUdG9d3tsPVjeuC39rgD2/3Ct2i5+6yZoXvyUEb/8H -------------------------------------------------------------------------------- /src/cmd/peer/AddPeer.ts: -------------------------------------------------------------------------------- 1 | import https from 'node:https' 2 | import fetch from 'node-fetch' 3 | import Table from 'cli-table3' 4 | import chalk from 'chalk' 5 | import Peer, { IPeer } from '../../models/Peer' 6 | import PeerConnection from '../../models/PeerConnection' 7 | import { SYSTEM_CONFIG_ENDPOINT } from '../../lib/Constants' 8 | import mongoose from 'mongoose' 9 | 10 | const addPeer = async (argv: any): Promise => { 11 | if (argv.url) { 12 | let url = argv.url.includes('://') ? argv.url : `https://${argv.url}` 13 | url = url.endsWith(SYSTEM_CONFIG_ENDPOINT) ? url : `${url}${SYSTEM_CONFIG_ENDPOINT}` 14 | try { 15 | const agent = new https.Agent({ rejectUnauthorized: false }) 16 | const res = await fetch(url, { agent: agent }) 17 | if (res.ok) { 18 | const peer = await res.json() 19 | await printPeer(peer) 20 | await savePeer(peer) 21 | } else { 22 | console.error(`Status: ${res.status}: ${res.statusText}`) 23 | } 24 | } catch (error: any) { 25 | if (error.message) { 26 | console.error(`${chalk.red('Error:')} ${url} - ${error.message} `) 27 | } 28 | } 29 | } else { 30 | console.error(`${chalk.red('Error:')} System config url is required`) 31 | } 32 | await mongoose.disconnect() 33 | } 34 | 35 | const printPeer = async (peer: IPeer): Promise => { 36 | let table = new Table() 37 | table.push( 38 | { 'Node Id': peer.node_id }, 39 | { 'REST URL': peer.rest_url }, 40 | { 'gRPC URL': peer.grpc_url }, 41 | { 'Fingerprint': peer.fingerprint }, 42 | ) 43 | console.log(table.toString()) 44 | } 45 | 46 | const savePeer = async (peer: IPeer): Promise => { 47 | const connection = new PeerConnection(peer) 48 | const restOK = await connection.restOK() 49 | const grpcOK = await connection.grpcOK() 50 | console.log(`REST connection: ${restOK ? chalk.green('OK') : chalk.red('FAILED')}`) 51 | console.log(`gRPC connection: ${grpcOK ? chalk.green('OK') : chalk.red('FAILED')}`) 52 | if (restOK && grpcOK) { 53 | try { 54 | await Peer.create(peer) 55 | console.log(`Added node ${peer.node_id} to peer list`) 56 | } catch (error: any) { 57 | console.error(`${chalk.red('Error:')} Failed to save node - ${error.message}`) 58 | } 59 | } 60 | } 61 | 62 | const AddPeerCommand = { 63 | command: 'add [url]', 64 | describe: 'Add a new peer from system config url', 65 | builder: {}, 66 | handler: addPeer, 67 | } 68 | export default AddPeerCommand -------------------------------------------------------------------------------- /src/grpc/client/client.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Code examples for querying Hermes gRPC service 3 | */ 4 | 5 | import * as grpc from "@grpc/grpc-js" 6 | import fs from 'node:fs' 7 | import { 8 | LedgerRequest, 9 | LedgerRangeRequest, 10 | MasterKeyRequest, 11 | ValidationResponse 12 | } from '../../protos/pb/validations_pb' 13 | import { ValidationsClient } from '../../protos/pb/validations_grpc_pb' 14 | import { PingRequest, PongResponse } from "../../protos/pb/ping_pb" 15 | import { PingClient } from '../../protos/pb/ping_grpc_pb' 16 | 17 | // Validation client 18 | const creds = (): grpc.ChannelCredentials => { 19 | const cert = fs.readFileSync('./config/cert.pem') 20 | return grpc.credentials.createSsl(cert) 21 | } 22 | 23 | const client = new ValidationsClient( 24 | 'localhost:50589', 25 | creds() 26 | ) 27 | 28 | // Example 1 - getValidationsByLedger 29 | 30 | const ledgerRequest = new LedgerRequest() 31 | ledgerRequest.setLedgerIndex(72982904) 32 | ledgerRequest.setRequestingNode('hEf94tMDQLuatpNk63mfVgXrFvo4YNDZvBTZtCZktrpd4') 33 | ledgerRequest.setRequestingHost('peer.example.com') 34 | 35 | client.getValidationsByLedger(ledgerRequest) 36 | .on('data', (validation: ValidationResponse) => { 37 | console.log('[getValidationsByLedger]' + validation.getLedgerIndex()) 38 | }) 39 | .on('end', (error: any)=>{ 40 | console.log('Stream ended') 41 | }) 42 | .on('error', (error: any)=>{ 43 | console.error(error) 44 | }) 45 | 46 | // Example 2 - getValidationsByMasterKey 47 | 48 | const masterKeyRequest = new MasterKeyRequest() 49 | masterKeyRequest.setMasterKey('nHDB2PAPYqF86j9j3c6w1F1ZqwvQfiWcFShZ9Pokg9q4ohNDSkAz') 50 | masterKeyRequest.setRequestingNode('hEf94tMDQLuatpNk63mfVgXrFvo4YNDZvBTZtCZktrpd4') 51 | masterKeyRequest.setRequestingHost('peer.example.com') 52 | 53 | client.getValidationsByMasterKey(masterKeyRequest) 54 | .on('data', (validation: ValidationResponse) => { 55 | console.log('[getValidationsByMasterKey]' + validation.getMasterKey()) 56 | }) 57 | 58 | // Example 3 - getValidationsByLedgerRange 59 | 60 | const ledgerRangeRequest = new LedgerRangeRequest() 61 | ledgerRangeRequest.setLedgerIndexMin(72982904) 62 | ledgerRangeRequest.setLedgerIndexMax(75088275) 63 | ledgerRangeRequest.setRequestingNode('hEf94tMDQLuatpNk63mfVgXrFvo4YNDZvBTZtCZktrpd4') 64 | ledgerRangeRequest.setRequestingHost('peer.example.com') 65 | 66 | client.getValidationsByLedgerRange(ledgerRangeRequest) 67 | .on('data', (validation: ValidationResponse) => { 68 | console.log('[getValidationsByLedgerRange]' + validation.getLedgerIndex()) 69 | }) 70 | -------------------------------------------------------------------------------- /src/models/PeerConnection.ts: -------------------------------------------------------------------------------- 1 | import https from 'node:https' 2 | import crypto from 'node:crypto' 3 | import fetch from 'node-fetch' 4 | import * as grpc from "@grpc/grpc-js" 5 | import { PingClient } from '../protos/pb/ping_grpc_pb' 6 | import { PingRequest, PongResponse } from "../protos/pb/ping_pb" 7 | import { IPeer } from './Peer' 8 | import LocalNode from '../lib/LocalNode' 9 | 10 | import { SYSTEM_CONFIG_ENDPOINT } from '../lib/Constants' 11 | 12 | class PeerConnection { 13 | private _rest_url: string 14 | private _grpc_url: string 15 | private _certificate: string 16 | private _fingerprint: string 17 | 18 | constructor(peer: IPeer) { 19 | this._rest_url = peer.rest_url 20 | this._grpc_url = peer.grpc_url 21 | this._certificate = Buffer.from(peer.certificate, 'base64').toString('ascii') 22 | this._fingerprint = peer.fingerprint 23 | } 24 | 25 | // Test if secure REST connection is OK 26 | async restOK(): Promise { 27 | let url = this._rest_url.startsWith('https://') ? this._rest_url : `https://${this._rest_url}` 28 | url = url.endsWith(SYSTEM_CONFIG_ENDPOINT) ? url : `${url}${SYSTEM_CONFIG_ENDPOINT}` 29 | 30 | try { 31 | const agent = new https.Agent({ ca: [this._certificate] }) 32 | const res = await fetch(url, { agent }) 33 | if (res.ok) { 34 | const data = await res.json() 35 | return (data.fingerprint === this._fingerprint) 36 | } else { 37 | return false 38 | } 39 | } catch (error: any) { 40 | console.error(`Error: ${error?.message}`) 41 | return false 42 | } 43 | } 44 | 45 | // Test if secure gRPC connection is OK 46 | async grpcOK(): Promise { 47 | const client = new PingClient(this._grpc_url, grpc.credentials.createSsl(Buffer.from(this._certificate))) 48 | const req = new PingRequest() 49 | const message = crypto.randomBytes(32).toString('hex') 50 | req.setMessage(message) 51 | if (LocalNode.node_id) req.setRequestingNode(LocalNode.node_id) 52 | if (LocalNode.host) req.setRequestingHost(LocalNode.host) 53 | 54 | try { 55 | return new Promise((resolve, reject) => client.ping(req, (error, res: PongResponse) => { 56 | const signature = res.getSignature() 57 | resolve(crypto.verify('SHA256', Buffer.from(message), this._certificate, Buffer.from(signature, 'hex'))) 58 | })) 59 | } catch (error: any) { 60 | console.error(`Error: ${error?.message}`) 61 | return false 62 | } 63 | } 64 | 65 | } 66 | 67 | export default PeerConnection -------------------------------------------------------------------------------- /src/protos/pb/ping_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: ping 2 | // file: ping.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class PingRequest extends jspb.Message { 10 | getMessage(): string; 11 | setMessage(value: string): PingRequest; 12 | 13 | hasRequestingNode(): boolean; 14 | clearRequestingNode(): void; 15 | getRequestingNode(): string | undefined; 16 | setRequestingNode(value: string): PingRequest; 17 | 18 | hasRequestingHost(): boolean; 19 | clearRequestingHost(): void; 20 | getRequestingHost(): string | undefined; 21 | setRequestingHost(value: string): PingRequest; 22 | 23 | serializeBinary(): Uint8Array; 24 | toObject(includeInstance?: boolean): PingRequest.AsObject; 25 | static toObject(includeInstance: boolean, msg: PingRequest): PingRequest.AsObject; 26 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 27 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 28 | static serializeBinaryToWriter(message: PingRequest, writer: jspb.BinaryWriter): void; 29 | static deserializeBinary(bytes: Uint8Array): PingRequest; 30 | static deserializeBinaryFromReader(message: PingRequest, reader: jspb.BinaryReader): PingRequest; 31 | } 32 | 33 | export namespace PingRequest { 34 | export type AsObject = { 35 | message: string, 36 | requestingNode?: string, 37 | requestingHost?: string, 38 | } 39 | } 40 | 41 | export class PongResponse extends jspb.Message { 42 | getMessage(): string; 43 | setMessage(value: string): PongResponse; 44 | getSignature(): string; 45 | setSignature(value: string): PongResponse; 46 | 47 | serializeBinary(): Uint8Array; 48 | toObject(includeInstance?: boolean): PongResponse.AsObject; 49 | static toObject(includeInstance: boolean, msg: PongResponse): PongResponse.AsObject; 50 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 51 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 52 | static serializeBinaryToWriter(message: PongResponse, writer: jspb.BinaryWriter): void; 53 | static deserializeBinary(bytes: Uint8Array): PongResponse; 54 | static deserializeBinaryFromReader(message: PongResponse, reader: jspb.BinaryReader): PongResponse; 55 | } 56 | 57 | export namespace PongResponse { 58 | export type AsObject = { 59 | message: string, 60 | signature: string, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Hermes environment settings 2 | 3 | # Set LOG_LEVEL 4 | # 5 | # Valid values are: 6 | # silly, verbose, info, timing, http, notice, warn, error, silent 7 | # 8 | # Default: info 9 | LOG_LEVEL = info 10 | 11 | # Update validator registry every Nth ledger. 12 | # 13 | # Default: 256 (ledgers) 14 | # VALIDATOR_SAMPLE_INTERVAL = 256 15 | 16 | # Enable Validation message ingress and storage. 17 | # If disabled, Hermes will only serve requests and won't store any new 18 | # validation messages 19 | # 20 | # Default: true 21 | INGRESS_ENABLED = true 22 | 23 | # Enable syncing validation messages from peers. 24 | # If disabled, Hermes will only serve requests and won't poll its peers 25 | # for validation messages 26 | # 27 | # Default: true 28 | PEERSYNC_ENABLED = true 29 | PEERSYNC_POLL_INTERVAL = 3600 30 | 31 | # The number of past ledger validations to request from other peers. As 32 | # per current ledger interval, XRPL closes roughly 1000 ledgers per hour. 33 | # 34 | # Default: 6000 35 | # PEERSYNC_FETCH_DEPTH = 6000 36 | 37 | # PEER_PRIVATE flag's behavior does not resemble 38 | # rippled's [peer_private] setting yet. At the moment, 39 | # this flag will supress Peer discovery via REST and 40 | # gRPC interfaces. 41 | # 42 | # Default: false 43 | PEER_PRIVATE = false 44 | 45 | # Server hostname will be used to generate certificate and advertise 46 | # access to gRPC and REST services. The hostname must resolve. 47 | # 48 | # Default: localhost 49 | SERVER_HOSTNAME = 'localhost' 50 | 51 | # Server key and certificate location 52 | SERVER_PRIVATE_KEY = 'config/private.pem' 53 | SERVER_CERTIFICATE = 'config/cert.pem' 54 | 55 | # HTTP/REST service 56 | # 57 | # Default: 50588 58 | SERVER_REST_ENABLED = true 59 | SERVER_REST_PORT = 50588 60 | 61 | # gRPC service 62 | SERVER_GRPC_ENABLED = true 63 | SERVER_GRPC_ADDRESS = '0.0.0.0' 64 | SERVER_GRPC_PORT = 50589 65 | 66 | # Express.js settings 67 | EXPRESS_REQUEST_LOG = 'log/express.log'; 68 | EXPRESS_LOG_FORMAT =':remote-addr - [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms'; 69 | 70 | # Mongodb settings 71 | MONGODB_HOST = 'localhost' 72 | MONGODB_PORT = 27017 73 | MONGODB_DATABASE = 'hermes_prod' 74 | 75 | # XRPL node settings 76 | RIPPLED_URL = 'ws://localhost:6005' 77 | 78 | # Online deletion: Specify how many validations to keep. The service 79 | # periodically deletes any validations with ledger_index older than this number. 80 | # ONLINE_DELETE setting specifies ledger count. A value of 250000 would allow 81 | # the service to hold approx 10 days worth of validations. If omitted or 82 | # commented out, validations are not deleted. 83 | # 84 | # Default: (disabled, 250000) 85 | # ONLINE_DELETE = 250000 86 | 87 | # Server certificate SUBJECT settings 88 | SERVER_CERTIFICATE_DAYS = 3650 89 | SERVER_CERTIFICATE_SUBJECT_C = 'CH' 90 | SERVER_CERTIFICATE_SUBJECT_ST = 'State' 91 | SERVER_CERTIFICATE_SUBJECT_L = 'Location' 92 | SERVER_CERTIFICATE_SUBJECT_O = 'Organization' 93 | -------------------------------------------------------------------------------- /src/processors/ValidationMessage.ts: -------------------------------------------------------------------------------- 1 | import { decodeNodePublic } from 'ripple-address-codec' 2 | import { verify_validation } from '../lib/VerifyValidation' 3 | import Validation, { IValidation } from '../models/Validation' 4 | import logger from '../logger' 5 | import { MongoServerError } from 'mongodb' 6 | import ValidatorRegistry from './ValidatorRegistry' 7 | import ENV from '../lib/ENV' 8 | 9 | 10 | const LOGPREFIX = '[validation-manager]' 11 | 12 | class ValidationMessage { 13 | private _validation: IValidation 14 | private _signingPubKey: string 15 | 16 | constructor(validation: IValidation) { 17 | this._validation = validation 18 | this._signingPubKey = decodeNodePublic(validation.validation_public_key).toString('hex').toUpperCase() 19 | } 20 | 21 | create(): boolean { 22 | if (this.verify()) { 23 | Validation.create(this._validation, (error) => { 24 | if (error instanceof MongoServerError && error.code === 11000) { 25 | logger.verbose(LOGPREFIX, `${error}`) 26 | } 27 | else if (error) { 28 | logger.error(LOGPREFIX, `${error}`) 29 | } 30 | }) 31 | if (this._validation.ledger_index % ENV.VALIDATOR_SAMPLE_INTERVAL === 0) { 32 | const registry = new ValidatorRegistry(this._validation) 33 | registry.refresh() 34 | } 35 | return true 36 | } 37 | else { 38 | logger.verbose(LOGPREFIX, `Error: Signature verification failed for ${this._validation.validation_public_key}.${this._validation.ledger_index}`) 39 | return false 40 | } 41 | } 42 | 43 | verify(): boolean { 44 | if ( this._validation?.data && this._validation?.validation_public_key) { 45 | const result = verify_validation(this._signingPubKey, this._validation.data.toUpperCase()) 46 | let _verified = result['_verified'] 47 | 48 | // First check if the property keys are present in verified and unverified objects 49 | // If yes, then check if the property values are the same 50 | if (_verified && this._validation?.flags && result['Flags']) { 51 | _verified &&= String(this._validation?.flags) === result['Flags'] 52 | } 53 | if (_verified && this._validation?.ledger_index && result['LedgerSequence']) { 54 | _verified &&= String(this._validation.ledger_index) === result['LedgerSequence'] 55 | } 56 | if (_verified && this._validation?.signing_time && result['SigningTime']) { 57 | _verified &&= String(this._validation.signing_time) === result['SigningTime'] 58 | } 59 | if (_verified && this._validation?.cookie && result['Cookie']) { 60 | _verified &&= this._validation?.cookie === result['Cookie'] 61 | } 62 | if (_verified && this._validation?.ledger_hash && result['LedgerHash']) { 63 | _verified &&= this._validation.ledger_hash === result['LedgerHash'] 64 | } 65 | if (_verified && this._validation?.validated_hash && result['ValidatedHash']) { 66 | _verified &&= this._validation?.validated_hash === result['ValidatedHash'] 67 | } 68 | if (_verified && this._signingPubKey && result['SigningPubKey']) { 69 | _verified &&= this._signingPubKey === result['SigningPubKey'] 70 | } 71 | if (_verified && this._validation?.signature && result['Signature']) { 72 | _verified &&= this._validation.signature === result['Signature'] 73 | } 74 | return _verified 75 | } else { 76 | return false 77 | } 78 | } 79 | } 80 | 81 | export default ValidationMessage -------------------------------------------------------------------------------- /src/processors/peersync/PollService.ts: -------------------------------------------------------------------------------- 1 | import * as grpc from "@grpc/grpc-js" 2 | import logger from "../../logger" 3 | import { IPeer } from '../../models/Peer' 4 | import { IValidation } from '../../models/Validation' 5 | import { ValidationsClient } from '../../protos/pb/validations_grpc_pb' 6 | import { 7 | LedgerRangeRequest, 8 | ValidationResponse, 9 | } from '../../protos/pb/validations_pb' 10 | import PeerManager from "./PeerManager" 11 | import LocalNode from '../../lib/LocalNode' 12 | import ValidationMessage from '../ValidationMessage' 13 | 14 | const LOGPREFIX = '[peersync]' 15 | 16 | class PollService { 17 | private readonly _peer: IPeer 18 | private readonly _manager: PeerManager 19 | private _startTime: number 20 | private _totalDocs: number 21 | 22 | constructor(peer: IPeer, manager: PeerManager) { 23 | this._peer = peer 24 | this._manager = manager 25 | this._startTime = new Date().getTime() 26 | this._totalDocs = 0 27 | } 28 | 29 | private credentials(): grpc.ChannelCredentials { 30 | const certificate = Buffer.from(this._peer.certificate, 'base64') 31 | return grpc.credentials.createSsl(certificate) 32 | } 33 | 34 | async fetch(ledgerIndexMin: number, ledgerIndexMax: number) { 35 | const client = new ValidationsClient(this._peer.grpc_url, this.credentials()) 36 | const ledgerRangeRequest = new LedgerRangeRequest() 37 | ledgerRangeRequest.setLedgerIndexMin(ledgerIndexMin) 38 | ledgerRangeRequest.setLedgerIndexMax(ledgerIndexMax) 39 | if (LocalNode.node_id) ledgerRangeRequest.setRequestingNode(LocalNode.node_id) 40 | if (LocalNode.host) ledgerRangeRequest.setRequestingHost(LocalNode.host) 41 | 42 | client.getValidationsByLedgerRange(ledgerRangeRequest) 43 | .on('data', (validation: ValidationResponse) => { 44 | try { 45 | const vm = new ValidationMessage(this.deserialize(validation)) 46 | vm.create() 47 | } catch { 48 | } 49 | this._totalDocs++ 50 | }) 51 | .on('end', () => { 52 | delete this._manager.connections[this._peer.node_id] 53 | const timeTaken = new Date().getTime() - this._startTime 54 | logger.info(LOGPREFIX, `Peer ${this._peer.node_id} ${this._peer.grpc_url} sync complete (${this._totalDocs} validations in ${timeTaken/1000} sec)`) 55 | }) 56 | .on('error', (error: any) => { 57 | delete this._manager.connections[this._peer.node_id] 58 | logger.error(LOGPREFIX, `${this._peer.node_id} ${this._peer.grpc_url} ${error}`) 59 | }) 60 | } 61 | 62 | // Internal: Deserialize Protobuf message to object 63 | private deserialize(validation: ValidationResponse): IValidation { 64 | return { 65 | cookie: validation.getCookie(), 66 | data: validation.getData(), 67 | type: validation.getType(), 68 | flags: validation.getFlags(), 69 | full: validation.getFull(), 70 | ledger_hash: validation.getLedgerHash(), 71 | ledger_index: validation.getLedgerIndex(), 72 | master_key: validation.getMasterKey(), 73 | signature: validation.getSignature(), 74 | signing_time: validation.getSigningTime(), 75 | validated_hash: validation.getValidatedHash(), 76 | validation_public_key: validation.getValidationPublicKey(), 77 | server_version: validation.getServerVersion(), 78 | base_fee: validation.getBaseFee(), 79 | load_fee: validation.getLoadFee(), 80 | reserve_base: validation.getReserveBase(), 81 | reserve_inc: validation.getReserveInc(), 82 | } 83 | } 84 | } 85 | 86 | export default PollService -------------------------------------------------------------------------------- /src/grpc/server/ValidationRequest.ts: -------------------------------------------------------------------------------- 1 | import * as grpc from '@grpc/grpc-js' 2 | import { 3 | LedgerRequest, 4 | LedgerRangeRequest, 5 | MasterKeyRequest, 6 | ValidationResponse 7 | } from '../../protos/pb/validations_pb' 8 | import Validation from "../../models/Validation" 9 | import logger from '../../logger' 10 | 11 | const LOGPREFIX = '[grpc]' 12 | 13 | export const getValidationsByLedger = async ( call: grpc.ServerWritableStream ) => { 14 | const request = call.request as LedgerRequest 15 | logger.info(LOGPREFIX, `Ledger request from ${request.getRequestingNode()} ${request.getRequestingHost()}: ${request.getLedgerIndex()}`) 16 | const cursor = Validation 17 | .find({ ledger_index: request.getLedgerIndex() }) 18 | .lean() 19 | .cursor() 20 | 21 | cursor.on('data', async (doc) => { 22 | cursor.pause() 23 | call.write(serialize(doc), () => { 24 | cursor.resume() 25 | }) 26 | }) 27 | cursor.on('end', () => { 28 | call.end() 29 | }) 30 | cursor.on('error', (error) => { 31 | call.end() 32 | logger.error(LOGPREFIX, `${error}`) 33 | }) 34 | } 35 | 36 | export const getValidationsByLedgerRange = async ( call: grpc.ServerWritableStream ) => { 37 | const request = call.request as LedgerRangeRequest 38 | logger.info(LOGPREFIX, `LedgerRange request from ${request.getRequestingNode()} ${request.getRequestingHost()}: [${request.getLedgerIndexMin()}..${request.getLedgerIndexMax()}]`) 39 | const cursor = Validation 40 | .find({ 41 | ledger_index: { 42 | $gte: request.getLedgerIndexMin(), 43 | $lte: request.getLedgerIndexMax() 44 | } 45 | }) 46 | .lean() 47 | .cursor() 48 | 49 | cursor.on('data', async (doc) => { 50 | cursor.pause() 51 | call.write(serialize(doc), () => { 52 | cursor.resume() 53 | }) 54 | }) 55 | cursor.on('end', () => { 56 | call.end() 57 | }) 58 | cursor.on('error', (error) => { 59 | call.end() 60 | logger.error(LOGPREFIX, `${error}`) 61 | }) 62 | } 63 | 64 | export const getValidationsByMasterKey = async ( call: grpc.ServerWritableStream ) => { 65 | const request = call.request as MasterKeyRequest 66 | logger.info(LOGPREFIX, `MasterKey request from ${request.getRequestingNode()} ${request.getRequestingHost()}: ${request.getMasterKey()}`) 67 | const cursor = Validation 68 | .find({ master_key: request.getMasterKey() }) 69 | .lean() 70 | .cursor() 71 | 72 | cursor.on('data', async (doc) => { 73 | cursor.pause() 74 | call.write(serialize(doc), () => { 75 | cursor.resume() 76 | }) 77 | }) 78 | cursor.on('end', () => { 79 | call.end() 80 | }) 81 | cursor.on('error', (error) => { 82 | call.end() 83 | logger.error(LOGPREFIX, `${error}`) 84 | }) 85 | } 86 | 87 | // Internal: Serialize Mongoose object to Protobuf message 88 | const serialize = (doc: any): ValidationResponse => { 89 | const validation = new ValidationResponse() 90 | validation.setCookie(doc.cookie) 91 | validation.setData(doc.data) 92 | validation.setType(doc.type) 93 | validation.setFlags(doc.flags) 94 | validation.setFull(doc.full) 95 | validation.setLedgerHash(doc.ledger_hash) 96 | validation.setLedgerIndex(doc.ledger_index) 97 | validation.setMasterKey(doc.master_key) 98 | validation.setSignature(doc.signature) 99 | validation.setSigningTime(doc.signing_time) 100 | validation.setValidatedHash(doc.validated_hash) 101 | validation.setValidationPublicKey(doc.validation_public_key) 102 | validation.setServerVersion(doc.server_version) 103 | validation.setBaseFee(doc.base_fee) 104 | validation.setLoadFee(doc.load_fee) 105 | validation.setReserveBase(doc.reserve_base) 106 | validation.setReserveInc(doc.reserve_inc) 107 | return validation 108 | } -------------------------------------------------------------------------------- /src/lib/Keypair.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import crypto from 'node:crypto' 3 | import { spawnSync } from 'node:child_process' 4 | import ENV from './ENV' 5 | 6 | class Keypair { 7 | private _private_key_file: string 8 | private _private_key: crypto.KeyObject | void 9 | private _public_key: crypto.KeyObject | void 10 | 11 | constructor(private_key_file: string) { 12 | this._private_key_file = private_key_file 13 | if (fs.existsSync(private_key_file)) { 14 | const data = fs.readFileSync(private_key_file, 'utf8') 15 | try { 16 | this._private_key = crypto.createPrivateKey(data) 17 | } catch (error) { 18 | if (error instanceof Error) { 19 | console.error(`Error: Unable to load private key from file '${private_key_file}' ` + error.message) 20 | } 21 | } 22 | try { 23 | this._public_key = crypto.createPublicKey(data) 24 | } catch (error) { 25 | if (error instanceof Error) { 26 | console.error(`Error: Unable to derive public key from file '${private_key_file}' ` + error.message) 27 | } 28 | } 29 | } else { 30 | console.error(`Private key '${private_key_file}' is missing`) 31 | } 32 | } 33 | 34 | get privateKey(): crypto.KeyObject | void { 35 | return this._private_key 36 | } 37 | 38 | get publicKey(): crypto.KeyObject | void { 39 | return this._public_key 40 | } 41 | 42 | get privateKeyPEM(): string | Buffer | void { 43 | if (this._private_key) { 44 | return this._private_key.export({ format: 'pem', type: 'pkcs8' }) 45 | } 46 | } 47 | 48 | get publicKeyPEM(): string | Buffer | void { 49 | if (this._public_key) { 50 | return this._public_key.export({ format: 'pem', type: 'spki' }) 51 | } 52 | } 53 | 54 | get fingerprint256(): string | void { 55 | if (this._public_key) { 56 | return crypto 57 | .createHash('sha256') 58 | .update(this._public_key.export({ format: 'der', type: 'spki' })) 59 | .digest('hex') 60 | } 61 | } 62 | 63 | get isValid(): boolean { 64 | return (this.privateKey && this.publicKey) ? true : false 65 | } 66 | 67 | generateKeys(): void { 68 | const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { 69 | modulusLength: 4096, 70 | }) 71 | this._private_key = privateKey 72 | this._public_key = publicKey 73 | this.savePrivateKey() 74 | } 75 | 76 | generateCert(): boolean { 77 | if (ENV.SERVER_PRIVATE_KEY && ENV.SERVER_CERTIFICATE) { 78 | const _days = ENV.SERVER_CERTIFICATE_DAYS || '3650' 79 | const _C = ENV.SERVER_CERTIFICATE_SUBJECT_C 80 | const _ST = ENV.SERVER_CERTIFICATE_SUBJECT_ST 81 | const _L = ENV.SERVER_CERTIFICATE_SUBJECT_L 82 | const _O = ENV.SERVER_CERTIFICATE_SUBJECT_O 83 | const _CN = ENV.SERVER_CERTIFICATE_SUBJECT_CN 84 | const cmd = spawnSync('openssl',[ 85 | 'req', 86 | '-key', 87 | ENV.SERVER_PRIVATE_KEY, 88 | '-new', 89 | '-x509', 90 | '-days', 91 | _days, 92 | '-out', 93 | ENV.SERVER_CERTIFICATE, 94 | '-subj', 95 | `/C=${_C}/ST=${_ST}/L=${_L}/O=${_O}/CN=${_CN}`, 96 | ]) 97 | if (cmd.error) { 98 | console.error(`Error: Cannot generate certificate: ${cmd.error}` ) 99 | return false 100 | } else { 101 | return true 102 | } 103 | } else { 104 | return false 105 | } 106 | } 107 | 108 | private savePrivateKey(): void { 109 | if (this.privateKeyPEM) { 110 | fs.writeFile(this._private_key_file, this.privateKeyPEM, { mode: 0o600 }, (error) => { 111 | if (error instanceof Error) { 112 | console.error(`Error: Unable to save private key '${this._private_key_file}' ${error.message}`) 113 | } else { 114 | this.generateCert() 115 | } 116 | }) 117 | } 118 | } 119 | } 120 | 121 | export default Keypair -------------------------------------------------------------------------------- /src/protos/pb/validations_grpc_pb.js: -------------------------------------------------------------------------------- 1 | // GENERATED CODE -- DO NOT EDIT! 2 | 3 | 'use strict'; 4 | var grpc = require('@grpc/grpc-js'); 5 | var validations_pb = require('./validations_pb.js'); 6 | 7 | function serialize_validations_LedgerRangeRequest(arg) { 8 | if (!(arg instanceof validations_pb.LedgerRangeRequest)) { 9 | throw new Error('Expected argument of type validations.LedgerRangeRequest'); 10 | } 11 | return Buffer.from(arg.serializeBinary()); 12 | } 13 | 14 | function deserialize_validations_LedgerRangeRequest(buffer_arg) { 15 | return validations_pb.LedgerRangeRequest.deserializeBinary(new Uint8Array(buffer_arg)); 16 | } 17 | 18 | function serialize_validations_LedgerRequest(arg) { 19 | if (!(arg instanceof validations_pb.LedgerRequest)) { 20 | throw new Error('Expected argument of type validations.LedgerRequest'); 21 | } 22 | return Buffer.from(arg.serializeBinary()); 23 | } 24 | 25 | function deserialize_validations_LedgerRequest(buffer_arg) { 26 | return validations_pb.LedgerRequest.deserializeBinary(new Uint8Array(buffer_arg)); 27 | } 28 | 29 | function serialize_validations_MasterKeyRequest(arg) { 30 | if (!(arg instanceof validations_pb.MasterKeyRequest)) { 31 | throw new Error('Expected argument of type validations.MasterKeyRequest'); 32 | } 33 | return Buffer.from(arg.serializeBinary()); 34 | } 35 | 36 | function deserialize_validations_MasterKeyRequest(buffer_arg) { 37 | return validations_pb.MasterKeyRequest.deserializeBinary(new Uint8Array(buffer_arg)); 38 | } 39 | 40 | function serialize_validations_ValidationResponse(arg) { 41 | if (!(arg instanceof validations_pb.ValidationResponse)) { 42 | throw new Error('Expected argument of type validations.ValidationResponse'); 43 | } 44 | return Buffer.from(arg.serializeBinary()); 45 | } 46 | 47 | function deserialize_validations_ValidationResponse(buffer_arg) { 48 | return validations_pb.ValidationResponse.deserializeBinary(new Uint8Array(buffer_arg)); 49 | } 50 | 51 | 52 | var ValidationsService = exports.ValidationsService = { 53 | getValidation: { 54 | path: '/validations.Validations/GetValidation', 55 | requestStream: false, 56 | responseStream: false, 57 | requestType: validations_pb.LedgerRequest, 58 | responseType: validations_pb.ValidationResponse, 59 | requestSerialize: serialize_validations_LedgerRequest, 60 | requestDeserialize: deserialize_validations_LedgerRequest, 61 | responseSerialize: serialize_validations_ValidationResponse, 62 | responseDeserialize: deserialize_validations_ValidationResponse, 63 | }, 64 | getValidationsByLedger: { 65 | path: '/validations.Validations/GetValidationsByLedger', 66 | requestStream: false, 67 | responseStream: true, 68 | requestType: validations_pb.LedgerRequest, 69 | responseType: validations_pb.ValidationResponse, 70 | requestSerialize: serialize_validations_LedgerRequest, 71 | requestDeserialize: deserialize_validations_LedgerRequest, 72 | responseSerialize: serialize_validations_ValidationResponse, 73 | responseDeserialize: deserialize_validations_ValidationResponse, 74 | }, 75 | getValidationsByLedgerRange: { 76 | path: '/validations.Validations/GetValidationsByLedgerRange', 77 | requestStream: false, 78 | responseStream: true, 79 | requestType: validations_pb.LedgerRangeRequest, 80 | responseType: validations_pb.ValidationResponse, 81 | requestSerialize: serialize_validations_LedgerRangeRequest, 82 | requestDeserialize: deserialize_validations_LedgerRangeRequest, 83 | responseSerialize: serialize_validations_ValidationResponse, 84 | responseDeserialize: deserialize_validations_ValidationResponse, 85 | }, 86 | getValidationsByMasterKey: { 87 | path: '/validations.Validations/GetValidationsByMasterKey', 88 | requestStream: false, 89 | responseStream: true, 90 | requestType: validations_pb.MasterKeyRequest, 91 | responseType: validations_pb.ValidationResponse, 92 | requestSerialize: serialize_validations_MasterKeyRequest, 93 | requestDeserialize: deserialize_validations_MasterKeyRequest, 94 | responseSerialize: serialize_validations_ValidationResponse, 95 | responseDeserialize: deserialize_validations_ValidationResponse, 96 | }, 97 | }; 98 | 99 | exports.ValidationsClient = grpc.makeGenericClientConstructor(ValidationsService); 100 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hermes server 8 | 9 | 10 | 72 | 73 | 74 |
75 |
76 | 77 | Hermes server 78 | 79 |
80 | 81 |
82 |

Hermes: XRPL Validation message service

83 |

Hermes is a layer 2 messaging network for storing and relaying XRPL 84 | validation messages. Hermes servers can be linked together to create a P2P network to spool 85 | validationReceived messages emitted by rippled. 86 |

87 | 88 |
89 | 90 |
91 |
92 |

Explore

93 |

These REST endpoints can provide Hermes server's status

94 | 99 |
100 | 101 |
102 |

Resources

103 |

Read more detailed instructions and blog post on Hermes.

104 | 108 |
109 |
110 |
111 |
112 | Hermes server · © 2023, The XRPScan Project 113 |
114 |
115 | 116 | -------------------------------------------------------------------------------- /src/lib/ENV.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import logger from 'npmlog' 3 | 4 | class ENV { 5 | public static get LOG_LEVEL(): string { 6 | if (process.env.LOG_LEVEL && Object.keys(logger.levels).includes(process.env.LOG_LEVEL)) { 7 | return process.env.LOG_LEVEL 8 | } else { 9 | return 'info' 10 | } 11 | } 12 | public static get VALIDATOR_SAMPLE_INTERVAL(): number { 13 | return process.env.VALIDATOR_SAMPLE_INTERVAL ? Number(process.env.VALIDATOR_SAMPLE_INTERVAL) : 256 14 | } 15 | public static get INGRESS_ENABLED(): boolean { 16 | return process.env.INGRESS_ENABLED === 'true' ? true : false 17 | } 18 | public static get PEERSYNC_ENABLED(): boolean { 19 | return process.env.PEERSYNC_ENABLED === 'true' ? true : false 20 | } 21 | public static get PEERSYNC_POLL_INTERVAL(): number { 22 | if (process.env.PEERSYNC_POLL_INTERVAL) { 23 | if (Number(process.env.PEERSYNC_POLL_INTERVAL) < 600) { 24 | return 600 25 | } else { 26 | return Number(process.env.PEERSYNC_POLL_INTERVAL) 27 | } 28 | } else { 29 | return 3600 30 | } 31 | } 32 | public static get PEERSYNC_POLL_INTERVAL_MS(): number { 33 | return this.PEERSYNC_POLL_INTERVAL * 1000 34 | } 35 | public static get PEERSYNC_FETCH_DEPTH(): number { 36 | return process.env.PEERSYNC_FETCH_DEPTH ? Number(process.env.PEERSYNC_FETCH_DEPTH) : 6000 37 | } 38 | public static get PEER_PRIVATE(): boolean { 39 | return process.env.PEER_PRIVATE === 'true' ? true : false 40 | } 41 | public static get SERVER_HOSTNAME(): string { 42 | return process.env.SERVER_HOSTNAME || 'localhost' 43 | } 44 | public static get SERVER_PRIVATE_KEY(): string { 45 | return process.env.SERVER_PRIVATE_KEY || 'config/private.pem' 46 | } 47 | public static get SERVER_CERTIFICATE(): string { 48 | return process.env.SERVER_CERTIFICATE || 'config/cert.pem' 49 | } 50 | public static get SERVER_REST_ENABLED(): boolean { 51 | return process.env.SERVER_REST_ENABLED === 'true' ? true : false 52 | } 53 | public static get SERVER_REST_PORT(): number { 54 | return process.env.SERVER_REST_PORT ? Number(process.env.SERVER_REST_PORT) : 50588 55 | } 56 | public static get SERVER_GRPC_ENABLED(): boolean { 57 | return process.env.SERVER_GRPC_ENABLED === 'true' ? true : false 58 | } 59 | public static get SERVER_GRPC_ADDRESS(): string { 60 | return process.env.SERVER_GRPC_ADDRESS || '127.0.0.1' 61 | } 62 | public static get SERVER_GRPC_PORT(): number { 63 | return process.env.SERVER_GRPC_PORT ? Number(process.env.SERVER_GRPC_PORT) : 50589 64 | } 65 | public static get EXPRESS_REQUEST_LOG(): string { 66 | return process.env.EXPRESS_REQUEST_LOG || 'log/express.log' 67 | } 68 | public static get EXPRESS_LOG_FORMAT(): string { 69 | return process.env.EXPRESS_LOG_FORMAT || ':remote-addr - [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms' 70 | } 71 | public static get MONGODB_HOST(): string { 72 | return process.env.MONGODB_HOST || 'xrpscan.example.com' 73 | } 74 | public static get MONGODB_PORT(): number { 75 | return process.env.MONGODB_PORT ? Number(process.env.MONGODB_PORT) : 27017 76 | } 77 | public static get MONGODB_DATABASE(): string { 78 | return process.env.MONGODB_DATABASE || 'hermes_prod' 79 | } 80 | public static get RIPPLED_URL(): string { 81 | return process.env.RIPPLED_URL || 'wss://xrplcluster.com' 82 | } 83 | public static get ONLINE_DELETE(): number { 84 | return process.env.ONLINE_DELETE ? Number(process.env.ONLINE_DELETE) : 0 85 | } 86 | public static get ONLINE_DELETE_INTERVAL(): number { 87 | return process.env.ONLINE_DELETE_INTERVAL ? Number(process.env.ONLINE_DELETE_INTERVAL) : 3600 * 24 88 | } 89 | public static get ONLINE_DELETE_INTERVAL_MS(): number { 90 | return this.ONLINE_DELETE_INTERVAL * 1000 91 | } 92 | public static get SERVER_CERTIFICATE_DAYS(): string { 93 | return process.env.SERVER_CERTIFICATE_DAYS || '3650' 94 | } 95 | public static get SERVER_CERTIFICATE_SUBJECT_C(): string { 96 | return process.env.SERVER_CERTIFICATE_SUBJECT_C || 'CH' 97 | } 98 | public static get SERVER_CERTIFICATE_SUBJECT_ST(): string { 99 | return process.env.SERVER_CERTIFICATE_SUBJECT_ST || 'Denial' 100 | } 101 | public static get SERVER_CERTIFICATE_SUBJECT_L(): string { 102 | return process.env.SERVER_CERTIFICATE_SUBJECT_L || 'Dislocated' 103 | } 104 | public static get SERVER_CERTIFICATE_SUBJECT_O(): string { 105 | return process.env.SERVER_CERTIFICATE_SUBJECT_O || 'Unorganized' 106 | } 107 | public static get SERVER_CERTIFICATE_SUBJECT_CN(): string { 108 | return process.env.SERVER_HOSTNAME || 'localhost' 109 | } 110 | 111 | } 112 | export default ENV 113 | -------------------------------------------------------------------------------- /src/protos/pb/validations_grpc_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: validations 2 | // file: validations.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as grpc from "@grpc/grpc-js"; 8 | import * as validations_pb from "./validations_pb"; 9 | 10 | interface IValidationsService extends grpc.ServiceDefinition { 11 | getValidation: IValidationsService_IGetValidation; 12 | getValidationsByLedger: IValidationsService_IGetValidationsByLedger; 13 | getValidationsByLedgerRange: IValidationsService_IGetValidationsByLedgerRange; 14 | getValidationsByMasterKey: IValidationsService_IGetValidationsByMasterKey; 15 | } 16 | 17 | interface IValidationsService_IGetValidation extends grpc.MethodDefinition { 18 | path: "/validations.Validations/GetValidation"; 19 | requestStream: false; 20 | responseStream: false; 21 | requestSerialize: grpc.serialize; 22 | requestDeserialize: grpc.deserialize; 23 | responseSerialize: grpc.serialize; 24 | responseDeserialize: grpc.deserialize; 25 | } 26 | interface IValidationsService_IGetValidationsByLedger extends grpc.MethodDefinition { 27 | path: "/validations.Validations/GetValidationsByLedger"; 28 | requestStream: false; 29 | responseStream: true; 30 | requestSerialize: grpc.serialize; 31 | requestDeserialize: grpc.deserialize; 32 | responseSerialize: grpc.serialize; 33 | responseDeserialize: grpc.deserialize; 34 | } 35 | interface IValidationsService_IGetValidationsByLedgerRange extends grpc.MethodDefinition { 36 | path: "/validations.Validations/GetValidationsByLedgerRange"; 37 | requestStream: false; 38 | responseStream: true; 39 | requestSerialize: grpc.serialize; 40 | requestDeserialize: grpc.deserialize; 41 | responseSerialize: grpc.serialize; 42 | responseDeserialize: grpc.deserialize; 43 | } 44 | interface IValidationsService_IGetValidationsByMasterKey extends grpc.MethodDefinition { 45 | path: "/validations.Validations/GetValidationsByMasterKey"; 46 | requestStream: false; 47 | responseStream: true; 48 | requestSerialize: grpc.serialize; 49 | requestDeserialize: grpc.deserialize; 50 | responseSerialize: grpc.serialize; 51 | responseDeserialize: grpc.deserialize; 52 | } 53 | 54 | export const ValidationsService: IValidationsService; 55 | 56 | export interface IValidationsServer extends grpc.UntypedServiceImplementation { 57 | getValidation: grpc.handleUnaryCall; 58 | getValidationsByLedger: grpc.handleServerStreamingCall; 59 | getValidationsByLedgerRange: grpc.handleServerStreamingCall; 60 | getValidationsByMasterKey: grpc.handleServerStreamingCall; 61 | } 62 | 63 | export interface IValidationsClient { 64 | getValidation(request: validations_pb.LedgerRequest, callback: (error: grpc.ServiceError | null, response: validations_pb.ValidationResponse) => void): grpc.ClientUnaryCall; 65 | getValidation(request: validations_pb.LedgerRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: validations_pb.ValidationResponse) => void): grpc.ClientUnaryCall; 66 | getValidation(request: validations_pb.LedgerRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: validations_pb.ValidationResponse) => void): grpc.ClientUnaryCall; 67 | getValidationsByLedger(request: validations_pb.LedgerRequest, options?: Partial): grpc.ClientReadableStream; 68 | getValidationsByLedger(request: validations_pb.LedgerRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; 69 | getValidationsByLedgerRange(request: validations_pb.LedgerRangeRequest, options?: Partial): grpc.ClientReadableStream; 70 | getValidationsByLedgerRange(request: validations_pb.LedgerRangeRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; 71 | getValidationsByMasterKey(request: validations_pb.MasterKeyRequest, options?: Partial): grpc.ClientReadableStream; 72 | getValidationsByMasterKey(request: validations_pb.MasterKeyRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; 73 | } 74 | 75 | export class ValidationsClient extends grpc.Client implements IValidationsClient { 76 | constructor(address: string, credentials: grpc.ChannelCredentials, options?: Partial); 77 | public getValidation(request: validations_pb.LedgerRequest, callback: (error: grpc.ServiceError | null, response: validations_pb.ValidationResponse) => void): grpc.ClientUnaryCall; 78 | public getValidation(request: validations_pb.LedgerRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: validations_pb.ValidationResponse) => void): grpc.ClientUnaryCall; 79 | public getValidation(request: validations_pb.LedgerRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: validations_pb.ValidationResponse) => void): grpc.ClientUnaryCall; 80 | public getValidationsByLedger(request: validations_pb.LedgerRequest, options?: Partial): grpc.ClientReadableStream; 81 | public getValidationsByLedger(request: validations_pb.LedgerRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; 82 | public getValidationsByLedgerRange(request: validations_pb.LedgerRangeRequest, options?: Partial): grpc.ClientReadableStream; 83 | public getValidationsByLedgerRange(request: validations_pb.LedgerRangeRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; 84 | public getValidationsByMasterKey(request: validations_pb.MasterKeyRequest, options?: Partial): grpc.ClientReadableStream; 85 | public getValidationsByMasterKey(request: validations_pb.MasterKeyRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; 86 | } 87 | -------------------------------------------------------------------------------- /src/protos/pb/validations_pb.d.ts: -------------------------------------------------------------------------------- 1 | // package: validations 2 | // file: validations.proto 3 | 4 | /* tslint:disable */ 5 | /* eslint-disable */ 6 | 7 | import * as jspb from "google-protobuf"; 8 | 9 | export class LedgerRequest extends jspb.Message { 10 | getLedgerIndex(): number; 11 | setLedgerIndex(value: number): LedgerRequest; 12 | 13 | hasRequestingNode(): boolean; 14 | clearRequestingNode(): void; 15 | getRequestingNode(): string | undefined; 16 | setRequestingNode(value: string): LedgerRequest; 17 | 18 | hasRequestingHost(): boolean; 19 | clearRequestingHost(): void; 20 | getRequestingHost(): string | undefined; 21 | setRequestingHost(value: string): LedgerRequest; 22 | 23 | serializeBinary(): Uint8Array; 24 | toObject(includeInstance?: boolean): LedgerRequest.AsObject; 25 | static toObject(includeInstance: boolean, msg: LedgerRequest): LedgerRequest.AsObject; 26 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 27 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 28 | static serializeBinaryToWriter(message: LedgerRequest, writer: jspb.BinaryWriter): void; 29 | static deserializeBinary(bytes: Uint8Array): LedgerRequest; 30 | static deserializeBinaryFromReader(message: LedgerRequest, reader: jspb.BinaryReader): LedgerRequest; 31 | } 32 | 33 | export namespace LedgerRequest { 34 | export type AsObject = { 35 | ledgerIndex: number, 36 | requestingNode?: string, 37 | requestingHost?: string, 38 | } 39 | } 40 | 41 | export class LedgerRangeRequest extends jspb.Message { 42 | getLedgerIndexMin(): number; 43 | setLedgerIndexMin(value: number): LedgerRangeRequest; 44 | getLedgerIndexMax(): number; 45 | setLedgerIndexMax(value: number): LedgerRangeRequest; 46 | 47 | hasRequestingNode(): boolean; 48 | clearRequestingNode(): void; 49 | getRequestingNode(): string | undefined; 50 | setRequestingNode(value: string): LedgerRangeRequest; 51 | 52 | hasRequestingHost(): boolean; 53 | clearRequestingHost(): void; 54 | getRequestingHost(): string | undefined; 55 | setRequestingHost(value: string): LedgerRangeRequest; 56 | 57 | serializeBinary(): Uint8Array; 58 | toObject(includeInstance?: boolean): LedgerRangeRequest.AsObject; 59 | static toObject(includeInstance: boolean, msg: LedgerRangeRequest): LedgerRangeRequest.AsObject; 60 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 61 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 62 | static serializeBinaryToWriter(message: LedgerRangeRequest, writer: jspb.BinaryWriter): void; 63 | static deserializeBinary(bytes: Uint8Array): LedgerRangeRequest; 64 | static deserializeBinaryFromReader(message: LedgerRangeRequest, reader: jspb.BinaryReader): LedgerRangeRequest; 65 | } 66 | 67 | export namespace LedgerRangeRequest { 68 | export type AsObject = { 69 | ledgerIndexMin: number, 70 | ledgerIndexMax: number, 71 | requestingNode?: string, 72 | requestingHost?: string, 73 | } 74 | } 75 | 76 | export class MasterKeyRequest extends jspb.Message { 77 | getMasterKey(): string; 78 | setMasterKey(value: string): MasterKeyRequest; 79 | 80 | hasRequestingNode(): boolean; 81 | clearRequestingNode(): void; 82 | getRequestingNode(): string | undefined; 83 | setRequestingNode(value: string): MasterKeyRequest; 84 | 85 | hasRequestingHost(): boolean; 86 | clearRequestingHost(): void; 87 | getRequestingHost(): string | undefined; 88 | setRequestingHost(value: string): MasterKeyRequest; 89 | 90 | serializeBinary(): Uint8Array; 91 | toObject(includeInstance?: boolean): MasterKeyRequest.AsObject; 92 | static toObject(includeInstance: boolean, msg: MasterKeyRequest): MasterKeyRequest.AsObject; 93 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 94 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 95 | static serializeBinaryToWriter(message: MasterKeyRequest, writer: jspb.BinaryWriter): void; 96 | static deserializeBinary(bytes: Uint8Array): MasterKeyRequest; 97 | static deserializeBinaryFromReader(message: MasterKeyRequest, reader: jspb.BinaryReader): MasterKeyRequest; 98 | } 99 | 100 | export namespace MasterKeyRequest { 101 | export type AsObject = { 102 | masterKey: string, 103 | requestingNode?: string, 104 | requestingHost?: string, 105 | } 106 | } 107 | 108 | export class ValidationResponse extends jspb.Message { 109 | getCookie(): string; 110 | setCookie(value: string): ValidationResponse; 111 | getType(): string; 112 | setType(value: string): ValidationResponse; 113 | getFlags(): number; 114 | setFlags(value: number): ValidationResponse; 115 | getFull(): boolean; 116 | setFull(value: boolean): ValidationResponse; 117 | getLedgerHash(): string; 118 | setLedgerHash(value: string): ValidationResponse; 119 | getLedgerIndex(): number; 120 | setLedgerIndex(value: number): ValidationResponse; 121 | getMasterKey(): string; 122 | setMasterKey(value: string): ValidationResponse; 123 | getSignature(): string; 124 | setSignature(value: string): ValidationResponse; 125 | getSigningTime(): number; 126 | setSigningTime(value: number): ValidationResponse; 127 | getValidatedHash(): string; 128 | setValidatedHash(value: string): ValidationResponse; 129 | getValidationPublicKey(): string; 130 | setValidationPublicKey(value: string): ValidationResponse; 131 | getData(): string; 132 | setData(value: string): ValidationResponse; 133 | getServerVersion(): string; 134 | setServerVersion(value: string): ValidationResponse; 135 | getBaseFee(): number; 136 | setBaseFee(value: number): ValidationResponse; 137 | getLoadFee(): number; 138 | setLoadFee(value: number): ValidationResponse; 139 | getReserveBase(): number; 140 | setReserveBase(value: number): ValidationResponse; 141 | getReserveInc(): number; 142 | setReserveInc(value: number): ValidationResponse; 143 | 144 | serializeBinary(): Uint8Array; 145 | toObject(includeInstance?: boolean): ValidationResponse.AsObject; 146 | static toObject(includeInstance: boolean, msg: ValidationResponse): ValidationResponse.AsObject; 147 | static extensions: {[key: number]: jspb.ExtensionFieldInfo}; 148 | static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; 149 | static serializeBinaryToWriter(message: ValidationResponse, writer: jspb.BinaryWriter): void; 150 | static deserializeBinary(bytes: Uint8Array): ValidationResponse; 151 | static deserializeBinaryFromReader(message: ValidationResponse, reader: jspb.BinaryReader): ValidationResponse; 152 | } 153 | 154 | export namespace ValidationResponse { 155 | export type AsObject = { 156 | cookie: string, 157 | type: string, 158 | flags: number, 159 | full: boolean, 160 | ledgerHash: string, 161 | ledgerIndex: number, 162 | masterKey: string, 163 | signature: string, 164 | signingTime: number, 165 | validatedHash: string, 166 | validationPublicKey: string, 167 | data: string, 168 | serverVersion: string, 169 | baseFee: number, 170 | loadFee: number, 171 | reserveBase: number, 172 | reserveInc: number, 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Hermes: XRP Ledger Validation message service 2 | 3 | Hermes is a layer 2 messaging network for storing and relaying XRPL validation messages. Hermes servers can be linked together to create a P2P network to spool validation messages emitted by rippled's `validationReceived` event. 4 | 5 | ### What is Hermes? 6 | Validation messages are ephemeral, therefore they must be saved as soon as they're emitted. If a service processing XRPL Validation messages needs to be momentarily offline (due to an outage, service restart or upgrade), it can fetch missing Validation messages from the Hermes network. 7 | 8 | Hermes server offers data access via REST and gRPC endpoints. See examples below on how to access it. 9 | 10 | ### Requirements 11 | 12 | 1. Linux server with OpenSSL binaries. 13 | 2. Access to a MongoDB server. Remote OK, but local is faster. 14 | 3. Access to a rippled node. 15 | 4. Node.js runtime. 16 | 5. `pm2` process manager *(optional)* 17 | 18 | ### Installation 19 | 20 | 1. Hermes can run on Linux distributions. We've tested it on Fedora and CentOS. To serve client requests, Hermes uses ports 50588 (REST) and 50589 (gRPC). These configurable ports must be opened on the firewall. 21 | 22 | 2. Hermes can use existing private-key and TLS certificates. To generate a new TLS certificate via Hermes, ensure `openssl` is installed. To verify, run: 23 | 24 | ``` 25 | $ openssl version 26 | OpenSSL 1.1.1k FIPS 25 Mar 2021 27 | ``` 28 | 3. MongoDB is used to persist validation messages. A remote MongoDB server may be used, although performance may vary. To set up a local MongoDB server, follow the [MongoDB installation manual](https://www.mongodb.com/docs/manual/installation/). 29 | 30 | 4. Validation messages are sourced from a rippled node. Depending on available resources, a remote or local rippled server may be used. To set up a local rippled server, follow the [rippled installation guide](https://xrpl.org/install-rippled.html). 31 | 32 | 5. Hermes is tested on Node.js 18.7. [Download Node.js](https://nodejs.org/en/) and set up `PATH`. 33 | 34 | 6. Download the Hermes server and create a configuration file. 35 | 36 | ``` 37 | $ git clone https://github.com/xrpscan/hermes.git 38 | $ cd hermes 39 | $ npm install 40 | $ npm run build 41 | $ cp .env.example .env 42 | ``` 43 | 44 | 7. Review server configuration in `.env` file. Use a valid and resolvable value for `SERVER_HOSTNAME`. This value would be used to generate TLS certificates, and Peers would connect to this URL via the auto-config mechanism. 45 | 46 | ``` 47 | SERVER_HOSTNAME = 'hermes.example.com' 48 | ``` 49 | 50 | 8. If a new TLS certificate is needed, run: 51 | 52 | ``` 53 | $ npm run keypair generate 54 | ``` 55 | 56 | 9. Start Hermes server 57 | 58 | ``` 59 | $ npm start 60 | > hermes@0.1.0 start 61 | > node dist/index.js 62 | 63 | [Hermes] info [REST] Secure service started on https://hermes.example.com:50588 64 | [Hermes] info [gRPC] Secure service started on hermes.example.com:50589 65 | [Hermes] info [xrpl] Connected: ws://localhost:6006 66 | [Hermes] info [ingress] Ingressing validation messages from ws://localhost:6006 67 | [Hermes] info [mongod] Connected: mongodb://localhost:27017/hermes_prod 68 | ``` 69 | 70 | 10. *Optional* - Run with the pm2 process manager 71 | 72 | Install pm2 73 | 74 | ``` 75 | # npm install -g pm2 76 | ``` 77 | 78 | Run the provided pm2 start script 79 | ``` 80 | $ ./bin/start.sh 81 | ``` 82 | 83 | ### Upgrading (from source) 84 | 85 | 1. Hermes can be upgraded from source. To be on the safer side, please backup your SSL keys (default location is `config/private.pem`, `config/cert.pem`) and `.env` config file. 86 | 87 | ``` 88 | $ cd hermes 89 | $ git fetch --all 90 | $ git rebase origin/main 91 | $ npm run build 92 | ``` 93 | 94 | 2. If required, restore SSL keys and `.env` file. 95 | 96 | 3. Start upgraded Hermes server 97 | 98 | ``` 99 | $ npm start 100 | ``` 101 | 102 | ### Creating a P2P network 103 | 104 | Hermes servers can be linked together to create a layer 2 messaging network. To add `vms.test.xrpscan.com:50588` as a peer, run: 105 | 106 | ``` 107 | $ npm run peer add vms.test.xrpscan.com:50588 108 | ``` 109 | 110 | This will initiate a ping handshake and add the node as a trusted peer. Optionally, others can add your node's URL to establish 2-way messaging. 111 | 112 | To view a list of trusted peers, run: 113 | 114 | ``` 115 | $ npm run peer ls 116 | ``` 117 | 118 | To remove a peer, run: 119 | 120 | ``` 121 | $ npm run peer remove 122 | ``` 123 | 124 | ### Maintenance 125 | 126 | XRP Ledger validators generate millions of validation messages every day. The online deletion feature lets the service delete validation messages from older ledgers to keep disk usage from rapidly growing over time. The default config lets the service store all validation messages indefinitely. In order to enable online deletion, set `ONLINE_DELETE` setting in `.env` file to the number of latest validated ledgers you wish to keep. 127 | 128 | ![ONLINE_DELETE](https://github.com/xrpscan/hermes/blob/main/assets/online-delete.png?raw=true) 129 | 130 | ``` 131 | ONLINE_DELETE = 250000 # Store validations from the last 10 days approx 132 | ``` 133 | or 134 | ``` 135 | ONLINE_DELETE = 660000 # Store validations from the last 1 month approx 136 | ``` 137 | 138 | If needed, MongoDB collection may be compacted to free up disk space and return it back to the OS. 139 | 140 | ``` 141 | $ npm run online-delete compact 142 | [online-delete] Compact validations collection: 344 MB freed 143 | ``` 144 | 145 | ### Production notes 146 | 147 | These settings are recommended while running Hermes in production environment. 148 | 149 | 1. Use XFS filesystem 150 | 151 | MongoDB strongly recommends using XFS filesystem for its data directory `storage.dbPath`. [Read more →](https://www.mongodb.com/docs/manual/administration/production-notes/#kernel-and-file-systems) 152 | 153 | 2. Disable Transparent Huge Pages 154 | 155 | Database workloads often perform poorly with `transparent_hugepage` enabled, because they tend to have sparse rather than contiguous memory access patterns. When running MongoDB on Linux, `transparent_hugepage` should be disabled for best performance. [Read more →](https://www.mongodb.com/docs/manual/tutorial/transparent-huge-pages/) 156 | 157 | 3. Limit MongoDB memory usage 158 | 159 | With default configuration, MongoDB will use upto 50% of host's memory. To accommodate additional services that need RAM, you may have to decrease WiredTiger internal cache size. [Read more →](https://www.mongodb.com/docs/manual/faq/diagnostics/#memory-diagnostics-for-the-wiredtiger-storage-engine) 160 | 161 | ``` 162 | storage: 163 | dbPath: /var/lib/mongodb 164 | ... 165 | wiredTiger: 166 | engineConfig: 167 | cacheSizeGB: 4 168 | ``` 169 | 170 | 4. Using PM2 & Auto start Hermes on boot 171 | 172 | In production environment, running Hermes with `pm2` process manager is recommended. An example pm2 startup script is available at `bin/start.sh`. It is possible to auto start pm2 on system boot-up. After Hermes is up and running with pm2, run: 173 | 174 | ``` 175 | pm2 save 176 | pm2 startup 177 | ``` 178 | And follow instructions printed by pm2. [Read more →](https://pm2.keymetrics.io/docs/usage/startup/) 179 | 180 | ### Architecture 181 | 182 | ![Hermes architecture](https://github.com/xrpscan/hermes/blob/main/assets/hermes-architecture-v2.png?raw=true) 183 | 184 | ### Known issues 185 | 186 | * [yarn peer [add|ls|remove] commands don't return shell #6](https://github.com/xrpscan/hermes/issues/6) 187 | * [gRPC connection terminated due to JavaScript heap out of memory #8](https://github.com/xrpscan/hermes/issues/7) 188 | 189 | ### Also see 190 | 191 | * [Consensus - xrpl.org](https://xrpl.org/consensus.html) 192 | * [Validations Stream - xrpl.org](https://xrpl.org/subscribe.html#validations-stream) 193 | 194 | ### Reporting bugs 195 | 196 | Please create a new issue in [Hermes issue tracker](https://github.com/xrpscan/hermes/issues/new) 197 | 198 | ### EOF 199 | -------------------------------------------------------------------------------- /src/lib/VerifyValidation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * VerifyValidation.js 3 | * 4 | * Based on XRPL Validation collector by Richard Holland 5 | * https://github.com/RichardAH/xrpl-validation-collector 6 | * 7 | * Verifies validation message signature. 8 | **/ 9 | const elliptic = require('elliptic') 10 | const secp256k1 = new elliptic.ec('secp256k1') 11 | const ed25519 = new elliptic.eddsa('ed25519') 12 | const crypto = require('node:crypto') 13 | 14 | export const verify_validation = (public_key, val) => 15 | { 16 | if (typeof(val) == 'string') 17 | val = Buffer.from(val, 'hex') 18 | else if (typeof(val) == 'object' && val.data !== undefined) 19 | val = val.data 20 | 21 | const fail = (msg) => 22 | { 23 | throw new Error(`Validation Parse Error: ${msg}`) 24 | } 25 | 26 | const parse_uint32 = (val, upto) => 27 | { 28 | return (BigInt(val[upto ]) << 24n) + 29 | (BigInt(val[upto + 1]) << 16n) + 30 | (BigInt(val[upto + 2]) << 8n) + 31 | (BigInt(val[upto + 3])) + "" 32 | } 33 | 34 | const parse_uint64 = (val, upto) => 35 | { 36 | return (BigInt(val[upto ]) << 56n) + 37 | (BigInt(val[upto + 1]) << 48n) + 38 | (BigInt(val[upto + 2]) << 40n) + 39 | (BigInt(val[upto + 3]) << 32n) + 40 | (BigInt(val[upto + 4]) << 24n) + 41 | (BigInt(val[upto + 5]) << 16n) + 42 | (BigInt(val[upto + 6]) << 8n) + 43 | (BigInt(val[upto + 7])) + "" 44 | } 45 | 46 | // remaining bytes 47 | const rem = ((len)=> 48 | { 49 | return (upto)=>{return len-upto} 50 | })(val.length) 51 | 52 | let upto = 0 53 | let json = {} 54 | 55 | // Flags 56 | if (val[upto++] != 0x22 || rem(upto) < 5) 57 | return fail('sfFlags missing or incomplete') 58 | json['Flags'] = parse_uint32(val, upto) 59 | upto += 4 60 | 61 | // LedgerSequence 62 | if (val[upto++] != 0x26 || rem(upto) < 5) 63 | return fail('sfLedgerSequnece missing or incomplete') 64 | json['LedgerSequence'] = parse_uint32(val, upto) 65 | upto += 4 66 | 67 | // CloseTime (optional) 68 | if (val[upto] == 0x27) 69 | { 70 | upto++ 71 | if (rem(upto) < 4) 72 | return fail('sfCloseTime payload missing') 73 | json['CloseTime'] = parse_uint32(val, upto) 74 | upto += 4 75 | } 76 | 77 | // SigningTime 78 | if (val[upto++] != 0x29 || rem(upto) < 5) 79 | return fail('sfSigningTime missing or incomplete') 80 | json['SigningTime'] = parse_uint32(val, upto) 81 | upto += 4 82 | 83 | // LoadFee (optional) 84 | if (val[upto] == 0x20 && rem(upto) >= 1 && val[upto + 1] == 0x18) 85 | { 86 | upto += 2 87 | if (rem(upto) < 4) 88 | return fail('sfLoadFee payload missing') 89 | json['LoadFee'] = parse_uint32(val, upto) 90 | upto += 4 91 | } 92 | 93 | // ReserveBase (optional) 94 | if (val[upto] == 0x20 && rem(upto) >= 1 && val[upto + 1] == 0x1F) 95 | { 96 | upto += 2 97 | if (rem(upto) < 4) 98 | return fail('sfReserveBase payload missing') 99 | json['ReserveBase'] = parse_uint32(val, upto) 100 | upto += 4 101 | } 102 | 103 | // ReserveIncrement (optional) 104 | if (val[upto] == 0x20 && rem(upto) >= 1 && val[upto + 1] == 0x20) 105 | { 106 | upto += 2 107 | if (rem(upto) < 4) 108 | return fail('sfReserveIncrement payload missing') 109 | json['ReserveIncrement'] = parse_uint32(val, upto) 110 | upto += 4 111 | } 112 | 113 | // BaseFee (optional) 114 | if (val[upto] == 0x35) 115 | { 116 | upto++ 117 | if (rem(upto) < 8) 118 | return fail('sfBaseFee payload missing') 119 | json['BaseFee'] = parse_uint64(val, upto) 120 | upto += 8 121 | } 122 | 123 | // Cookie (optional) 124 | if (val[upto] == 0x3A) 125 | { 126 | upto++ 127 | if (rem(upto) < 8) 128 | return fail('sfCookie payload missing') 129 | json['Cookie'] = parse_uint64(val, upto) 130 | upto += 8 131 | } 132 | 133 | // ServerVersion (optional) 134 | if (val[upto] == 0x3B) 135 | { 136 | upto++ 137 | if (rem(upto) < 8) 138 | return fail('sfServerVersion payload missing') 139 | json['ServerVersion'] = parse_uint64(val, upto) 140 | upto += 8 141 | } 142 | 143 | // LedgerHash 144 | if (val[upto++] != 0x51 || rem(upto) < 5) 145 | return fail('sfLedgerHash missing or incomplete') 146 | json['LedgerHash'] = 147 | val.slice(upto, upto + 32).toString('hex').toUpperCase() 148 | upto += 32 149 | 150 | // ConsensusHash 151 | if (val[upto] == 0x50 && rem(upto) >= 1 && val[upto + 1] == 0x17) 152 | { 153 | upto += 2 154 | if (rem(upto) < 32) 155 | return fail('sfConsensusHash payload missing') 156 | json['ConsensusHash'] = 157 | val.slice(upto, upto + 32).toString('hex').toUpperCase() 158 | upto += 32 159 | } 160 | 161 | // ValidatedHash 162 | if (val[upto] == 0x50 && rem(upto) >= 1 && val[upto + 1] == 0x19) 163 | { 164 | upto += 2 165 | if (rem(upto) < 32) 166 | return fail('sfValidatedHash payload missing') 167 | json['ValidatedHash'] = 168 | val.slice(upto, upto + 32).toString('hex').toUpperCase() 169 | upto += 32 170 | } 171 | 172 | // SigningPubKey 173 | if (val[upto++] != 0x73 || rem(upto) < 2) 174 | return fail('sfSigningPubKey missing') 175 | let key_size = val[upto++] 176 | if (rem(upto) < key_size) 177 | return fail('sfSigningPubKey payload missing') 178 | json['SigningPubKey'] = 179 | val.slice(upto, upto + key_size).toString('hex').toUpperCase() 180 | upto += key_size 181 | 182 | 183 | // Signature 184 | let sig_start = upto 185 | if (val[upto++] != 0x76 || rem(upto) < 2) 186 | return fail('sfSignature missing') 187 | let sig_size = val[upto++] 188 | if (rem(upto) < sig_size) 189 | return fail('sfSignature missing') 190 | json['Signature'] = 191 | val.slice(upto, upto + sig_size).toString('hex').toUpperCase() 192 | upto += sig_size 193 | let sig_end = upto 194 | 195 | // Amendments (optional) 196 | if (rem(upto) >= 1 && val[upto] == 0x03 && val[upto + 1] == 0x13) 197 | { 198 | upto += 2 199 | // parse variable length 200 | if (rem(upto) < 1) 201 | return fail('sfAmendments payload missing or incomplete [1]') 202 | let len = val[upto++] 203 | if (len <= 192) 204 | { 205 | // do nothing 206 | } 207 | else if (len >= 193 && len <= 240) 208 | { 209 | if (rem(upto) < 1) 210 | return fail('sfAmendments payload missing or incomplete [2]') 211 | len = 193 + ((len - 193) * 256) + val[upto++] 212 | } 213 | else if (len >= 241 && len <= 254) 214 | { 215 | if (rem(upto) < 2) 216 | return fail('sfAmendments payload missing or incomplete [2]') 217 | 218 | len = 219 | 12481 + ((len - 241) * 65536) + (val[upto + 1] * 256) + val[upto + 2] 220 | upto += 2 221 | } 222 | 223 | if (rem(upto) < len) 224 | return fail('sfAmendments payload missing or incomplete [3]') 225 | 226 | json['Amendments'] = [] 227 | 228 | let end = upto + len 229 | while (upto < end) 230 | { 231 | json['Amendments'].push(val.slice(upto, upto + 32).toString('hex')) 232 | upto += 32 233 | } 234 | } 235 | 236 | // Check public key 237 | if (public_key.toUpperCase() != 238 | json['SigningPubKey'].toString('hex').toUpperCase()) 239 | { 240 | json['_verified'] = false 241 | json['_verification_error'] = 242 | 'SigningPubKey did not match or was not present' 243 | return json 244 | } 245 | 246 | // Check signature 247 | const computed_hash = 248 | crypto.createHash('sha512').update( 249 | Buffer.concat( 250 | [ Buffer.from('VAL\x00', 'utf-8'), 251 | val.slice(0, sig_start), 252 | val.slice(sig_end, val.length)]) 253 | ).digest().toString('hex').slice(0,64) 254 | 255 | 256 | const verify_key = 257 | (public_key.slice(2) == 'ED' 258 | ? ed25519.keyFromPublic(public_key.slice(2), 'hex') 259 | : secp256k1.keyFromPublic(public_key, 'hex')) 260 | 261 | if (!verify_key.verify( 262 | computed_hash, json['Signature'])) 263 | { 264 | json['_verified'] = false 265 | json['_verification_error'] = 266 | 'Signature (ed25519) did not match or was not present' 267 | return json 268 | } 269 | 270 | json['_verified'] = true 271 | return json 272 | 273 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/protos/pb/ping_pb.js: -------------------------------------------------------------------------------- 1 | // source: ping.proto 2 | /** 3 | * @fileoverview 4 | * @enhanceable 5 | * @suppress {missingRequire} reports error on implicit type usages. 6 | * @suppress {messageConventions} JS Compiler reports an error if a variable or 7 | * field starts with 'MSG_' and isn't a translatable message. 8 | * @public 9 | */ 10 | // GENERATED CODE -- DO NOT EDIT! 11 | /* eslint-disable */ 12 | // @ts-nocheck 13 | 14 | var jspb = require('google-protobuf'); 15 | var goog = jspb; 16 | var global = Function('return this')(); 17 | 18 | goog.exportSymbol('proto.ping.PingRequest', null, global); 19 | goog.exportSymbol('proto.ping.PongResponse', null, global); 20 | /** 21 | * Generated by JsPbCodeGenerator. 22 | * @param {Array=} opt_data Optional initial data array, typically from a 23 | * server response, or constructed directly in Javascript. The array is used 24 | * in place and becomes part of the constructed object. It is not cloned. 25 | * If no data is provided, the constructed object will be empty, but still 26 | * valid. 27 | * @extends {jspb.Message} 28 | * @constructor 29 | */ 30 | proto.ping.PingRequest = function(opt_data) { 31 | jspb.Message.initialize(this, opt_data, 0, -1, null, null); 32 | }; 33 | goog.inherits(proto.ping.PingRequest, jspb.Message); 34 | if (goog.DEBUG && !COMPILED) { 35 | /** 36 | * @public 37 | * @override 38 | */ 39 | proto.ping.PingRequest.displayName = 'proto.ping.PingRequest'; 40 | } 41 | /** 42 | * Generated by JsPbCodeGenerator. 43 | * @param {Array=} opt_data Optional initial data array, typically from a 44 | * server response, or constructed directly in Javascript. The array is used 45 | * in place and becomes part of the constructed object. It is not cloned. 46 | * If no data is provided, the constructed object will be empty, but still 47 | * valid. 48 | * @extends {jspb.Message} 49 | * @constructor 50 | */ 51 | proto.ping.PongResponse = function(opt_data) { 52 | jspb.Message.initialize(this, opt_data, 0, -1, null, null); 53 | }; 54 | goog.inherits(proto.ping.PongResponse, jspb.Message); 55 | if (goog.DEBUG && !COMPILED) { 56 | /** 57 | * @public 58 | * @override 59 | */ 60 | proto.ping.PongResponse.displayName = 'proto.ping.PongResponse'; 61 | } 62 | 63 | 64 | 65 | if (jspb.Message.GENERATE_TO_OBJECT) { 66 | /** 67 | * Creates an object representation of this proto. 68 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 69 | * Optional fields that are not set will be set to undefined. 70 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 71 | * For the list of reserved names please see: 72 | * net/proto2/compiler/js/internal/generator.cc#kKeyword. 73 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the 74 | * JSPB instance for transitional soy proto support: 75 | * http://goto/soy-param-migration 76 | * @return {!Object} 77 | */ 78 | proto.ping.PingRequest.prototype.toObject = function(opt_includeInstance) { 79 | return proto.ping.PingRequest.toObject(opt_includeInstance, this); 80 | }; 81 | 82 | 83 | /** 84 | * Static version of the {@see toObject} method. 85 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include 86 | * the JSPB instance for transitional soy proto support: 87 | * http://goto/soy-param-migration 88 | * @param {!proto.ping.PingRequest} msg The msg instance to transform. 89 | * @return {!Object} 90 | * @suppress {unusedLocalVariables} f is only used for nested messages 91 | */ 92 | proto.ping.PingRequest.toObject = function(includeInstance, msg) { 93 | var f, obj = { 94 | message: jspb.Message.getFieldWithDefault(msg, 1, ""), 95 | requestingNode: jspb.Message.getFieldWithDefault(msg, 14, ""), 96 | requestingHost: jspb.Message.getFieldWithDefault(msg, 15, "") 97 | }; 98 | 99 | if (includeInstance) { 100 | obj.$jspbMessageInstance = msg; 101 | } 102 | return obj; 103 | }; 104 | } 105 | 106 | 107 | /** 108 | * Deserializes binary data (in protobuf wire format). 109 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 110 | * @return {!proto.ping.PingRequest} 111 | */ 112 | proto.ping.PingRequest.deserializeBinary = function(bytes) { 113 | var reader = new jspb.BinaryReader(bytes); 114 | var msg = new proto.ping.PingRequest; 115 | return proto.ping.PingRequest.deserializeBinaryFromReader(msg, reader); 116 | }; 117 | 118 | 119 | /** 120 | * Deserializes binary data (in protobuf wire format) from the 121 | * given reader into the given message object. 122 | * @param {!proto.ping.PingRequest} msg The message object to deserialize into. 123 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 124 | * @return {!proto.ping.PingRequest} 125 | */ 126 | proto.ping.PingRequest.deserializeBinaryFromReader = function(msg, reader) { 127 | while (reader.nextField()) { 128 | if (reader.isEndGroup()) { 129 | break; 130 | } 131 | var field = reader.getFieldNumber(); 132 | switch (field) { 133 | case 1: 134 | var value = /** @type {string} */ (reader.readString()); 135 | msg.setMessage(value); 136 | break; 137 | case 14: 138 | var value = /** @type {string} */ (reader.readString()); 139 | msg.setRequestingNode(value); 140 | break; 141 | case 15: 142 | var value = /** @type {string} */ (reader.readString()); 143 | msg.setRequestingHost(value); 144 | break; 145 | default: 146 | reader.skipField(); 147 | break; 148 | } 149 | } 150 | return msg; 151 | }; 152 | 153 | 154 | /** 155 | * Serializes the message to binary data (in protobuf wire format). 156 | * @return {!Uint8Array} 157 | */ 158 | proto.ping.PingRequest.prototype.serializeBinary = function() { 159 | var writer = new jspb.BinaryWriter(); 160 | proto.ping.PingRequest.serializeBinaryToWriter(this, writer); 161 | return writer.getResultBuffer(); 162 | }; 163 | 164 | 165 | /** 166 | * Serializes the given message to binary data (in protobuf wire 167 | * format), writing to the given BinaryWriter. 168 | * @param {!proto.ping.PingRequest} message 169 | * @param {!jspb.BinaryWriter} writer 170 | * @suppress {unusedLocalVariables} f is only used for nested messages 171 | */ 172 | proto.ping.PingRequest.serializeBinaryToWriter = function(message, writer) { 173 | var f = undefined; 174 | f = message.getMessage(); 175 | if (f.length > 0) { 176 | writer.writeString( 177 | 1, 178 | f 179 | ); 180 | } 181 | f = /** @type {string} */ (jspb.Message.getField(message, 14)); 182 | if (f != null) { 183 | writer.writeString( 184 | 14, 185 | f 186 | ); 187 | } 188 | f = /** @type {string} */ (jspb.Message.getField(message, 15)); 189 | if (f != null) { 190 | writer.writeString( 191 | 15, 192 | f 193 | ); 194 | } 195 | }; 196 | 197 | 198 | /** 199 | * optional string message = 1; 200 | * @return {string} 201 | */ 202 | proto.ping.PingRequest.prototype.getMessage = function() { 203 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); 204 | }; 205 | 206 | 207 | /** 208 | * @param {string} value 209 | * @return {!proto.ping.PingRequest} returns this 210 | */ 211 | proto.ping.PingRequest.prototype.setMessage = function(value) { 212 | return jspb.Message.setProto3StringField(this, 1, value); 213 | }; 214 | 215 | 216 | /** 217 | * optional string requesting_node = 14; 218 | * @return {string} 219 | */ 220 | proto.ping.PingRequest.prototype.getRequestingNode = function() { 221 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 14, "")); 222 | }; 223 | 224 | 225 | /** 226 | * @param {string} value 227 | * @return {!proto.ping.PingRequest} returns this 228 | */ 229 | proto.ping.PingRequest.prototype.setRequestingNode = function(value) { 230 | return jspb.Message.setField(this, 14, value); 231 | }; 232 | 233 | 234 | /** 235 | * Clears the field making it undefined. 236 | * @return {!proto.ping.PingRequest} returns this 237 | */ 238 | proto.ping.PingRequest.prototype.clearRequestingNode = function() { 239 | return jspb.Message.setField(this, 14, undefined); 240 | }; 241 | 242 | 243 | /** 244 | * Returns whether this field is set. 245 | * @return {boolean} 246 | */ 247 | proto.ping.PingRequest.prototype.hasRequestingNode = function() { 248 | return jspb.Message.getField(this, 14) != null; 249 | }; 250 | 251 | 252 | /** 253 | * optional string requesting_host = 15; 254 | * @return {string} 255 | */ 256 | proto.ping.PingRequest.prototype.getRequestingHost = function() { 257 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 15, "")); 258 | }; 259 | 260 | 261 | /** 262 | * @param {string} value 263 | * @return {!proto.ping.PingRequest} returns this 264 | */ 265 | proto.ping.PingRequest.prototype.setRequestingHost = function(value) { 266 | return jspb.Message.setField(this, 15, value); 267 | }; 268 | 269 | 270 | /** 271 | * Clears the field making it undefined. 272 | * @return {!proto.ping.PingRequest} returns this 273 | */ 274 | proto.ping.PingRequest.prototype.clearRequestingHost = function() { 275 | return jspb.Message.setField(this, 15, undefined); 276 | }; 277 | 278 | 279 | /** 280 | * Returns whether this field is set. 281 | * @return {boolean} 282 | */ 283 | proto.ping.PingRequest.prototype.hasRequestingHost = function() { 284 | return jspb.Message.getField(this, 15) != null; 285 | }; 286 | 287 | 288 | 289 | 290 | 291 | if (jspb.Message.GENERATE_TO_OBJECT) { 292 | /** 293 | * Creates an object representation of this proto. 294 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 295 | * Optional fields that are not set will be set to undefined. 296 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 297 | * For the list of reserved names please see: 298 | * net/proto2/compiler/js/internal/generator.cc#kKeyword. 299 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the 300 | * JSPB instance for transitional soy proto support: 301 | * http://goto/soy-param-migration 302 | * @return {!Object} 303 | */ 304 | proto.ping.PongResponse.prototype.toObject = function(opt_includeInstance) { 305 | return proto.ping.PongResponse.toObject(opt_includeInstance, this); 306 | }; 307 | 308 | 309 | /** 310 | * Static version of the {@see toObject} method. 311 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include 312 | * the JSPB instance for transitional soy proto support: 313 | * http://goto/soy-param-migration 314 | * @param {!proto.ping.PongResponse} msg The msg instance to transform. 315 | * @return {!Object} 316 | * @suppress {unusedLocalVariables} f is only used for nested messages 317 | */ 318 | proto.ping.PongResponse.toObject = function(includeInstance, msg) { 319 | var f, obj = { 320 | message: jspb.Message.getFieldWithDefault(msg, 1, ""), 321 | signature: jspb.Message.getFieldWithDefault(msg, 2, "") 322 | }; 323 | 324 | if (includeInstance) { 325 | obj.$jspbMessageInstance = msg; 326 | } 327 | return obj; 328 | }; 329 | } 330 | 331 | 332 | /** 333 | * Deserializes binary data (in protobuf wire format). 334 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 335 | * @return {!proto.ping.PongResponse} 336 | */ 337 | proto.ping.PongResponse.deserializeBinary = function(bytes) { 338 | var reader = new jspb.BinaryReader(bytes); 339 | var msg = new proto.ping.PongResponse; 340 | return proto.ping.PongResponse.deserializeBinaryFromReader(msg, reader); 341 | }; 342 | 343 | 344 | /** 345 | * Deserializes binary data (in protobuf wire format) from the 346 | * given reader into the given message object. 347 | * @param {!proto.ping.PongResponse} msg The message object to deserialize into. 348 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 349 | * @return {!proto.ping.PongResponse} 350 | */ 351 | proto.ping.PongResponse.deserializeBinaryFromReader = function(msg, reader) { 352 | while (reader.nextField()) { 353 | if (reader.isEndGroup()) { 354 | break; 355 | } 356 | var field = reader.getFieldNumber(); 357 | switch (field) { 358 | case 1: 359 | var value = /** @type {string} */ (reader.readString()); 360 | msg.setMessage(value); 361 | break; 362 | case 2: 363 | var value = /** @type {string} */ (reader.readString()); 364 | msg.setSignature(value); 365 | break; 366 | default: 367 | reader.skipField(); 368 | break; 369 | } 370 | } 371 | return msg; 372 | }; 373 | 374 | 375 | /** 376 | * Serializes the message to binary data (in protobuf wire format). 377 | * @return {!Uint8Array} 378 | */ 379 | proto.ping.PongResponse.prototype.serializeBinary = function() { 380 | var writer = new jspb.BinaryWriter(); 381 | proto.ping.PongResponse.serializeBinaryToWriter(this, writer); 382 | return writer.getResultBuffer(); 383 | }; 384 | 385 | 386 | /** 387 | * Serializes the given message to binary data (in protobuf wire 388 | * format), writing to the given BinaryWriter. 389 | * @param {!proto.ping.PongResponse} message 390 | * @param {!jspb.BinaryWriter} writer 391 | * @suppress {unusedLocalVariables} f is only used for nested messages 392 | */ 393 | proto.ping.PongResponse.serializeBinaryToWriter = function(message, writer) { 394 | var f = undefined; 395 | f = message.getMessage(); 396 | if (f.length > 0) { 397 | writer.writeString( 398 | 1, 399 | f 400 | ); 401 | } 402 | f = message.getSignature(); 403 | if (f.length > 0) { 404 | writer.writeString( 405 | 2, 406 | f 407 | ); 408 | } 409 | }; 410 | 411 | 412 | /** 413 | * optional string message = 1; 414 | * @return {string} 415 | */ 416 | proto.ping.PongResponse.prototype.getMessage = function() { 417 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); 418 | }; 419 | 420 | 421 | /** 422 | * @param {string} value 423 | * @return {!proto.ping.PongResponse} returns this 424 | */ 425 | proto.ping.PongResponse.prototype.setMessage = function(value) { 426 | return jspb.Message.setProto3StringField(this, 1, value); 427 | }; 428 | 429 | 430 | /** 431 | * optional string signature = 2; 432 | * @return {string} 433 | */ 434 | proto.ping.PongResponse.prototype.getSignature = function() { 435 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); 436 | }; 437 | 438 | 439 | /** 440 | * @param {string} value 441 | * @return {!proto.ping.PongResponse} returns this 442 | */ 443 | proto.ping.PongResponse.prototype.setSignature = function(value) { 444 | return jspb.Message.setProto3StringField(this, 2, value); 445 | }; 446 | 447 | 448 | goog.object.extend(exports, proto.ping); 449 | -------------------------------------------------------------------------------- /src/protos/pb/validations_pb.js: -------------------------------------------------------------------------------- 1 | // source: validations.proto 2 | /** 3 | * @fileoverview 4 | * @enhanceable 5 | * @suppress {missingRequire} reports error on implicit type usages. 6 | * @suppress {messageConventions} JS Compiler reports an error if a variable or 7 | * field starts with 'MSG_' and isn't a translatable message. 8 | * @public 9 | */ 10 | // GENERATED CODE -- DO NOT EDIT! 11 | /* eslint-disable */ 12 | // @ts-nocheck 13 | 14 | var jspb = require('google-protobuf'); 15 | var goog = jspb; 16 | var global = Function('return this')(); 17 | 18 | goog.exportSymbol('proto.validations.LedgerRangeRequest', null, global); 19 | goog.exportSymbol('proto.validations.LedgerRequest', null, global); 20 | goog.exportSymbol('proto.validations.MasterKeyRequest', null, global); 21 | goog.exportSymbol('proto.validations.ValidationResponse', null, global); 22 | /** 23 | * Generated by JsPbCodeGenerator. 24 | * @param {Array=} opt_data Optional initial data array, typically from a 25 | * server response, or constructed directly in Javascript. The array is used 26 | * in place and becomes part of the constructed object. It is not cloned. 27 | * If no data is provided, the constructed object will be empty, but still 28 | * valid. 29 | * @extends {jspb.Message} 30 | * @constructor 31 | */ 32 | proto.validations.LedgerRequest = function(opt_data) { 33 | jspb.Message.initialize(this, opt_data, 0, -1, null, null); 34 | }; 35 | goog.inherits(proto.validations.LedgerRequest, jspb.Message); 36 | if (goog.DEBUG && !COMPILED) { 37 | /** 38 | * @public 39 | * @override 40 | */ 41 | proto.validations.LedgerRequest.displayName = 'proto.validations.LedgerRequest'; 42 | } 43 | /** 44 | * Generated by JsPbCodeGenerator. 45 | * @param {Array=} opt_data Optional initial data array, typically from a 46 | * server response, or constructed directly in Javascript. The array is used 47 | * in place and becomes part of the constructed object. It is not cloned. 48 | * If no data is provided, the constructed object will be empty, but still 49 | * valid. 50 | * @extends {jspb.Message} 51 | * @constructor 52 | */ 53 | proto.validations.LedgerRangeRequest = function(opt_data) { 54 | jspb.Message.initialize(this, opt_data, 0, -1, null, null); 55 | }; 56 | goog.inherits(proto.validations.LedgerRangeRequest, jspb.Message); 57 | if (goog.DEBUG && !COMPILED) { 58 | /** 59 | * @public 60 | * @override 61 | */ 62 | proto.validations.LedgerRangeRequest.displayName = 'proto.validations.LedgerRangeRequest'; 63 | } 64 | /** 65 | * Generated by JsPbCodeGenerator. 66 | * @param {Array=} opt_data Optional initial data array, typically from a 67 | * server response, or constructed directly in Javascript. The array is used 68 | * in place and becomes part of the constructed object. It is not cloned. 69 | * If no data is provided, the constructed object will be empty, but still 70 | * valid. 71 | * @extends {jspb.Message} 72 | * @constructor 73 | */ 74 | proto.validations.MasterKeyRequest = function(opt_data) { 75 | jspb.Message.initialize(this, opt_data, 0, -1, null, null); 76 | }; 77 | goog.inherits(proto.validations.MasterKeyRequest, jspb.Message); 78 | if (goog.DEBUG && !COMPILED) { 79 | /** 80 | * @public 81 | * @override 82 | */ 83 | proto.validations.MasterKeyRequest.displayName = 'proto.validations.MasterKeyRequest'; 84 | } 85 | /** 86 | * Generated by JsPbCodeGenerator. 87 | * @param {Array=} opt_data Optional initial data array, typically from a 88 | * server response, or constructed directly in Javascript. The array is used 89 | * in place and becomes part of the constructed object. It is not cloned. 90 | * If no data is provided, the constructed object will be empty, but still 91 | * valid. 92 | * @extends {jspb.Message} 93 | * @constructor 94 | */ 95 | proto.validations.ValidationResponse = function(opt_data) { 96 | jspb.Message.initialize(this, opt_data, 0, -1, null, null); 97 | }; 98 | goog.inherits(proto.validations.ValidationResponse, jspb.Message); 99 | if (goog.DEBUG && !COMPILED) { 100 | /** 101 | * @public 102 | * @override 103 | */ 104 | proto.validations.ValidationResponse.displayName = 'proto.validations.ValidationResponse'; 105 | } 106 | 107 | 108 | 109 | if (jspb.Message.GENERATE_TO_OBJECT) { 110 | /** 111 | * Creates an object representation of this proto. 112 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 113 | * Optional fields that are not set will be set to undefined. 114 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 115 | * For the list of reserved names please see: 116 | * net/proto2/compiler/js/internal/generator.cc#kKeyword. 117 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the 118 | * JSPB instance for transitional soy proto support: 119 | * http://goto/soy-param-migration 120 | * @return {!Object} 121 | */ 122 | proto.validations.LedgerRequest.prototype.toObject = function(opt_includeInstance) { 123 | return proto.validations.LedgerRequest.toObject(opt_includeInstance, this); 124 | }; 125 | 126 | 127 | /** 128 | * Static version of the {@see toObject} method. 129 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include 130 | * the JSPB instance for transitional soy proto support: 131 | * http://goto/soy-param-migration 132 | * @param {!proto.validations.LedgerRequest} msg The msg instance to transform. 133 | * @return {!Object} 134 | * @suppress {unusedLocalVariables} f is only used for nested messages 135 | */ 136 | proto.validations.LedgerRequest.toObject = function(includeInstance, msg) { 137 | var f, obj = { 138 | ledgerIndex: jspb.Message.getFieldWithDefault(msg, 1, 0), 139 | requestingNode: jspb.Message.getFieldWithDefault(msg, 14, ""), 140 | requestingHost: jspb.Message.getFieldWithDefault(msg, 15, "") 141 | }; 142 | 143 | if (includeInstance) { 144 | obj.$jspbMessageInstance = msg; 145 | } 146 | return obj; 147 | }; 148 | } 149 | 150 | 151 | /** 152 | * Deserializes binary data (in protobuf wire format). 153 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 154 | * @return {!proto.validations.LedgerRequest} 155 | */ 156 | proto.validations.LedgerRequest.deserializeBinary = function(bytes) { 157 | var reader = new jspb.BinaryReader(bytes); 158 | var msg = new proto.validations.LedgerRequest; 159 | return proto.validations.LedgerRequest.deserializeBinaryFromReader(msg, reader); 160 | }; 161 | 162 | 163 | /** 164 | * Deserializes binary data (in protobuf wire format) from the 165 | * given reader into the given message object. 166 | * @param {!proto.validations.LedgerRequest} msg The message object to deserialize into. 167 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 168 | * @return {!proto.validations.LedgerRequest} 169 | */ 170 | proto.validations.LedgerRequest.deserializeBinaryFromReader = function(msg, reader) { 171 | while (reader.nextField()) { 172 | if (reader.isEndGroup()) { 173 | break; 174 | } 175 | var field = reader.getFieldNumber(); 176 | switch (field) { 177 | case 1: 178 | var value = /** @type {number} */ (reader.readUint32()); 179 | msg.setLedgerIndex(value); 180 | break; 181 | case 14: 182 | var value = /** @type {string} */ (reader.readString()); 183 | msg.setRequestingNode(value); 184 | break; 185 | case 15: 186 | var value = /** @type {string} */ (reader.readString()); 187 | msg.setRequestingHost(value); 188 | break; 189 | default: 190 | reader.skipField(); 191 | break; 192 | } 193 | } 194 | return msg; 195 | }; 196 | 197 | 198 | /** 199 | * Serializes the message to binary data (in protobuf wire format). 200 | * @return {!Uint8Array} 201 | */ 202 | proto.validations.LedgerRequest.prototype.serializeBinary = function() { 203 | var writer = new jspb.BinaryWriter(); 204 | proto.validations.LedgerRequest.serializeBinaryToWriter(this, writer); 205 | return writer.getResultBuffer(); 206 | }; 207 | 208 | 209 | /** 210 | * Serializes the given message to binary data (in protobuf wire 211 | * format), writing to the given BinaryWriter. 212 | * @param {!proto.validations.LedgerRequest} message 213 | * @param {!jspb.BinaryWriter} writer 214 | * @suppress {unusedLocalVariables} f is only used for nested messages 215 | */ 216 | proto.validations.LedgerRequest.serializeBinaryToWriter = function(message, writer) { 217 | var f = undefined; 218 | f = message.getLedgerIndex(); 219 | if (f !== 0) { 220 | writer.writeUint32( 221 | 1, 222 | f 223 | ); 224 | } 225 | f = /** @type {string} */ (jspb.Message.getField(message, 14)); 226 | if (f != null) { 227 | writer.writeString( 228 | 14, 229 | f 230 | ); 231 | } 232 | f = /** @type {string} */ (jspb.Message.getField(message, 15)); 233 | if (f != null) { 234 | writer.writeString( 235 | 15, 236 | f 237 | ); 238 | } 239 | }; 240 | 241 | 242 | /** 243 | * optional uint32 ledger_index = 1; 244 | * @return {number} 245 | */ 246 | proto.validations.LedgerRequest.prototype.getLedgerIndex = function() { 247 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); 248 | }; 249 | 250 | 251 | /** 252 | * @param {number} value 253 | * @return {!proto.validations.LedgerRequest} returns this 254 | */ 255 | proto.validations.LedgerRequest.prototype.setLedgerIndex = function(value) { 256 | return jspb.Message.setProto3IntField(this, 1, value); 257 | }; 258 | 259 | 260 | /** 261 | * optional string requesting_node = 14; 262 | * @return {string} 263 | */ 264 | proto.validations.LedgerRequest.prototype.getRequestingNode = function() { 265 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 14, "")); 266 | }; 267 | 268 | 269 | /** 270 | * @param {string} value 271 | * @return {!proto.validations.LedgerRequest} returns this 272 | */ 273 | proto.validations.LedgerRequest.prototype.setRequestingNode = function(value) { 274 | return jspb.Message.setField(this, 14, value); 275 | }; 276 | 277 | 278 | /** 279 | * Clears the field making it undefined. 280 | * @return {!proto.validations.LedgerRequest} returns this 281 | */ 282 | proto.validations.LedgerRequest.prototype.clearRequestingNode = function() { 283 | return jspb.Message.setField(this, 14, undefined); 284 | }; 285 | 286 | 287 | /** 288 | * Returns whether this field is set. 289 | * @return {boolean} 290 | */ 291 | proto.validations.LedgerRequest.prototype.hasRequestingNode = function() { 292 | return jspb.Message.getField(this, 14) != null; 293 | }; 294 | 295 | 296 | /** 297 | * optional string requesting_host = 15; 298 | * @return {string} 299 | */ 300 | proto.validations.LedgerRequest.prototype.getRequestingHost = function() { 301 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 15, "")); 302 | }; 303 | 304 | 305 | /** 306 | * @param {string} value 307 | * @return {!proto.validations.LedgerRequest} returns this 308 | */ 309 | proto.validations.LedgerRequest.prototype.setRequestingHost = function(value) { 310 | return jspb.Message.setField(this, 15, value); 311 | }; 312 | 313 | 314 | /** 315 | * Clears the field making it undefined. 316 | * @return {!proto.validations.LedgerRequest} returns this 317 | */ 318 | proto.validations.LedgerRequest.prototype.clearRequestingHost = function() { 319 | return jspb.Message.setField(this, 15, undefined); 320 | }; 321 | 322 | 323 | /** 324 | * Returns whether this field is set. 325 | * @return {boolean} 326 | */ 327 | proto.validations.LedgerRequest.prototype.hasRequestingHost = function() { 328 | return jspb.Message.getField(this, 15) != null; 329 | }; 330 | 331 | 332 | 333 | 334 | 335 | if (jspb.Message.GENERATE_TO_OBJECT) { 336 | /** 337 | * Creates an object representation of this proto. 338 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 339 | * Optional fields that are not set will be set to undefined. 340 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 341 | * For the list of reserved names please see: 342 | * net/proto2/compiler/js/internal/generator.cc#kKeyword. 343 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the 344 | * JSPB instance for transitional soy proto support: 345 | * http://goto/soy-param-migration 346 | * @return {!Object} 347 | */ 348 | proto.validations.LedgerRangeRequest.prototype.toObject = function(opt_includeInstance) { 349 | return proto.validations.LedgerRangeRequest.toObject(opt_includeInstance, this); 350 | }; 351 | 352 | 353 | /** 354 | * Static version of the {@see toObject} method. 355 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include 356 | * the JSPB instance for transitional soy proto support: 357 | * http://goto/soy-param-migration 358 | * @param {!proto.validations.LedgerRangeRequest} msg The msg instance to transform. 359 | * @return {!Object} 360 | * @suppress {unusedLocalVariables} f is only used for nested messages 361 | */ 362 | proto.validations.LedgerRangeRequest.toObject = function(includeInstance, msg) { 363 | var f, obj = { 364 | ledgerIndexMin: jspb.Message.getFieldWithDefault(msg, 1, 0), 365 | ledgerIndexMax: jspb.Message.getFieldWithDefault(msg, 2, 0), 366 | requestingNode: jspb.Message.getFieldWithDefault(msg, 14, ""), 367 | requestingHost: jspb.Message.getFieldWithDefault(msg, 15, "") 368 | }; 369 | 370 | if (includeInstance) { 371 | obj.$jspbMessageInstance = msg; 372 | } 373 | return obj; 374 | }; 375 | } 376 | 377 | 378 | /** 379 | * Deserializes binary data (in protobuf wire format). 380 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 381 | * @return {!proto.validations.LedgerRangeRequest} 382 | */ 383 | proto.validations.LedgerRangeRequest.deserializeBinary = function(bytes) { 384 | var reader = new jspb.BinaryReader(bytes); 385 | var msg = new proto.validations.LedgerRangeRequest; 386 | return proto.validations.LedgerRangeRequest.deserializeBinaryFromReader(msg, reader); 387 | }; 388 | 389 | 390 | /** 391 | * Deserializes binary data (in protobuf wire format) from the 392 | * given reader into the given message object. 393 | * @param {!proto.validations.LedgerRangeRequest} msg The message object to deserialize into. 394 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 395 | * @return {!proto.validations.LedgerRangeRequest} 396 | */ 397 | proto.validations.LedgerRangeRequest.deserializeBinaryFromReader = function(msg, reader) { 398 | while (reader.nextField()) { 399 | if (reader.isEndGroup()) { 400 | break; 401 | } 402 | var field = reader.getFieldNumber(); 403 | switch (field) { 404 | case 1: 405 | var value = /** @type {number} */ (reader.readUint32()); 406 | msg.setLedgerIndexMin(value); 407 | break; 408 | case 2: 409 | var value = /** @type {number} */ (reader.readUint32()); 410 | msg.setLedgerIndexMax(value); 411 | break; 412 | case 14: 413 | var value = /** @type {string} */ (reader.readString()); 414 | msg.setRequestingNode(value); 415 | break; 416 | case 15: 417 | var value = /** @type {string} */ (reader.readString()); 418 | msg.setRequestingHost(value); 419 | break; 420 | default: 421 | reader.skipField(); 422 | break; 423 | } 424 | } 425 | return msg; 426 | }; 427 | 428 | 429 | /** 430 | * Serializes the message to binary data (in protobuf wire format). 431 | * @return {!Uint8Array} 432 | */ 433 | proto.validations.LedgerRangeRequest.prototype.serializeBinary = function() { 434 | var writer = new jspb.BinaryWriter(); 435 | proto.validations.LedgerRangeRequest.serializeBinaryToWriter(this, writer); 436 | return writer.getResultBuffer(); 437 | }; 438 | 439 | 440 | /** 441 | * Serializes the given message to binary data (in protobuf wire 442 | * format), writing to the given BinaryWriter. 443 | * @param {!proto.validations.LedgerRangeRequest} message 444 | * @param {!jspb.BinaryWriter} writer 445 | * @suppress {unusedLocalVariables} f is only used for nested messages 446 | */ 447 | proto.validations.LedgerRangeRequest.serializeBinaryToWriter = function(message, writer) { 448 | var f = undefined; 449 | f = message.getLedgerIndexMin(); 450 | if (f !== 0) { 451 | writer.writeUint32( 452 | 1, 453 | f 454 | ); 455 | } 456 | f = message.getLedgerIndexMax(); 457 | if (f !== 0) { 458 | writer.writeUint32( 459 | 2, 460 | f 461 | ); 462 | } 463 | f = /** @type {string} */ (jspb.Message.getField(message, 14)); 464 | if (f != null) { 465 | writer.writeString( 466 | 14, 467 | f 468 | ); 469 | } 470 | f = /** @type {string} */ (jspb.Message.getField(message, 15)); 471 | if (f != null) { 472 | writer.writeString( 473 | 15, 474 | f 475 | ); 476 | } 477 | }; 478 | 479 | 480 | /** 481 | * optional uint32 ledger_index_min = 1; 482 | * @return {number} 483 | */ 484 | proto.validations.LedgerRangeRequest.prototype.getLedgerIndexMin = function() { 485 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); 486 | }; 487 | 488 | 489 | /** 490 | * @param {number} value 491 | * @return {!proto.validations.LedgerRangeRequest} returns this 492 | */ 493 | proto.validations.LedgerRangeRequest.prototype.setLedgerIndexMin = function(value) { 494 | return jspb.Message.setProto3IntField(this, 1, value); 495 | }; 496 | 497 | 498 | /** 499 | * optional uint32 ledger_index_max = 2; 500 | * @return {number} 501 | */ 502 | proto.validations.LedgerRangeRequest.prototype.getLedgerIndexMax = function() { 503 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); 504 | }; 505 | 506 | 507 | /** 508 | * @param {number} value 509 | * @return {!proto.validations.LedgerRangeRequest} returns this 510 | */ 511 | proto.validations.LedgerRangeRequest.prototype.setLedgerIndexMax = function(value) { 512 | return jspb.Message.setProto3IntField(this, 2, value); 513 | }; 514 | 515 | 516 | /** 517 | * optional string requesting_node = 14; 518 | * @return {string} 519 | */ 520 | proto.validations.LedgerRangeRequest.prototype.getRequestingNode = function() { 521 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 14, "")); 522 | }; 523 | 524 | 525 | /** 526 | * @param {string} value 527 | * @return {!proto.validations.LedgerRangeRequest} returns this 528 | */ 529 | proto.validations.LedgerRangeRequest.prototype.setRequestingNode = function(value) { 530 | return jspb.Message.setField(this, 14, value); 531 | }; 532 | 533 | 534 | /** 535 | * Clears the field making it undefined. 536 | * @return {!proto.validations.LedgerRangeRequest} returns this 537 | */ 538 | proto.validations.LedgerRangeRequest.prototype.clearRequestingNode = function() { 539 | return jspb.Message.setField(this, 14, undefined); 540 | }; 541 | 542 | 543 | /** 544 | * Returns whether this field is set. 545 | * @return {boolean} 546 | */ 547 | proto.validations.LedgerRangeRequest.prototype.hasRequestingNode = function() { 548 | return jspb.Message.getField(this, 14) != null; 549 | }; 550 | 551 | 552 | /** 553 | * optional string requesting_host = 15; 554 | * @return {string} 555 | */ 556 | proto.validations.LedgerRangeRequest.prototype.getRequestingHost = function() { 557 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 15, "")); 558 | }; 559 | 560 | 561 | /** 562 | * @param {string} value 563 | * @return {!proto.validations.LedgerRangeRequest} returns this 564 | */ 565 | proto.validations.LedgerRangeRequest.prototype.setRequestingHost = function(value) { 566 | return jspb.Message.setField(this, 15, value); 567 | }; 568 | 569 | 570 | /** 571 | * Clears the field making it undefined. 572 | * @return {!proto.validations.LedgerRangeRequest} returns this 573 | */ 574 | proto.validations.LedgerRangeRequest.prototype.clearRequestingHost = function() { 575 | return jspb.Message.setField(this, 15, undefined); 576 | }; 577 | 578 | 579 | /** 580 | * Returns whether this field is set. 581 | * @return {boolean} 582 | */ 583 | proto.validations.LedgerRangeRequest.prototype.hasRequestingHost = function() { 584 | return jspb.Message.getField(this, 15) != null; 585 | }; 586 | 587 | 588 | 589 | 590 | 591 | if (jspb.Message.GENERATE_TO_OBJECT) { 592 | /** 593 | * Creates an object representation of this proto. 594 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 595 | * Optional fields that are not set will be set to undefined. 596 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 597 | * For the list of reserved names please see: 598 | * net/proto2/compiler/js/internal/generator.cc#kKeyword. 599 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the 600 | * JSPB instance for transitional soy proto support: 601 | * http://goto/soy-param-migration 602 | * @return {!Object} 603 | */ 604 | proto.validations.MasterKeyRequest.prototype.toObject = function(opt_includeInstance) { 605 | return proto.validations.MasterKeyRequest.toObject(opt_includeInstance, this); 606 | }; 607 | 608 | 609 | /** 610 | * Static version of the {@see toObject} method. 611 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include 612 | * the JSPB instance for transitional soy proto support: 613 | * http://goto/soy-param-migration 614 | * @param {!proto.validations.MasterKeyRequest} msg The msg instance to transform. 615 | * @return {!Object} 616 | * @suppress {unusedLocalVariables} f is only used for nested messages 617 | */ 618 | proto.validations.MasterKeyRequest.toObject = function(includeInstance, msg) { 619 | var f, obj = { 620 | masterKey: jspb.Message.getFieldWithDefault(msg, 1, ""), 621 | requestingNode: jspb.Message.getFieldWithDefault(msg, 14, ""), 622 | requestingHost: jspb.Message.getFieldWithDefault(msg, 15, "") 623 | }; 624 | 625 | if (includeInstance) { 626 | obj.$jspbMessageInstance = msg; 627 | } 628 | return obj; 629 | }; 630 | } 631 | 632 | 633 | /** 634 | * Deserializes binary data (in protobuf wire format). 635 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 636 | * @return {!proto.validations.MasterKeyRequest} 637 | */ 638 | proto.validations.MasterKeyRequest.deserializeBinary = function(bytes) { 639 | var reader = new jspb.BinaryReader(bytes); 640 | var msg = new proto.validations.MasterKeyRequest; 641 | return proto.validations.MasterKeyRequest.deserializeBinaryFromReader(msg, reader); 642 | }; 643 | 644 | 645 | /** 646 | * Deserializes binary data (in protobuf wire format) from the 647 | * given reader into the given message object. 648 | * @param {!proto.validations.MasterKeyRequest} msg The message object to deserialize into. 649 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 650 | * @return {!proto.validations.MasterKeyRequest} 651 | */ 652 | proto.validations.MasterKeyRequest.deserializeBinaryFromReader = function(msg, reader) { 653 | while (reader.nextField()) { 654 | if (reader.isEndGroup()) { 655 | break; 656 | } 657 | var field = reader.getFieldNumber(); 658 | switch (field) { 659 | case 1: 660 | var value = /** @type {string} */ (reader.readString()); 661 | msg.setMasterKey(value); 662 | break; 663 | case 14: 664 | var value = /** @type {string} */ (reader.readString()); 665 | msg.setRequestingNode(value); 666 | break; 667 | case 15: 668 | var value = /** @type {string} */ (reader.readString()); 669 | msg.setRequestingHost(value); 670 | break; 671 | default: 672 | reader.skipField(); 673 | break; 674 | } 675 | } 676 | return msg; 677 | }; 678 | 679 | 680 | /** 681 | * Serializes the message to binary data (in protobuf wire format). 682 | * @return {!Uint8Array} 683 | */ 684 | proto.validations.MasterKeyRequest.prototype.serializeBinary = function() { 685 | var writer = new jspb.BinaryWriter(); 686 | proto.validations.MasterKeyRequest.serializeBinaryToWriter(this, writer); 687 | return writer.getResultBuffer(); 688 | }; 689 | 690 | 691 | /** 692 | * Serializes the given message to binary data (in protobuf wire 693 | * format), writing to the given BinaryWriter. 694 | * @param {!proto.validations.MasterKeyRequest} message 695 | * @param {!jspb.BinaryWriter} writer 696 | * @suppress {unusedLocalVariables} f is only used for nested messages 697 | */ 698 | proto.validations.MasterKeyRequest.serializeBinaryToWriter = function(message, writer) { 699 | var f = undefined; 700 | f = message.getMasterKey(); 701 | if (f.length > 0) { 702 | writer.writeString( 703 | 1, 704 | f 705 | ); 706 | } 707 | f = /** @type {string} */ (jspb.Message.getField(message, 14)); 708 | if (f != null) { 709 | writer.writeString( 710 | 14, 711 | f 712 | ); 713 | } 714 | f = /** @type {string} */ (jspb.Message.getField(message, 15)); 715 | if (f != null) { 716 | writer.writeString( 717 | 15, 718 | f 719 | ); 720 | } 721 | }; 722 | 723 | 724 | /** 725 | * optional string master_key = 1; 726 | * @return {string} 727 | */ 728 | proto.validations.MasterKeyRequest.prototype.getMasterKey = function() { 729 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); 730 | }; 731 | 732 | 733 | /** 734 | * @param {string} value 735 | * @return {!proto.validations.MasterKeyRequest} returns this 736 | */ 737 | proto.validations.MasterKeyRequest.prototype.setMasterKey = function(value) { 738 | return jspb.Message.setProto3StringField(this, 1, value); 739 | }; 740 | 741 | 742 | /** 743 | * optional string requesting_node = 14; 744 | * @return {string} 745 | */ 746 | proto.validations.MasterKeyRequest.prototype.getRequestingNode = function() { 747 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 14, "")); 748 | }; 749 | 750 | 751 | /** 752 | * @param {string} value 753 | * @return {!proto.validations.MasterKeyRequest} returns this 754 | */ 755 | proto.validations.MasterKeyRequest.prototype.setRequestingNode = function(value) { 756 | return jspb.Message.setField(this, 14, value); 757 | }; 758 | 759 | 760 | /** 761 | * Clears the field making it undefined. 762 | * @return {!proto.validations.MasterKeyRequest} returns this 763 | */ 764 | proto.validations.MasterKeyRequest.prototype.clearRequestingNode = function() { 765 | return jspb.Message.setField(this, 14, undefined); 766 | }; 767 | 768 | 769 | /** 770 | * Returns whether this field is set. 771 | * @return {boolean} 772 | */ 773 | proto.validations.MasterKeyRequest.prototype.hasRequestingNode = function() { 774 | return jspb.Message.getField(this, 14) != null; 775 | }; 776 | 777 | 778 | /** 779 | * optional string requesting_host = 15; 780 | * @return {string} 781 | */ 782 | proto.validations.MasterKeyRequest.prototype.getRequestingHost = function() { 783 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 15, "")); 784 | }; 785 | 786 | 787 | /** 788 | * @param {string} value 789 | * @return {!proto.validations.MasterKeyRequest} returns this 790 | */ 791 | proto.validations.MasterKeyRequest.prototype.setRequestingHost = function(value) { 792 | return jspb.Message.setField(this, 15, value); 793 | }; 794 | 795 | 796 | /** 797 | * Clears the field making it undefined. 798 | * @return {!proto.validations.MasterKeyRequest} returns this 799 | */ 800 | proto.validations.MasterKeyRequest.prototype.clearRequestingHost = function() { 801 | return jspb.Message.setField(this, 15, undefined); 802 | }; 803 | 804 | 805 | /** 806 | * Returns whether this field is set. 807 | * @return {boolean} 808 | */ 809 | proto.validations.MasterKeyRequest.prototype.hasRequestingHost = function() { 810 | return jspb.Message.getField(this, 15) != null; 811 | }; 812 | 813 | 814 | 815 | 816 | 817 | if (jspb.Message.GENERATE_TO_OBJECT) { 818 | /** 819 | * Creates an object representation of this proto. 820 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 821 | * Optional fields that are not set will be set to undefined. 822 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 823 | * For the list of reserved names please see: 824 | * net/proto2/compiler/js/internal/generator.cc#kKeyword. 825 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the 826 | * JSPB instance for transitional soy proto support: 827 | * http://goto/soy-param-migration 828 | * @return {!Object} 829 | */ 830 | proto.validations.ValidationResponse.prototype.toObject = function(opt_includeInstance) { 831 | return proto.validations.ValidationResponse.toObject(opt_includeInstance, this); 832 | }; 833 | 834 | 835 | /** 836 | * Static version of the {@see toObject} method. 837 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include 838 | * the JSPB instance for transitional soy proto support: 839 | * http://goto/soy-param-migration 840 | * @param {!proto.validations.ValidationResponse} msg The msg instance to transform. 841 | * @return {!Object} 842 | * @suppress {unusedLocalVariables} f is only used for nested messages 843 | */ 844 | proto.validations.ValidationResponse.toObject = function(includeInstance, msg) { 845 | var f, obj = { 846 | cookie: jspb.Message.getFieldWithDefault(msg, 1, ""), 847 | type: jspb.Message.getFieldWithDefault(msg, 2, ""), 848 | flags: jspb.Message.getFieldWithDefault(msg, 3, 0), 849 | full: jspb.Message.getBooleanFieldWithDefault(msg, 4, false), 850 | ledgerHash: jspb.Message.getFieldWithDefault(msg, 5, ""), 851 | ledgerIndex: jspb.Message.getFieldWithDefault(msg, 6, 0), 852 | masterKey: jspb.Message.getFieldWithDefault(msg, 7, ""), 853 | signature: jspb.Message.getFieldWithDefault(msg, 8, ""), 854 | signingTime: jspb.Message.getFieldWithDefault(msg, 9, 0), 855 | validatedHash: jspb.Message.getFieldWithDefault(msg, 10, ""), 856 | validationPublicKey: jspb.Message.getFieldWithDefault(msg, 11, ""), 857 | data: jspb.Message.getFieldWithDefault(msg, 12, ""), 858 | serverVersion: jspb.Message.getFieldWithDefault(msg, 16, ""), 859 | baseFee: jspb.Message.getFieldWithDefault(msg, 17, 0), 860 | loadFee: jspb.Message.getFieldWithDefault(msg, 18, 0), 861 | reserveBase: jspb.Message.getFieldWithDefault(msg, 19, 0), 862 | reserveInc: jspb.Message.getFieldWithDefault(msg, 20, 0) 863 | }; 864 | 865 | if (includeInstance) { 866 | obj.$jspbMessageInstance = msg; 867 | } 868 | return obj; 869 | }; 870 | } 871 | 872 | 873 | /** 874 | * Deserializes binary data (in protobuf wire format). 875 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 876 | * @return {!proto.validations.ValidationResponse} 877 | */ 878 | proto.validations.ValidationResponse.deserializeBinary = function(bytes) { 879 | var reader = new jspb.BinaryReader(bytes); 880 | var msg = new proto.validations.ValidationResponse; 881 | return proto.validations.ValidationResponse.deserializeBinaryFromReader(msg, reader); 882 | }; 883 | 884 | 885 | /** 886 | * Deserializes binary data (in protobuf wire format) from the 887 | * given reader into the given message object. 888 | * @param {!proto.validations.ValidationResponse} msg The message object to deserialize into. 889 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 890 | * @return {!proto.validations.ValidationResponse} 891 | */ 892 | proto.validations.ValidationResponse.deserializeBinaryFromReader = function(msg, reader) { 893 | while (reader.nextField()) { 894 | if (reader.isEndGroup()) { 895 | break; 896 | } 897 | var field = reader.getFieldNumber(); 898 | switch (field) { 899 | case 1: 900 | var value = /** @type {string} */ (reader.readString()); 901 | msg.setCookie(value); 902 | break; 903 | case 2: 904 | var value = /** @type {string} */ (reader.readString()); 905 | msg.setType(value); 906 | break; 907 | case 3: 908 | var value = /** @type {number} */ (reader.readUint32()); 909 | msg.setFlags(value); 910 | break; 911 | case 4: 912 | var value = /** @type {boolean} */ (reader.readBool()); 913 | msg.setFull(value); 914 | break; 915 | case 5: 916 | var value = /** @type {string} */ (reader.readString()); 917 | msg.setLedgerHash(value); 918 | break; 919 | case 6: 920 | var value = /** @type {number} */ (reader.readUint32()); 921 | msg.setLedgerIndex(value); 922 | break; 923 | case 7: 924 | var value = /** @type {string} */ (reader.readString()); 925 | msg.setMasterKey(value); 926 | break; 927 | case 8: 928 | var value = /** @type {string} */ (reader.readString()); 929 | msg.setSignature(value); 930 | break; 931 | case 9: 932 | var value = /** @type {number} */ (reader.readUint32()); 933 | msg.setSigningTime(value); 934 | break; 935 | case 10: 936 | var value = /** @type {string} */ (reader.readString()); 937 | msg.setValidatedHash(value); 938 | break; 939 | case 11: 940 | var value = /** @type {string} */ (reader.readString()); 941 | msg.setValidationPublicKey(value); 942 | break; 943 | case 12: 944 | var value = /** @type {string} */ (reader.readString()); 945 | msg.setData(value); 946 | break; 947 | case 16: 948 | var value = /** @type {string} */ (reader.readString()); 949 | msg.setServerVersion(value); 950 | break; 951 | case 17: 952 | var value = /** @type {number} */ (reader.readInt32()); 953 | msg.setBaseFee(value); 954 | break; 955 | case 18: 956 | var value = /** @type {number} */ (reader.readInt32()); 957 | msg.setLoadFee(value); 958 | break; 959 | case 19: 960 | var value = /** @type {number} */ (reader.readUint32()); 961 | msg.setReserveBase(value); 962 | break; 963 | case 20: 964 | var value = /** @type {number} */ (reader.readUint32()); 965 | msg.setReserveInc(value); 966 | break; 967 | default: 968 | reader.skipField(); 969 | break; 970 | } 971 | } 972 | return msg; 973 | }; 974 | 975 | 976 | /** 977 | * Serializes the message to binary data (in protobuf wire format). 978 | * @return {!Uint8Array} 979 | */ 980 | proto.validations.ValidationResponse.prototype.serializeBinary = function() { 981 | var writer = new jspb.BinaryWriter(); 982 | proto.validations.ValidationResponse.serializeBinaryToWriter(this, writer); 983 | return writer.getResultBuffer(); 984 | }; 985 | 986 | 987 | /** 988 | * Serializes the given message to binary data (in protobuf wire 989 | * format), writing to the given BinaryWriter. 990 | * @param {!proto.validations.ValidationResponse} message 991 | * @param {!jspb.BinaryWriter} writer 992 | * @suppress {unusedLocalVariables} f is only used for nested messages 993 | */ 994 | proto.validations.ValidationResponse.serializeBinaryToWriter = function(message, writer) { 995 | var f = undefined; 996 | f = message.getCookie(); 997 | if (f.length > 0) { 998 | writer.writeString( 999 | 1, 1000 | f 1001 | ); 1002 | } 1003 | f = message.getType(); 1004 | if (f.length > 0) { 1005 | writer.writeString( 1006 | 2, 1007 | f 1008 | ); 1009 | } 1010 | f = message.getFlags(); 1011 | if (f !== 0) { 1012 | writer.writeUint32( 1013 | 3, 1014 | f 1015 | ); 1016 | } 1017 | f = message.getFull(); 1018 | if (f) { 1019 | writer.writeBool( 1020 | 4, 1021 | f 1022 | ); 1023 | } 1024 | f = message.getLedgerHash(); 1025 | if (f.length > 0) { 1026 | writer.writeString( 1027 | 5, 1028 | f 1029 | ); 1030 | } 1031 | f = message.getLedgerIndex(); 1032 | if (f !== 0) { 1033 | writer.writeUint32( 1034 | 6, 1035 | f 1036 | ); 1037 | } 1038 | f = message.getMasterKey(); 1039 | if (f.length > 0) { 1040 | writer.writeString( 1041 | 7, 1042 | f 1043 | ); 1044 | } 1045 | f = message.getSignature(); 1046 | if (f.length > 0) { 1047 | writer.writeString( 1048 | 8, 1049 | f 1050 | ); 1051 | } 1052 | f = message.getSigningTime(); 1053 | if (f !== 0) { 1054 | writer.writeUint32( 1055 | 9, 1056 | f 1057 | ); 1058 | } 1059 | f = message.getValidatedHash(); 1060 | if (f.length > 0) { 1061 | writer.writeString( 1062 | 10, 1063 | f 1064 | ); 1065 | } 1066 | f = message.getValidationPublicKey(); 1067 | if (f.length > 0) { 1068 | writer.writeString( 1069 | 11, 1070 | f 1071 | ); 1072 | } 1073 | f = message.getData(); 1074 | if (f.length > 0) { 1075 | writer.writeString( 1076 | 12, 1077 | f 1078 | ); 1079 | } 1080 | f = message.getServerVersion(); 1081 | if (f.length > 0) { 1082 | writer.writeString( 1083 | 16, 1084 | f 1085 | ); 1086 | } 1087 | f = message.getBaseFee(); 1088 | if (f !== 0) { 1089 | writer.writeInt32( 1090 | 17, 1091 | f 1092 | ); 1093 | } 1094 | f = message.getLoadFee(); 1095 | if (f !== 0) { 1096 | writer.writeInt32( 1097 | 18, 1098 | f 1099 | ); 1100 | } 1101 | f = message.getReserveBase(); 1102 | if (f !== 0) { 1103 | writer.writeUint32( 1104 | 19, 1105 | f 1106 | ); 1107 | } 1108 | f = message.getReserveInc(); 1109 | if (f !== 0) { 1110 | writer.writeUint32( 1111 | 20, 1112 | f 1113 | ); 1114 | } 1115 | }; 1116 | 1117 | 1118 | /** 1119 | * optional string cookie = 1; 1120 | * @return {string} 1121 | */ 1122 | proto.validations.ValidationResponse.prototype.getCookie = function() { 1123 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); 1124 | }; 1125 | 1126 | 1127 | /** 1128 | * @param {string} value 1129 | * @return {!proto.validations.ValidationResponse} returns this 1130 | */ 1131 | proto.validations.ValidationResponse.prototype.setCookie = function(value) { 1132 | return jspb.Message.setProto3StringField(this, 1, value); 1133 | }; 1134 | 1135 | 1136 | /** 1137 | * optional string type = 2; 1138 | * @return {string} 1139 | */ 1140 | proto.validations.ValidationResponse.prototype.getType = function() { 1141 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); 1142 | }; 1143 | 1144 | 1145 | /** 1146 | * @param {string} value 1147 | * @return {!proto.validations.ValidationResponse} returns this 1148 | */ 1149 | proto.validations.ValidationResponse.prototype.setType = function(value) { 1150 | return jspb.Message.setProto3StringField(this, 2, value); 1151 | }; 1152 | 1153 | 1154 | /** 1155 | * optional uint32 flags = 3; 1156 | * @return {number} 1157 | */ 1158 | proto.validations.ValidationResponse.prototype.getFlags = function() { 1159 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0)); 1160 | }; 1161 | 1162 | 1163 | /** 1164 | * @param {number} value 1165 | * @return {!proto.validations.ValidationResponse} returns this 1166 | */ 1167 | proto.validations.ValidationResponse.prototype.setFlags = function(value) { 1168 | return jspb.Message.setProto3IntField(this, 3, value); 1169 | }; 1170 | 1171 | 1172 | /** 1173 | * optional bool full = 4; 1174 | * @return {boolean} 1175 | */ 1176 | proto.validations.ValidationResponse.prototype.getFull = function() { 1177 | return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false)); 1178 | }; 1179 | 1180 | 1181 | /** 1182 | * @param {boolean} value 1183 | * @return {!proto.validations.ValidationResponse} returns this 1184 | */ 1185 | proto.validations.ValidationResponse.prototype.setFull = function(value) { 1186 | return jspb.Message.setProto3BooleanField(this, 4, value); 1187 | }; 1188 | 1189 | 1190 | /** 1191 | * optional string ledger_hash = 5; 1192 | * @return {string} 1193 | */ 1194 | proto.validations.ValidationResponse.prototype.getLedgerHash = function() { 1195 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 5, "")); 1196 | }; 1197 | 1198 | 1199 | /** 1200 | * @param {string} value 1201 | * @return {!proto.validations.ValidationResponse} returns this 1202 | */ 1203 | proto.validations.ValidationResponse.prototype.setLedgerHash = function(value) { 1204 | return jspb.Message.setProto3StringField(this, 5, value); 1205 | }; 1206 | 1207 | 1208 | /** 1209 | * optional uint32 ledger_index = 6; 1210 | * @return {number} 1211 | */ 1212 | proto.validations.ValidationResponse.prototype.getLedgerIndex = function() { 1213 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 6, 0)); 1214 | }; 1215 | 1216 | 1217 | /** 1218 | * @param {number} value 1219 | * @return {!proto.validations.ValidationResponse} returns this 1220 | */ 1221 | proto.validations.ValidationResponse.prototype.setLedgerIndex = function(value) { 1222 | return jspb.Message.setProto3IntField(this, 6, value); 1223 | }; 1224 | 1225 | 1226 | /** 1227 | * optional string master_key = 7; 1228 | * @return {string} 1229 | */ 1230 | proto.validations.ValidationResponse.prototype.getMasterKey = function() { 1231 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 7, "")); 1232 | }; 1233 | 1234 | 1235 | /** 1236 | * @param {string} value 1237 | * @return {!proto.validations.ValidationResponse} returns this 1238 | */ 1239 | proto.validations.ValidationResponse.prototype.setMasterKey = function(value) { 1240 | return jspb.Message.setProto3StringField(this, 7, value); 1241 | }; 1242 | 1243 | 1244 | /** 1245 | * optional string signature = 8; 1246 | * @return {string} 1247 | */ 1248 | proto.validations.ValidationResponse.prototype.getSignature = function() { 1249 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 8, "")); 1250 | }; 1251 | 1252 | 1253 | /** 1254 | * @param {string} value 1255 | * @return {!proto.validations.ValidationResponse} returns this 1256 | */ 1257 | proto.validations.ValidationResponse.prototype.setSignature = function(value) { 1258 | return jspb.Message.setProto3StringField(this, 8, value); 1259 | }; 1260 | 1261 | 1262 | /** 1263 | * optional uint32 signing_time = 9; 1264 | * @return {number} 1265 | */ 1266 | proto.validations.ValidationResponse.prototype.getSigningTime = function() { 1267 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 9, 0)); 1268 | }; 1269 | 1270 | 1271 | /** 1272 | * @param {number} value 1273 | * @return {!proto.validations.ValidationResponse} returns this 1274 | */ 1275 | proto.validations.ValidationResponse.prototype.setSigningTime = function(value) { 1276 | return jspb.Message.setProto3IntField(this, 9, value); 1277 | }; 1278 | 1279 | 1280 | /** 1281 | * optional string validated_hash = 10; 1282 | * @return {string} 1283 | */ 1284 | proto.validations.ValidationResponse.prototype.getValidatedHash = function() { 1285 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 10, "")); 1286 | }; 1287 | 1288 | 1289 | /** 1290 | * @param {string} value 1291 | * @return {!proto.validations.ValidationResponse} returns this 1292 | */ 1293 | proto.validations.ValidationResponse.prototype.setValidatedHash = function(value) { 1294 | return jspb.Message.setProto3StringField(this, 10, value); 1295 | }; 1296 | 1297 | 1298 | /** 1299 | * optional string validation_public_key = 11; 1300 | * @return {string} 1301 | */ 1302 | proto.validations.ValidationResponse.prototype.getValidationPublicKey = function() { 1303 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 11, "")); 1304 | }; 1305 | 1306 | 1307 | /** 1308 | * @param {string} value 1309 | * @return {!proto.validations.ValidationResponse} returns this 1310 | */ 1311 | proto.validations.ValidationResponse.prototype.setValidationPublicKey = function(value) { 1312 | return jspb.Message.setProto3StringField(this, 11, value); 1313 | }; 1314 | 1315 | 1316 | /** 1317 | * optional string data = 12; 1318 | * @return {string} 1319 | */ 1320 | proto.validations.ValidationResponse.prototype.getData = function() { 1321 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 12, "")); 1322 | }; 1323 | 1324 | 1325 | /** 1326 | * @param {string} value 1327 | * @return {!proto.validations.ValidationResponse} returns this 1328 | */ 1329 | proto.validations.ValidationResponse.prototype.setData = function(value) { 1330 | return jspb.Message.setProto3StringField(this, 12, value); 1331 | }; 1332 | 1333 | 1334 | /** 1335 | * optional string server_version = 16; 1336 | * @return {string} 1337 | */ 1338 | proto.validations.ValidationResponse.prototype.getServerVersion = function() { 1339 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 16, "")); 1340 | }; 1341 | 1342 | 1343 | /** 1344 | * @param {string} value 1345 | * @return {!proto.validations.ValidationResponse} returns this 1346 | */ 1347 | proto.validations.ValidationResponse.prototype.setServerVersion = function(value) { 1348 | return jspb.Message.setProto3StringField(this, 16, value); 1349 | }; 1350 | 1351 | 1352 | /** 1353 | * optional int32 base_fee = 17; 1354 | * @return {number} 1355 | */ 1356 | proto.validations.ValidationResponse.prototype.getBaseFee = function() { 1357 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 17, 0)); 1358 | }; 1359 | 1360 | 1361 | /** 1362 | * @param {number} value 1363 | * @return {!proto.validations.ValidationResponse} returns this 1364 | */ 1365 | proto.validations.ValidationResponse.prototype.setBaseFee = function(value) { 1366 | return jspb.Message.setProto3IntField(this, 17, value); 1367 | }; 1368 | 1369 | 1370 | /** 1371 | * optional int32 load_fee = 18; 1372 | * @return {number} 1373 | */ 1374 | proto.validations.ValidationResponse.prototype.getLoadFee = function() { 1375 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 18, 0)); 1376 | }; 1377 | 1378 | 1379 | /** 1380 | * @param {number} value 1381 | * @return {!proto.validations.ValidationResponse} returns this 1382 | */ 1383 | proto.validations.ValidationResponse.prototype.setLoadFee = function(value) { 1384 | return jspb.Message.setProto3IntField(this, 18, value); 1385 | }; 1386 | 1387 | 1388 | /** 1389 | * optional uint32 reserve_base = 19; 1390 | * @return {number} 1391 | */ 1392 | proto.validations.ValidationResponse.prototype.getReserveBase = function() { 1393 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 19, 0)); 1394 | }; 1395 | 1396 | 1397 | /** 1398 | * @param {number} value 1399 | * @return {!proto.validations.ValidationResponse} returns this 1400 | */ 1401 | proto.validations.ValidationResponse.prototype.setReserveBase = function(value) { 1402 | return jspb.Message.setProto3IntField(this, 19, value); 1403 | }; 1404 | 1405 | 1406 | /** 1407 | * optional uint32 reserve_inc = 20; 1408 | * @return {number} 1409 | */ 1410 | proto.validations.ValidationResponse.prototype.getReserveInc = function() { 1411 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 20, 0)); 1412 | }; 1413 | 1414 | 1415 | /** 1416 | * @param {number} value 1417 | * @return {!proto.validations.ValidationResponse} returns this 1418 | */ 1419 | proto.validations.ValidationResponse.prototype.setReserveInc = function(value) { 1420 | return jspb.Message.setProto3IntField(this, 20, value); 1421 | }; 1422 | 1423 | 1424 | goog.object.extend(exports, proto.validations); 1425 | --------------------------------------------------------------------------------