Skip to content

Commit

Permalink
fix(cardano-services): tx submit provider init
Browse files Browse the repository at this point in the history
- fix `TxSubmitProvider` not initialized error in SRV mode
- set explicitly a Runnable prototype of the Proxy abstractions
  • Loading branch information
Ivaylo Andonov committed Nov 29, 2022
1 parent 5bdbbc9 commit f88f37f
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 70 deletions.
150 changes: 83 additions & 67 deletions packages/cardano-services/src/Program/services/ogmios.ts
Expand Up @@ -6,8 +6,8 @@ import { DnsResolver } from '../utils';
import { Logger } from 'ts-log';
import { MissingCardanoNodeOption } from '../errors';
import { OgmiosCardanoNode, OgmiosTxSubmitProvider, urlToConnectionConfig } from '@cardano-sdk/ogmios';
import { RunnableModule, isConnectionError } from '@cardano-sdk/util';
import { SubmitTxArgs } from '@cardano-sdk/core';
import { isConnectionError } from '@cardano-sdk/util';

const isCardanoNodeOperation = (prop: string | symbol): prop is 'eraSummaries' | 'systemStart' | 'stakeDistribution' =>
['eraSummaries', 'systemStart', 'stakeDistribution'].includes(prop as string);
Expand Down Expand Up @@ -58,40 +58,43 @@ export const ogmiosTxSubmitProviderWithDiscovery = async (
const { name, port } = await dnsResolver(serviceName!);
let ogmiosProvider = new OgmiosTxSubmitProvider({ host: name, port }, logger);

return new Proxy<OgmiosTxSubmitProvider>({} as OgmiosTxSubmitProvider, {
get(_, prop) {
if (prop === 'then') return;
if (prop === 'initialize') {
return () =>
ogmiosProvider.initialize().catch(async (error) => {
if (isConnectionError(error)) {
ogmiosProvider = await recreateOgmiosTxSubmitProvider(serviceName, ogmiosProvider, dnsResolver, logger);
return await ogmiosProvider.initialize();
}
throw error;
});
}
if (prop === 'submitTx') {
return (submitTxArgs: SubmitTxArgs) =>
ogmiosProvider.submitTx(submitTxArgs).catch(async (error) => {
if (isConnectionError(error)) {
ogmiosProvider = await recreateOgmiosTxSubmitProvider(serviceName, ogmiosProvider, dnsResolver, logger);
await ogmiosProvider.initialize();
await ogmiosProvider.start();
return await ogmiosProvider.submitTx(submitTxArgs);
}
throw error;
});
}
// Bind if it is a function, no intercept operations
if (typeof ogmiosProvider[prop as keyof OgmiosTxSubmitProvider] === 'function') {
const method = ogmiosProvider[prop as keyof OgmiosTxSubmitProvider] as any;
return method.bind(ogmiosProvider);
}
return Object.setPrototypeOf(
new Proxy<OgmiosTxSubmitProvider>({} as OgmiosTxSubmitProvider, {
get(_, prop) {
if (prop === 'then') return;
if (prop === 'initialize') {
return () =>
ogmiosProvider.initialize().catch(async (error) => {
if (isConnectionError(error)) {
ogmiosProvider = await recreateOgmiosTxSubmitProvider(serviceName, ogmiosProvider, dnsResolver, logger);
return await ogmiosProvider.initialize();
}
throw error;
});
}
if (prop === 'submitTx') {
return (submitTxArgs: SubmitTxArgs) =>
ogmiosProvider.submitTx(submitTxArgs).catch(async (error) => {
if (isConnectionError(error)) {
ogmiosProvider = await recreateOgmiosTxSubmitProvider(serviceName, ogmiosProvider, dnsResolver, logger);
await ogmiosProvider.initialize();
await ogmiosProvider.start();
return await ogmiosProvider.submitTx(submitTxArgs);
}
throw error;
});
}
// Bind if it is a function, no intercept operations
if (typeof ogmiosProvider[prop as keyof OgmiosTxSubmitProvider] === 'function') {
const method = ogmiosProvider[prop as keyof OgmiosTxSubmitProvider] as any;
return method.bind(ogmiosProvider);
}

return ogmiosProvider[prop as keyof OgmiosTxSubmitProvider];
}
});
return ogmiosProvider[prop as keyof OgmiosTxSubmitProvider];
}
}),
RunnableModule.prototype
);
};

