Skip to content

Commit

Permalink
feat(core-api): add instanceId getter to ICactusPlugin
Browse files Browse the repository at this point in the history
This will enable us to differentiate between instances
of the same plugin class at runtime which is a pre-
requisite for having intra-node routing among plugins
and also for better supporting fine grained dependency
injection at runtime where a certain plugin wants to
import another one and it's not enough to have the
plugin aspect available or even the exact class that
was instantiated.

An example of the above situation would be where you
have multiple different ledger connectors who all need
their own keychain plugins that may be connecting to
different underlying key management solutions because of
some exotic deployment topology where let's say you keep
your keys for your Fabric legder in a Vault instance
but the Corda or Besu related private keys are in the
built-in KMS of your cloud provider of choice. In this
situation you might need two separate instances of the
keychain plugin and the ledger connectors need to have
the ability to import exactly the instance of them that
they need otherwise they'll be talking to the wrong
KMS backend and that is not just a security hole but
but also just breaks everything in the Cactus node.

Lots of files had to be changed for this one simple
change in the interface because it's a new mandatory
method prescribed for every single class that claims
to implement a Cactus plugin. So because of that the
diff is huge but you should be looking at this one
in particular:
./packages/cactus-core-api/src/main/typescript/plugin/i-cactus-plugin.ts

Signed-off-by: Peter Somogyvari <peter.somogyvari@accenture.com>
  • Loading branch information
petermetz committed Dec 1, 2020
1 parent 31be060 commit e50d9ce
Show file tree
Hide file tree
Showing 21 changed files with 175 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,58 @@
import { PluginAspect } from "./plugin-aspect";

/**
* The common interface definiton that plugin classes can use to inherit from
* when defining their own options interface for their constructors.
*
* Not all plugins need to have a unique plugin ID, so if the plugin you are
* working does not, it is okay to provide a dummy implementation, but it is
* not really recommended. Instead the constructor of your plugin should
* generate one automatically at runtime if you definitely do not want the
* caller of your plugin class to have to provide an instanceID.
*
* For example if you have a ledger called `FooBar` then this could be seen as
* a recommended implementation:
*
* ```typescript
*
* interface IPluginLedgerConnectorFooBarOptions extends ICactusPluginOptions {
* logLevel?: LogLevelDesc;
* // your other FooBar specific parameters here
* }
*
* class PluginLedgerConnectorFooBar implements ICactusPlugin {
* constructor(public readonly options: IPluginLedgerConnectorFooBarOptions) {
* // your constructor logic here
* }
*
* public getInstanceId(): string {
* // this works because your {IPluginLedgerConnectorFooBarOptions}
* // inherits from the ICactusPluginOptions interface.
* return this.options.instanceId;
* }
* ```
*
*/
export interface ICactusPluginOptions {
instanceId: string;
}

