Skip to content

Commit

Permalink
feat(cactus-api-client): common verifier-factory
Browse files Browse the repository at this point in the history
Prototype for common verifier-factory like the one used in cmd-socketio
apps. More details in issue hyperledger-cacti#1878

Signed-off-by: Michal Bajer <michal.bajer@fujitsu.com>
  • Loading branch information
outSH committed Feb 23, 2022
1 parent 1ae6f72 commit f94ea46
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 3 deletions.
2 changes: 2 additions & 0 deletions packages/cactus-api-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
"@hyperledger/cactus-core": "1.0.0-rc.3",
"@hyperledger/cactus-core-api": "1.0.0-rc.3",
"@hyperledger/cactus-plugin-consortium-manual": "1.0.0-rc.3",
"@hyperledger/cactus-plugin-ledger-connector-besu": "1.0.0-rc.3",
"js-yaml": "3.14.1",
"rxjs": "7.3.0"
},
"devDependencies": {
Expand Down
5 changes: 5 additions & 0 deletions packages/cactus-api-client/src/main/typescript/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ export {
SocketIOApiClientOptions,
} from "./socketio-api-client";
export { Verifier, VerifierEventListener } from "./verifier";
export {
getValidatorApiClient,
VerifierFactory,
VerifierFactoryConfig,
} from "./verifier-factory";
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
const defaultMaxCounterRequestID = 100;
const defaultSyncFunctionTimeoutMillisecond = 5 * 1000; // 5 seconds

import { Logger, Checks } from "@hyperledger/cactus-common";
import { LogLevelDesc, LoggerProvider } from "@hyperledger/cactus-common";
import {
Logger,
Checks,
LogLevelDesc,
LoggerProvider,
} from "@hyperledger/cactus-common";
import { ISocketApiClient } from "@hyperledger/cactus-core-api";

import { Socket, SocketOptions, ManagerOptions, io } from "socket.io-client";
Expand Down
124 changes: 124 additions & 0 deletions packages/cactus-api-client/src/main/typescript/verifier-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2020-2022 Hyperledger Cactus Contributors
* SPDX-License-Identifier: Apache-2.0
*
* verifier-factory.ts
*/

import { Verifier } from "./verifier";

import {
Logger,
LoggerProvider,
LogLevelDesc,
} from "@hyperledger/cactus-common";

import {
SocketIOApiClient,
SocketIOApiClientOptions,
} from "./socketio-api-client";

import {
BesuApiClient,
BesuApiClientOptions,
} from "@hyperledger/cactus-plugin-ledger-connector-besu";
import { ISocketApiClient } from "@hyperledger/cactus-core-api";

// All ApiClients supported by the factory must be added here!
type ClientApiConfig = {
"legacy-socketio": {
in: SocketIOApiClientOptions;
out: SocketIOApiClient;
};
besu: {
in: BesuApiClientOptions;
out: BesuApiClient;
};
};

export function getValidatorApiClient<K extends keyof ClientApiConfig>(
validatorType: K,
options: ClientApiConfig[K]["in"],
): ClientApiConfig[K]["out"] {
switch (validatorType) {
case "legacy-socketio":
return new SocketIOApiClient(options as SocketIOApiClientOptions);
case "besu":
return new BesuApiClient(options as BesuApiClientOptions);
default:
// Will not compile if any ClientApiConfig keys was not handled by this switch
const _exhaustiveCheck: never = validatorType;
return _exhaustiveCheck;
}
}

// Verifier Factory class
export type VerifierFactoryConfig = {
logLevel: LogLevelDesc;
validators: (Record<PropertyKey, unknown> & {
validatorID: string;
clientType: keyof ClientApiConfig;
})[];
};

export class VerifierFactory {
private verifierMap = new Map<string, Verifier<ISocketApiClient<unknown>>>();
private readonly log: Logger;

readonly className: string;

constructor(private readonly verifierConfig: VerifierFactoryConfig) {
this.className = this.constructor.name;
if (
!verifierConfig.validators ||
verifierConfig.validators.some((v) => !v.validatorID) ||
verifierConfig.validators.some((v) => !v.clientType)
) {
throw new Error(
"Invalid VerifierFactory configuration; all validators needs to define at least 'validatorID' and 'clientType'",
);
}

const level: LogLevelDesc = verifierConfig.logLevel || "info";
const label = this.className;
this.log = LoggerProvider.getOrCreate({ level, label });
}

getVerifier<K extends keyof ClientApiConfig>(
validatorId: string,
type?: K,
): Verifier<ClientApiConfig[K]["out"]> {
// Read validator config
const validatorConfig = this.verifierConfig.validators.find(
(v) => v.validatorID === validatorId,
);
if (!validatorConfig) {
throw new Error(
`VerifierFactory - Missing validator (type = ${type}) with ID ${validatorId}`,
);
}

// Assert ClientApi types
if (type && type !== validatorConfig.clientType) {
throw new Error(
`VerifierFactory - Validator ${validatorId} type mismatch; requested=${type}, config=${validatorConfig.clientType}`,
);
}

// Return / create verifier
if (this.verifierMap.has(validatorId)) {
return this.verifierMap.get(validatorId) as Verifier<
ClientApiConfig[K]["out"]
>;
} else {
const clientApi = getValidatorApiClient(
validatorConfig.clientType,
(validatorConfig as unknown) as ClientApiConfig[K]["in"],
);

const verifier = new Verifier(clientApi);
this.verifierMap.set(validatorId, verifier);
return verifier;
}
}
}
48 changes: 48 additions & 0 deletions packages/cactus-api-client/src/main/typescript/verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type BlockTypeFromSocketApi<T> = T extends ISocketApiClient<infer U>
*
* @remarks
* Migrated from cmd-socketio-server for merging the codebases.
*
* @todo Don't throw exception for not supported operations, don't include these methods at all (if possible)
*/
export class Verifier<LedgerApiType extends ISocketApiClient<unknown>> {
private readonly log: Logger;
Expand Down Expand Up @@ -66,6 +68,10 @@ export class Verifier<LedgerApiType extends ISocketApiClient<unknown>> {
eventListener: VerifierEventListener<BlockTypeFromSocketApi<LedgerApiType>>,
monitorOptions?: Record<string, unknown>,
): void {
if (!this.ledgerApi.watchBlocksV1) {
throw new Error("startMonitor not supported on this ledger");
}

if (this.runningMonitors.has(appId)) {
throw new Error(`Monitor with appId '${appId}' is already running!`);
}
Expand Down Expand Up @@ -109,15 +115,57 @@ export class Verifier<LedgerApiType extends ISocketApiClient<unknown>> {
* @param appId - ID of application that requested the monitoring.
*/
stopMonitor(appId: string): void {
if (!this.ledgerApi.watchBlocksV1) {
throw new Error("stopMonitor not supported on this ledger");
}

const watchBlocksSub = this.runningMonitors.get(appId);
if (!watchBlocksSub) {
throw new Error("No monitor running with appId: " + appId);
}
watchBlocksSub.unsubscribe();
this.runningMonitors.delete(appId);

this.log.debug(
"Monitor removed, runningMonitors.size ==",
this.runningMonitors.size,
);
}

/**
* Immediately sends request to the validator, doesn't report any error or responses.
* @param contract - contract to execute on the ledger.
* @param method - function / method to be executed by validator.
* @param args - arguments.
*/
sendAsyncRequest(
contract: Record<string, unknown>,
method: Record<string, unknown>,
args: any,
): void {
if (!this.ledgerApi.sendAsyncRequest) {
throw new Error("stopMonitor not supported on this ledger");
}

return this.ledgerApi.sendAsyncRequest(contract, method, args);
}

/**
* Sends request to be executed on the ledger, watches and reports any error and the response from a ledger.
* @param contract - contract to execute on the ledger.
* @param method - function / method to be executed by validator.
* @param args - arguments.
* @returns Promise that will resolve with response from the ledger, or reject when error occurred.
*/
sendSyncRequest(
contract: Record<string, unknown>,
method: Record<string, unknown>,
args: any,
): Promise<any> {
if (!this.ledgerApi.sendSyncRequest) {
throw new Error("stopMonitor not supported on this ledger");
}

return this.ledgerApi.sendSyncRequest(contract, method, args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ class MockEventListener<T> implements VerifierEventListener<T> {
// Monitoring Tests
//////////////////////////////

test("Using operation not implemented on the ledger throws error", () => {
class EmptyImpl implements ISocketApiClient<string> {}
const apiClient = new EmptyImpl();
const sut = new Verifier(apiClient, sutLogLevel);

// Monitoring
const eventListenerMock = new MockEventListener();
expect(() => sut.startMonitor("someId", eventListenerMock)).toThrowError();
expect(() => sut.stopMonitor("someId")).toThrowError();

// Sending Requests
expect(() => sut.sendSyncRequest({}, {}, "")).toThrowError();
expect(() => sut.sendAsyncRequest({}, {}, "")).toThrowError();
});

describe("Monitoring Tests", () => {
// Assume block data format is string
let apiClientMock: MockApiClient<string>;
Expand Down Expand Up @@ -169,3 +184,44 @@ describe("Monitoring Tests", () => {
expect(mon?.closed).toBeTrue();
});
});

describe("Sending Requests Tests", () => {
let apiClientMock: MockApiClient<unknown>;
let sut: Verifier<MockApiClient<unknown>>;

beforeEach(() => {
apiClientMock = new MockApiClient();
apiClientMock.watchBlocksV1.mockReturnValue(
new Observable(() => log.debug("Mock subscribe called")),
);
sut = new Verifier(apiClientMock, sutLogLevel);
}, setupTimeout);

test("Send async request call proxied to the apiClient", () => {
const inContract = { foo: "bar" };
const inMethod = { func: "a" };
const inArgs = 5;

sut.sendAsyncRequest(inContract, inMethod, inArgs);

expect(apiClientMock.sendAsyncRequest).toBeCalledWith(
inContract,
inMethod,
inArgs,
);
});

test("Send sync request call proxied to the apiClient", () => {
const inContract = { foo: "bar" };
const inMethod = { func: "a" };
const inArgs = 5;

sut.sendSyncRequest(inContract, inMethod, inArgs);

expect(apiClientMock.sendSyncRequest).toBeCalledWith(
inContract,
inMethod,
inArgs,
);
});
});
3 changes: 3 additions & 0 deletions packages/cactus-api-client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
},
{
"path": "../cactus-test-tooling/tsconfig.json"
},
{
"path": "../cactus-plugin-ledger-connector-besu/tsconfig.json"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface ISocketApiClient<BlockType> {
args: any,
): Promise<any>;

watchBlocksV1(
watchBlocksV1?(
monitorOptions?: Record<string, unknown>,
): Observable<BlockType>;
}
Loading

0 comments on commit f94ea46

Please sign in to comment.