export const getOgmiosTxSubmitProvider = async (
Expand Down Expand Up @@ -127,40 +130,53 @@ export const ogmiosCardanoNodeWithDiscovery = async (
const { name, port } = await dnsResolver(serviceName!);
let ogmiosCardanoNode = new OgmiosCardanoNode({ host: name, port }, logger);

return new Proxy<OgmiosCardanoNode>({} as OgmiosCardanoNode, {
get(_, prop) {
if (prop === 'then') return;
if (prop === 'initialize') {
return () =>
ogmiosCardanoNode.initialize().catch(async (error) => {
if (isConnectionError(error)) {
ogmiosCardanoNode = await recreateOgmiosCardanoNode(serviceName, ogmiosCardanoNode, dnsResolver, logger);
return await ogmiosCardanoNode.initialize();
}
throw error;
});
}
if (isCardanoNodeOperation(prop)) {
return () =>
ogmiosCardanoNode[prop]().catch(async (error) => {
if (isConnectionError(error)) {
ogmiosCardanoNode = await recreateOgmiosCardanoNode(serviceName, ogmiosCardanoNode, dnsResolver, logger);
await ogmiosCardanoNode.initialize();
await ogmiosCardanoNode.start();
return await ogmiosCardanoNode[prop]();
}
throw error;
});
}
// Bind if it is a function, no intercept operations
if (typeof ogmiosCardanoNode[prop as keyof OgmiosCardanoNode] === 'function') {
const method = ogmiosCardanoNode[prop as keyof OgmiosCardanoNode] as any;
return method.bind(ogmiosCardanoNode);
}
return Object.setPrototypeOf(
new Proxy<OgmiosCardanoNode>({} as OgmiosCardanoNode, {
get(_, prop) {
if (prop === 'then') return;
if (prop === 'initialize') {
return () =>
ogmiosCardanoNode.initialize().catch(async (error) => {
if (isConnectionError(error)) {
ogmiosCardanoNode = await recreateOgmiosCardanoNode(
serviceName,
ogmiosCardanoNode,
dnsResolver,
logger
);
return await ogmiosCardanoNode.initialize();
}
throw error;
});
}
if (isCardanoNodeOperation(prop)) {
return () =>
ogmiosCardanoNode[prop]().catch(async (error) => {
if (isConnectionError(error)) {
ogmiosCardanoNode = await recreateOgmiosCardanoNode(
serviceName,
ogmiosCardanoNode,
dnsResolver,
logger
);
await ogmiosCardanoNode.initialize();
await ogmiosCardanoNode.start();
return await ogmiosCardanoNode[prop]();
}
throw error;
});
}
// Bind if it is a function, no intercept operations
if (typeof ogmiosCardanoNode[prop as keyof OgmiosCardanoNode] === 'function') {
const method = ogmiosCardanoNode[prop as keyof OgmiosCardanoNode] as any;
return method.bind(ogmiosCardanoNode);
}

return ogmiosCardanoNode[prop as keyof OgmiosCardanoNode];
}
});
return ogmiosCardanoNode[prop as keyof OgmiosCardanoNode];
}
}),
RunnableModule.prototype
);
};

export const getOgmiosCardanoNode = async (
Expand Down
28 changes: 26 additions & 2 deletions packages/cardano-services/test/Program/services/ogmios.test.ts
@@ -1,7 +1,7 @@
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable max-len */
import { CardanoNodeErrors, TxSubmitProvider } from '@cardano-sdk/core';
import { CardanoNodeErrors } from '@cardano-sdk/core';
import { Connection } from '@cardano-ogmios/client';
import { DbSyncEpochPollService, listenPromise, serverClosePromise } from '../../../src/util';
import { DbSyncNetworkInfoProvider, NetworkInfoHttpService } from '../../../src/NetworkInfo';
Expand Down Expand Up @@ -52,7 +52,7 @@ describe('Service dependency abstractions', () => {
let apiUrlBase: string;
let ogmiosServer: http.Server;
let ogmiosConnection: Connection;
let txSubmitProvider: TxSubmitProvider;
let txSubmitProvider: OgmiosTxSubmitProvider;
let ogmiosCardanoNode: OgmiosCardanoNode;
let httpServer: HttpServer;
let port: number;
Expand Down Expand Up @@ -92,6 +92,10 @@ describe('Service dependency abstractions', () => {
await httpServer.shutdown();
});

it('txSubmitProvider state should be running when http server has started', () => {
expect(txSubmitProvider.state).toEqual('running');
});

it('txSubmitProvider should be instance of a Proxy ', () => {
expect(types.isProxy(txSubmitProvider)).toEqual(true);
});
Expand All @@ -103,6 +107,15 @@ describe('Service dependency abstractions', () => {
expect(res.status).toBe(200);
expect(res.data).toEqual(healthCheckResponseMock());
});

it('TxSubmitHttpService replies with status 200 OK when /submit endpoint is reached', async () => {
const res = await axios.post(
`${apiUrlBase}/submit`,
{ signedTransaction: bufferToHexString(Buffer.from(new Uint8Array())) },
{ headers: { 'Content-Type': APPLICATION_JSON } }
);
expect(res.status).toBe(200);
});
});

describe('NetworkInfoHttpService', () => {
Expand Down Expand Up @@ -142,6 +155,10 @@ describe('Service dependency abstractions', () => {
await httpServer.shutdown();
});

it('ogmiosCardanoNode state should be running when http server has started', () => {
expect(ogmiosCardanoNode.state).toEqual('running');
});

it('ogmiosCardanoNode should be instance of a Proxy ', () => {
expect(types.isProxy(ogmiosCardanoNode)).toEqual(true);
});
Expand All @@ -153,6 +170,13 @@ describe('Service dependency abstractions', () => {
expect(res.status).toBe(200);
expect(res.data).toEqual(healthCheckResponseMock());
});

it('NetworkInfoHttpService replies with status 200 OK when /stake endpoint is reached', async () => {
const res = await axios.post(`${apiUrlBase}/stake`, undefined, {
headers: { 'Content-Type': APPLICATION_JSON }
});
expect(res.status).toBe(200);
});
});
});
});
Expand Down
6 changes: 5 additions & 1 deletion packages/cardano-services/test/util.ts
Expand Up @@ -21,7 +21,11 @@ export const ogmiosServerReady = (connection: Ogmios.Connection): Promise<void>
export const createHealthyMockOgmiosServer = (submitTxHook?: () => void) =>
createMockOgmiosServer({
healthCheck: { response: { networkSynchronization: 0.999, success: true } },
stateQuery: { eraSummaries: { response: { success: true } }, systemStart: { response: { success: true } } },
stateQuery: {
eraSummaries: { response: { success: true } },
stakeDistribution: { response: { success: true } },
systemStart: { response: { success: true } }
},
submitTx: { response: { success: true } },
submitTxHook
});
Expand Down

0 comments on commit f88f37f

Please sign in to comment.