/**
* This is the common base for all other plugin interface definitions to have as a parent.
*/
export interface ICactusPlugin {
/**
* Returns a string that uniquely identifies the specific instance of a plugin
* from other instances that may have been created from the exact same class.
* We need this to cover scenarios where identical plugin implementations
* need to be used because for example you have two different deployments of
* the same kind of ledger, leading to you needing two separate instances of
* the same kind of plugin in the plugin registry as well.
*
* @see {ICactusPluginOptions} For further details relevant to the instanceId
*/
getInstanceId(): string;

/**
* Returns the NodeJS/npm package name of the plugin which is used to identify
* plugin instances at runtime and differentiate them from other types of plugins.
Expand Down
6 changes: 5 additions & 1 deletion packages/cactus-core-api/src/main/typescript/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export {
} from "./plugin/web-service/i-plugin-web-service";
export { IWebServiceEndpoint } from "./plugin/web-service/i-web-service-endpoint";
export { PluginFactory } from "./plugin/plugin-factory";
export { ICactusPlugin, isICactusPlugin } from "./plugin/i-cactus-plugin";
export {
ICactusPlugin,
ICactusPluginOptions,
isICactusPlugin,
} from "./plugin/i-cactus-plugin";
export { PluginAspect } from "./plugin/plugin-aspect";
export { PluginRegistry } from "./plugin/plugin-registry";
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ import {
PluginRegistry,
IWebServiceEndpoint,
ICactusPlugin,
ICactusPluginOptions,
} from "@hyperledger/cactus-core-api";
import {
Checks,
Logger,
LoggerProvider,
LogLevelDesc,
} from "@hyperledger/cactus-common";
import { GetConsortiumEndpointV1 } from "./consortium/get-consortium-jws-endpoint-v1";
import { GetNodeJwsEndpoint } from "./consortium/get-node-jws-endpoint-v1";
import uuid from "uuid";

export interface IWebAppOptions {
port: number;
hostname: string;
}

export interface IPluginConsortiumManualOptions {
export interface IPluginConsortiumManualOptions extends ICactusPluginOptions {
keyPairPem: string;
consortium: Consortium;
pluginRegistry?: PluginRegistry;
Expand All @@ -37,15 +40,23 @@ export interface IPluginConsortiumManualOptions {
export class PluginConsortiumManual
implements ICactusPlugin, IPluginWebService {
private readonly log: Logger;
private readonly instanceId: string;
private httpServer: Server | SecureServer | null = null;

constructor(public readonly options: IPluginConsortiumManualOptions) {
const fnTag = `PluginConsortiumManual#constructor()`;
if (!options) {
throw new Error(`PluginConsortiumManual#ctor options falsy.`);
throw new Error(`${fnTag} options falsy.`);
}
Checks.truthy(options.instanceId, `${fnTag} options.instanceId`);
this.log = LoggerProvider.getOrCreate({
label: "plugin-consortium-manual",
});
this.instanceId = this.options.instanceId;
}

public getInstanceId(): string {
return this.instanceId;
}

public async shutdown(): Promise<void> {
Expand Down
3 changes: 2 additions & 1 deletion packages/cactus-plugin-keychain-memory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@
},
"homepage": "https://github.com/hyperledger/cactus#readme",
"dependencies": {
"@hyperledger/cactus-common": "^0.2.0",
"@hyperledger/cactus-core-api": "^0.2.0",
"joi": "14.3.1",
"typescript-optional": "2.0.1"
},
"devDependencies": {
"@types/joi": "14.3.4"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { v4 as uuidv4 } from "uuid";

import { PluginFactory } from "@hyperledger/cactus-core-api";
import {
IPluginKeychainOptions,
Expand All @@ -9,7 +11,10 @@ export class PluginFactoryKeychain extends PluginFactory<
IPluginKeychainOptions
> {
async create(
options: IPluginKeychainOptions = { backend: new Map() }
options: IPluginKeychainOptions = {
backend: new Map(),
instanceId: uuidv4(),
}
): Promise<PluginKeychainMemory> {
return new PluginKeychainMemory(options);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import {
ICactusPlugin,
ICactusPluginOptions,
IPluginKeychain,
PluginAspect,
} from "@hyperledger/cactus-core-api";

export interface IPluginKeychainOptions {
import { Checks } from "@hyperledger/cactus-common";

export interface IPluginKeychainOptions extends ICactusPluginOptions {
backend: Map<string, any>;
}

export class PluginKeychainMemory implements ICactusPlugin, IPluginKeychain {
private readonly instanceId: string;

constructor(public readonly options: IPluginKeychainOptions) {
const fnTag = `PluginKeychainMemory#constructor()`;
if (!options) {
throw new Error(`PluginKeychainMemory#ctor options falsy.`);
throw new Error(`${fnTag} options falsy.`);
}
Checks.truthy(options.instanceId, `${fnTag} options.instanceId`);
if (!options.backend) {
options.backend = new Map();
}
this.instanceId = this.options.instanceId;
}

public getInstanceId(): string {
return this.instanceId;
}

public getPackageName(): string {
Expand Down
3 changes: 2 additions & 1 deletion packages/cactus-plugin-kv-storage-memory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@
},
"homepage": "https://github.com/hyperledger/cactus#readme",
"dependencies": {
"@hyperledger/cactus-common": "^0.2.0",
"@hyperledger/cactus-core-api": "^0.2.0",
"joi": "14.3.1",
"typescript-optional": "2.0.1"
},
"devDependencies": {
"@types/joi": "14.3.4"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { v4 as uuidv4 } from "uuid";

import { PluginFactory } from "@hyperledger/cactus-core-api";
import {
IPluginKVStorageOptions,
Expand All @@ -9,7 +11,10 @@ export class PluginFactoryKVStorage extends PluginFactory<
IPluginKVStorageOptions
> {
async create(
options: IPluginKVStorageOptions = { backend: new Map() }
options: IPluginKVStorageOptions = {
backend: new Map(),
instanceId: uuidv4(),
}
): Promise<PluginKVStorageMemory> {
return new PluginKVStorageMemory(options);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import {
ICactusPlugin,
ICactusPluginOptions,
IPluginKVStorage,
PluginAspect,
} from "@hyperledger/cactus-core-api";

export interface IPluginKVStorageOptions {
import { Checks } from "@hyperledger/cactus-common";

export interface IPluginKVStorageOptions extends ICactusPluginOptions {
backend: Map<string, any>;
}

export class PluginKVStorageMemory implements ICactusPlugin, IPluginKVStorage {
private readonly instanceId: string;

constructor(public readonly options: IPluginKVStorageOptions) {
const fnTag = `PluginKVStorageMemory#constructor()`;
if (!options) {
throw new Error(`PluginKVStorageMemory#ctor options falsy.`);
throw new Error(`${fnTag} options falsy.`);
}
Checks.truthy(options.instanceId, `${fnTag} options.instanceId`);
if (!options.backend) {
options.backend = new Map();
}
this.instanceId = this.options.instanceId;
}

public getInstanceId(): string {
return this.instanceId;
}

public getPackageName(): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import {
ICactusPlugin,
ICactusPluginOptions,
IPluginLedgerConnector,
PluginAspect,
} from "@hyperledger/cactus-core-api";
import Web3 from "web3";
import EEAClient, { IWeb3InstanceExtended } from "web3-eea";

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

export interface IPluginLedgerConnectorBesuOptions {
export interface IPluginLedgerConnectorBesuOptions
extends ICactusPluginOptions {
rpcApiHttpHost: string;
logLevel?: LogLevelDesc;
}
Expand Down Expand Up @@ -72,14 +75,17 @@ export class PluginLedgerConnectorBesu
IBesuTransactionIn,
IBesuTransactionOut
> {
private readonly instanceId: string;
private readonly log: Logger;
private readonly web3: Web3;
private readonly web3Eea: IWeb3InstanceExtended;

constructor(public readonly options: IPluginLedgerConnectorBesuOptions) {
const fnTag = `PluginLedgerConnectorBesu#constructor()`;
if (!options) {
throw new Error(`PluginLedgerConnectorBesu#ctor options falsy.`);
throw new Error(`${fnTag} options falsy.`);
}
Checks.truthy(options.instanceId, `${fnTag} options.instanceId`);
const web3Provider = new Web3.providers.HttpProvider(
this.options.rpcApiHttpHost
);
Expand All @@ -89,6 +95,11 @@ export class PluginLedgerConnectorBesu
const level = options.logLevel || "INFO";
const label = "plugin-ledger-connector-besu";
this.log = LoggerProvider.getOrCreate({ level, label });
this.instanceId = this.options.instanceId;
}

public getInstanceId(): string {
return this.instanceId;
}

public getPackageName(): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// tslint:disable-next-line: no-var-requires
const tap = require("tap");
import { v4 as uuidv4 } from "uuid";
import {
PluginLedgerConnectorBesu,
PluginFactoryLedgerConnector,
Expand All @@ -26,6 +27,7 @@ tap.test("deploys contract via .json file", async (assert: any) => {
const factory = new PluginFactoryLedgerConnector();
const connector: PluginLedgerConnectorBesu = await factory.create({
rpcApiHttpHost,
instanceId: uuidv4(),
});

const options = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IPluginWebService,
IWebServiceEndpoint,
ICactusPlugin,
ICactusPluginOptions,
} from "@hyperledger/cactus-core-api";

import {
Expand All @@ -27,7 +28,8 @@ import {
} from "./deploy-contract-go-bin-endpoint-v1";
import { ISigningIdentity } from "./i-fabric-signing-identity";

export interface IPluginLedgerConnectorFabricOptions {
export interface IPluginLedgerConnectorFabricOptions
extends ICactusPluginOptions {
opsApiHttpHost: string;
logLevel?: LogLevelDesc;
webAppOptions?: any;
Expand All @@ -45,6 +47,7 @@ export class PluginLedgerConnectorFabric
IPluginLedgerConnector<any, any, any, any>,
ICactusPlugin,
IPluginWebService {
private readonly instanceId: string;
private readonly log: Logger;

private httpServer: Server | SecureServer | undefined;
Expand All @@ -53,6 +56,9 @@ export class PluginLedgerConnectorFabric
const fnTag = "PluginLedgerConnectorFabric#constructor()";

Checks.truthy(options, `${fnTag} arg options`);
Checks.truthy(options.instanceId, `${fnTag} options.instanceId`);

this.instanceId = options.instanceId;

const level = this.options.logLevel || "INFO";
const label = "plugin-ledger-connector-fabric";
Expand All @@ -66,6 +72,10 @@ export class PluginLedgerConnectorFabric
throw new Error("Method not implemented.");
}

public getInstanceId(): string {
return this.instanceId;
}

public getPackageName(): string {
return `@hyperledger/cactus-plugin-ledger-connectur-fabric`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AddressInfo } from "net";
import path from "path";

import test, { Test } from "tape";
import { v4 as uuidv4 } from "uuid";

import axios, { AxiosRequestConfig } from "axios";
import FormData from "form-data";
Expand Down Expand Up @@ -39,6 +40,7 @@ test("deploys contract from go source", async (t: Test) => {
const adminSigningIdentity = await ledger.getAdminSigningIdentity();

const pluginOpts: IPluginLedgerConnectorFabricOptions = {
instanceId: uuidv4(),
opsApiHttpHost,
connectionProfile,
adminSigningIdentity,
Expand Down
Loading

0 comments on commit e50d9ce

Please sign in to comment.