Skip to content

Commit

Permalink
feat(cardano-services): add NetworkInfoHttpService
Browse files Browse the repository at this point in the history
- extend env vars with CARDANO_NODE_CONFIG_PATH
- create NetworkInfoHttpService
- implement loadConfigs method to load genesis configs
- extend loadHttpServer with NetworkInfo service attached on demand
  • Loading branch information
Ivaylo Andonov committed May 23, 2022
1 parent e3d6876 commit 5f5f007
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 89 deletions.
@@ -1,28 +1,31 @@
import { DbSyncProvider } from '../../DbSyncProvider';
import { GenesisData } from './types';
import { Logger, dummyLogger } from 'ts-log';
import { NetworkInfo, NetworkInfoProvider, testnetTimeSettings } from '@cardano-sdk/core';
import { NetworkInfo, NetworkInfoProvider, timeSettingsConfig } from '@cardano-sdk/core';
import { NetworkInfoBuilder } from './NetworkInfoBuilder';
import { Pool } from 'pg';
import { toNetworkInfo } from './mappers';
import { extractGenesisData, toNetworkInfo } from './mappers';

export class DbSyncNetworkInfoProvider extends DbSyncProvider implements NetworkInfoProvider {
#logger: Logger;
#builder: NetworkInfoBuilder;
#genesisData: GenesisData;

constructor(db: Pool, logger = dummyLogger) {
constructor(cardanoNodeConfigPath: string, db: Pool, logger = dummyLogger) {
super(db);
this.#logger = logger;
this.#builder = new NetworkInfoBuilder(db, logger);
void this.loadGenesisData(cardanoNodeConfigPath);
}

public async loadGenesisData(cardanoNodeConfigPath: string): Promise<void> {
this.#genesisData = await extractGenesisData(cardanoNodeConfigPath);
}

public async networkInfo(): Promise<NetworkInfo> {
// Temporary until we got the genesis file as a package submodule
const { networkMagic, maxLovelaceSupply }: GenesisData = await fetch('./shelleyGenesisTestnet.json').then((data) =>
data.json()
);
// TODO: apply select by network magic ?
const timeSettings = testnetTimeSettings;
const { networkMagic, maxLovelaceSupply } = this.#genesisData;

const timeSettings = timeSettingsConfig[networkMagic];

this.#logger.debug('About to query network info data');

Expand Down
@@ -1,10 +1,12 @@
import { NetworkInfo, TimeSettings } from '@cardano-sdk/core';
import { Era, GenesisData } from './types';
import { NetworkInfo, ProviderError, ProviderFailure, TimeSettings } from '@cardano-sdk/core';
import path from 'path';

interface ToNetworkInfoInput {
networkMagic: number;
timeSettings: TimeSettings[];
maxLovelaceSupply: number;
circulatingSupply: bigint;
maxLovelaceSupply: bigint;
totalSupply: bigint;
activeStake: bigint;
liveStake: bigint;
Expand All @@ -20,16 +22,32 @@ export const toNetworkInfo = ({
liveStake
}: ToNetworkInfoInput): NetworkInfo => ({
lovelaceSupply: {
circulating: circulatingSupply,
max: maxLovelaceSupply,
total: totalSupply
circulating: BigInt(circulatingSupply),
max: BigInt(maxLovelaceSupply),
total: BigInt(totalSupply)
},
network: {
magic: networkMagic,
timeSettings
},
stake: {
active: activeStake,
live: liveStake
active: BigInt(activeStake),
live: BigInt(liveStake)
}
});

const loadGenesisConfig = (cardanoNodeConfigPath: string, genesisFilePath: string) =>
require(path.resolve(path.dirname(cardanoNodeConfigPath), genesisFilePath));

export const extractGenesisData = (cardanoNodeConfigPath: string, eraName = Era.Shelley): GenesisData => {
try {
const genesisFilePath = require(path.resolve(cardanoNodeConfigPath))[`${eraName}GenesisFile`];
const genesis = loadGenesisConfig(cardanoNodeConfigPath, genesisFilePath);
return {
maxLovelaceSupply: genesis.maxLovelaceSupply,
networkMagic: genesis.networkMagic
};
} catch (error) {
throw new ProviderError(ProviderFailure.NotFound, error);
}
};

This file was deleted.

@@ -1,3 +1,5 @@
import { NetworkMagic } from '@cardano-sdk/core/dist/Cardano';

export interface CirculatingSupplyModel {
circulation_supply: bigint;
}
Expand All @@ -15,6 +17,12 @@ export interface LiveStakeModel {
}

export interface GenesisData {
networkMagic: number;
maxLovelaceSupply: bigint;
networkMagic: NetworkMagic;
maxLovelaceSupply: number;
}

export enum Era {
Byron = 'Byron',
Shelley = 'Shelley',
Alonzo = 'Alonzo'
}
@@ -0,0 +1,39 @@
import { DbSyncNetworkInfoProvider } from './DbSyncNetworkInfoProvider';
import { HttpServer, HttpService } from '../Http';
import { Logger, dummyLogger } from 'ts-log';
import { NetworkInfo, ProviderError, ProviderFailure } from '@cardano-sdk/core';
import { ServiceNames } from '../Program';
import { providerHandler } from '../util';
import express from 'express';

export interface NetworkInfoServiceDependencies {
logger?: Logger;
networkInfoProvider: DbSyncNetworkInfoProvider;
}

export class NetworkInfoHttpService extends HttpService {
private constructor({ logger = dummyLogger }: NetworkInfoServiceDependencies, router: express.Router) {
super(ServiceNames.NetworkInfo, router, logger);
}

async healthCheck() {
return Promise.resolve({ ok: true });
}

static create({ logger = dummyLogger, networkInfoProvider }: NetworkInfoServiceDependencies) {
const router = express.Router();

router.post(
'/network',
providerHandler<[], NetworkInfo>(async (_, __, res) => {
try {
return HttpServer.sendJSON(res, await networkInfoProvider.networkInfo());
} catch (error) {
logger.error(error);
return HttpServer.sendJSON(res, new ProviderError(ProviderFailure.Unhealthy, error), 500);
}
}, logger)
);
return new NetworkInfoHttpService({ logger, networkInfoProvider }, router);
}
}
Expand Up @@ -5,5 +5,6 @@ export enum ProgramOptionDescriptions {
MetricsEnabled = 'Enable Prometheus Metrics',
OgmiosUrl = 'Ogmios URL',
RabbitMQUrl = 'RabbitMQ URL',
UseQueue = 'Enables RabbitMQ'
UseQueue = 'Enables RabbitMQ',
CardanoNodeConfigPath = 'Cardano node config path'
}
1 change: 1 addition & 0 deletions packages/cardano-services/src/Program/ServiceNames.ts
Expand Up @@ -4,5 +4,6 @@
*/
export enum ServiceNames {
StakePoolSearch = 'stake-pool-search',
NetworkInfo = 'network-info',
TxSubmit = 'tx-submit'
}
25 changes: 23 additions & 2 deletions packages/cardano-services/src/Program/loadHttpServer.ts
@@ -1,7 +1,11 @@
/* eslint-disable complexity */

import { DbSyncNetworkInfoProvider } from '../NetworkInfo/DbSyncNetworkInfoProvider/DbSyncNetworkInfoProvider';
import { DbSyncStakePoolSearchProvider, StakePoolSearchHttpService } from '../StakePoolSearch';
import { HttpServer, HttpServerConfig, HttpService } from '../Http';
import { LogLevel, createLogger } from 'bunyan';
import { MissingProgramOption, UnknownServiceName } from './errors';
import { NetworkInfoHttpService } from '../NetworkInfo/NetworkInfoHttpService';
import { Pool } from 'pg';
import { ProgramOptionDescriptions } from './ProgramOptionDescriptions';
import { RabbitMqTxSubmitProvider } from '@cardano-sdk/rabbitmq';
Expand All @@ -11,24 +15,25 @@ import { ogmiosTxSubmitProvider } from '@cardano-sdk/ogmios';

export interface ProgramArgs {
apiUrl: URL;
serviceNames: (ServiceNames.StakePoolSearch | ServiceNames.TxSubmit)[];
serviceNames: (ServiceNames.StakePoolSearch | ServiceNames.TxSubmit | ServiceNames.NetworkInfo)[];
options?: {
dbConnectionString?: string;
loggerMinSeverity?: LogLevel;
metricsEnabled?: boolean;
ogmiosUrl?: URL;
rabbitmqUrl?: URL;
useQueue?: boolean;
cardanoNodeConfigPath?: string;
};
}

// eslint-disable-next-line sonarjs/cognitive-complexity
export const loadHttpServer = async (args: ProgramArgs): Promise<HttpServer> => {
const services: HttpService[] = [];
const logger = createLogger({
level: args.options?.loggerMinSeverity,
name: 'http-server'
});

for (const serviceName of args.serviceNames) {
switch (serviceName) {
case ServiceNames.StakePoolSearch:
Expand All @@ -44,6 +49,22 @@ export const loadHttpServer = async (args: ProgramArgs): Promise<HttpServer> =>
})
);
break;
case ServiceNames.NetworkInfo:
if (args.options?.dbConnectionString === undefined)
throw new MissingProgramOption(ServiceNames.NetworkInfo, ProgramOptionDescriptions.DbConnection);
if (args.options?.cardanoNodeConfigPath === undefined)
throw new MissingProgramOption(ServiceNames.NetworkInfo, ProgramOptionDescriptions.CardanoNodeConfigPath);
services.push(
NetworkInfoHttpService.create({
logger,
networkInfoProvider: new DbSyncNetworkInfoProvider(
args.options?.cardanoNodeConfigPath,
new Pool({ connectionString: args.options.dbConnectionString }),
logger
)
})
);
break;
case ServiceNames.TxSubmit:
services.push(
await TxSubmitHttpService.create({
Expand Down
3 changes: 3 additions & 0 deletions packages/cardano-services/src/run.ts
Expand Up @@ -11,6 +11,7 @@ import onDeath from 'death';

const envSpecs = {
API_URL: envalid.url({ default: API_URL_DEFAULT }),
CARDANO_NODE_CONFIG_PATH: envalid.str({ default: undefined }),
DB_CONNECTION_STRING: envalid.str({ default: undefined }),
LOGGER_MIN_SEVERITY: envalid.str({ choices: loggerMethodNames as string[], default: 'info' }),
OGMIOS_URL: envalid.url({ default: OGMIOS_URL_DEFAULT }),
Expand All @@ -25,13 +26,15 @@ void (async () => {
const apiUrl = new URL(env.API_URL);
const ogmiosUrl = new URL(env.OGMIOS_URL);
const rabbitmqUrl = new URL(env.RABBITMQ_URL);
const cardanoNodeConfigPath = env.CARDANO_NODE_CONFIG_PATH;
const dbConnectionString = env.DB_CONNECTION_STRING ? new URL(env.DB_CONNECTION_STRING).toString() : undefined;
const serviceNames = env.SERVICE_NAMES.split(',') as ServiceNames[];

try {
const server = await loadHttpServer({
apiUrl,
options: {
cardanoNodeConfigPath,
dbConnectionString,
loggerMinSeverity: env.LOGGER_MIN_SEVERITY as LogLevel,
ogmiosUrl,
Expand Down

0 comments on commit 5f5f007

Please sign in to comment.