Skip to content

Commit d4af647

Browse files
AzaharaCpetermetz
authored andcommitted
feat(corda): resolves #888
Signed-off-by: AzaharaC <[email protected]>
1 parent 1eb811a commit d4af647

File tree

3 files changed

+809
-2
lines changed

3 files changed

+809
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
import test, { Test } from "tape-promise/tape";
2+
import { v4 as internalIpV4 } from "internal-ip";
3+
import { v4 as uuidv4 } from "uuid";
4+
import http from "http";
5+
import bodyParser from "body-parser";
6+
import express from "express";
7+
import { AddressInfo } from "net";
8+
9+
import { Containers, CordaTestLedger } from "@hyperledger/cactus-test-tooling";
10+
import {
11+
LogLevelDesc,
12+
IListenOptions,
13+
Servers,
14+
} from "@hyperledger/cactus-common";
15+
import {
16+
SampleCordappEnum,
17+
CordaConnectorContainer,
18+
} from "@hyperledger/cactus-test-tooling";
19+
20+
import {
21+
CordappDeploymentConfig,
22+
DefaultApi as CordaApi,
23+
DeployContractJarsV1Request,
24+
FlowInvocationType,
25+
InvokeContractV1Request,
26+
JvmTypeKind,
27+
} from "../../../main/typescript/generated/openapi/typescript-axios/index";
28+
import { Configuration } from "@hyperledger/cactus-core-api";
29+
30+
import {
31+
IPluginLedgerConnectorCordaOptions,
32+
PluginLedgerConnectorCorda,
33+
} from "../../../main/typescript/plugin-ledger-connector-corda";
34+
import { K_CACTUS_CORDA_TOTAL_TX_COUNT } from "../../../main/typescript/prometheus-exporter/metrics";
35+
36+
const logLevel: LogLevelDesc = "TRACE";
37+
38+
test("Tests are passing on the JVM side", async (t: Test) => {
39+
test.onFailure(async () => {
40+
await Containers.logDiagnostics({ logLevel });
41+
});
42+
43+
const ledger = new CordaTestLedger({
44+
imageName: "ghcr.io/hyperledger/cactus-corda-4-7-all-in-one-obligation",
45+
imageVersion: "2021-08-19--feat-888",
46+
logLevel,
47+
});
48+
t.ok(ledger, "CordaTestLedger instantaited OK");
49+
50+
test.onFinish(async () => {
51+
await ledger.stop();
52+
await ledger.destroy();
53+
});
54+
const ledgerContainer = await ledger.start();
55+
t.ok(ledgerContainer, "CordaTestLedger container truthy post-start() OK");
56+
57+
const corDappsDirPartyA = await ledger.getCorDappsDirPartyA();
58+
const corDappsDirPartyB = await ledger.getCorDappsDirPartyB();
59+
t.comment(`corDappsDirPartyA=${corDappsDirPartyA}`);
60+
t.comment(`corDappsDirPartyB=${corDappsDirPartyB}`);
61+
62+
await ledger.logDebugPorts();
63+
const partyARpcPort = await ledger.getRpcAPublicPort();
64+
const partyBRpcPort = await ledger.getRpcBPublicPort();
65+
66+
const jarFiles = await ledger.pullCordappJars(
67+
SampleCordappEnum.BASIC_CORDAPP,
68+
);
69+
t.comment(`Fetched ${jarFiles.length} cordapp jars OK`);
70+
71+
const internalIpOrUndefined = await internalIpV4();
72+
t.ok(internalIpOrUndefined, "Determined LAN IPv4 address successfully OK");
73+
const internalIp = internalIpOrUndefined as string;
74+
t.comment(`Internal IP (based on default gateway): ${internalIp}`);
75+
76+
// TODO: parse the gradle build files to extract the credentials?
77+
const partyARpcUsername = "user1";
78+
const partyARpcPassword = "password";
79+
const partyBRpcUsername = partyARpcUsername;
80+
const partyBRpcPassword = partyARpcPassword;
81+
const springAppConfig = {
82+
logging: {
83+
level: {
84+
root: "INFO",
85+
"net.corda": "INFO",
86+
"org.hyperledger.cactus": "DEBUG",
87+
},
88+
},
89+
cactus: {
90+
corda: {
91+
node: { host: internalIp },
92+
rpc: {
93+
port: partyARpcPort,
94+
username: partyARpcUsername,
95+
password: partyARpcPassword,
96+
},
97+
},
98+
},
99+
};
100+
const springApplicationJson = JSON.stringify(springAppConfig);
101+
const envVarSpringAppJson = `SPRING_APPLICATION_JSON=${springApplicationJson}`;
102+
t.comment(envVarSpringAppJson);
103+
104+
const connector = new CordaConnectorContainer({
105+
logLevel,
106+
imageName: "ghcr.io/hyperledger/cactus-connector-corda-server",
107+
imageVersion: "2021-03-25-feat-622",
108+
envVars: [envVarSpringAppJson],
109+
});
110+
t.ok(CordaConnectorContainer, "CordaConnectorContainer instantiated OK");
111+
112+
test.onFinish(async () => {
113+
try {
114+
await connector.stop();
115+
} finally {
116+
await connector.destroy();
117+
}
118+
});
119+
120+
const connectorContainer = await connector.start();
121+
t.ok(connectorContainer, "CordaConnectorContainer started OK");
122+
123+
await connector.logDebugPorts();
124+
const apiUrl = await connector.getApiLocalhostUrl();
125+
126+
const config = new Configuration({ basePath: apiUrl });
127+
const apiClient = new CordaApi(config);
128+
129+
const flowsRes1 = await apiClient.listFlowsV1();
130+
t.ok(flowsRes1.status === 200, "flowsRes1.status === 200 OK");
131+
t.ok(flowsRes1.data, "flowsRes1.data truthy OK");
132+
t.ok(flowsRes1.data.flowNames, "flowsRes1.data.flowNames truthy OK");
133+
t.comment(`apiClient.listFlowsV1() => ${JSON.stringify(flowsRes1.data)}`);
134+
const flowNamesPreDeploy = flowsRes1.data.flowNames;
135+
136+
const sshConfig = await ledger.getSshConfig();
137+
const hostKeyEntry = "not-used-right-now-so-this-does-not-matter... ;-(";
138+
139+
const cdcA: CordappDeploymentConfig = {
140+
cordappDir: corDappsDirPartyA,
141+
cordaNodeStartCmd: "supervisorctl start corda-a",
142+
cordaJarPath:
143+
"/samples-kotlin/Advanced/obligation-cordapp/build/nodes/ParticipantA/corda.jar",
144+
nodeBaseDirPath:
145+
"/samples-kotlin/Advanced/obligation-cordapp/build/nodes/ParticipantA/",
146+
rpcCredentials: {
147+
hostname: internalIp,
148+
port: partyARpcPort,
149+
username: partyARpcUsername,
150+
password: partyARpcPassword,
151+
},
152+
sshCredentials: {
153+
hostKeyEntry,
154+
hostname: internalIp,
155+
password: "root",
156+
port: sshConfig.port as number,
157+
username: sshConfig.username as string,
158+
},
159+
};
160+
161+
const cdcB: CordappDeploymentConfig = {
162+
cordappDir: corDappsDirPartyB,
163+
cordaNodeStartCmd: "supervisorctl start corda-b",
164+
cordaJarPath:
165+
"/samples-kotlin/Advanced/obligation-cordapp/build/nodes/ParticipantB/corda.jar",
166+
nodeBaseDirPath:
167+
"/samples-kotlin/Advanced/obligation-cordapp/build/nodes/ParticipantB/",
168+
rpcCredentials: {
169+
hostname: internalIp,
170+
port: partyBRpcPort,
171+
username: partyBRpcUsername,
172+
password: partyBRpcPassword,
173+
},
174+
sshCredentials: {
175+
hostKeyEntry,
176+
hostname: internalIp,
177+
password: "root",
178+
port: sshConfig.port as number,
179+
username: sshConfig.username as string,
180+
},
181+
};
182+
183+
const cordappDeploymentConfigs: CordappDeploymentConfig[] = [cdcA, cdcB];
184+
const depReq: DeployContractJarsV1Request = {
185+
jarFiles,
186+
cordappDeploymentConfigs,
187+
};
188+
const depRes = await apiClient.deployContractJarsV1(depReq);
189+
t.ok(depRes, "Jar deployment response truthy OK");
190+
t.equal(depRes.status, 200, "Jar deployment status code === 200 OK");
191+
t.ok(depRes.data, "Jar deployment response body truthy OK");
192+
t.ok(depRes.data.deployedJarFiles, "Jar deployment body deployedJarFiles OK");
193+
t.equal(
194+
depRes.data.deployedJarFiles.length,
195+
jarFiles.length,
196+
"Deployed jar file count equals count in request OK",
197+
);
198+
199+
const flowsRes2 = await apiClient.listFlowsV1();
200+
t.ok(flowsRes2.status === 200, "flowsRes2.status === 200 OK");
201+
t.comment(`apiClient.listFlowsV1() => ${JSON.stringify(flowsRes2.data)}`);
202+
t.ok(flowsRes2.data, "flowsRes2.data truthy OK");
203+
t.ok(flowsRes2.data.flowNames, "flowsRes2.data.flowNames truthy OK");
204+
const flowNamesPostDeploy = flowsRes2.data.flowNames;
205+
t.notDeepLooseEqual(
206+
flowNamesPostDeploy,
207+
flowNamesPreDeploy,
208+
"New flows detected post Cordapp Jar deployment OK",
209+
);
210+
211+
// let's see if this makes a difference and if yes, then we know that the issue
212+
// is a race condition for sure
213+
// await new Promise((r) => setTimeout(r, 120000));
214+
t.comment("Fetching network map for Corda network...");
215+
const networkMapRes = await apiClient.networkMapV1();
216+
t.ok(networkMapRes, "networkMapRes truthy OK");
217+
t.ok(networkMapRes.status, "networkMapRes.status truthy OK");
218+
t.ok(networkMapRes.data, "networkMapRes.data truthy OK");
219+
t.true(Array.isArray(networkMapRes.data), "networkMapRes.data isArray OK");
220+
t.true(networkMapRes.data.length > 0, "networkMapRes.data not empty OK");
221+
222+
const partyB = networkMapRes.data.find((it) =>
223+
it.legalIdentities.some((it2) => it2.name.organisation === "ParticipantB"),
224+
);
225+
const partyBPublicKey = partyB?.legalIdentities[0].owningKey;
226+
227+
const req: InvokeContractV1Request = ({
228+
timeoutMs: 60000,
229+
flowFullClassName: "net.corda.samples.example.flows.ExampleFlow$Initiator",
230+
flowInvocationType: FlowInvocationType.FlowDynamic,
231+
params: [
232+
{
233+
jvmTypeKind: JvmTypeKind.Primitive,
234+
jvmType: {
235+
fqClassName: "java.lang.Integer",
236+
},
237+
primitiveValue: 42,
238+
},
239+
{
240+
jvmTypeKind: JvmTypeKind.Reference,
241+
jvmType: {
242+
fqClassName: "net.corda.core.identity.Party",
243+
},
244+
jvmCtorArgs: [
245+
{
246+
jvmTypeKind: JvmTypeKind.Reference,
247+
jvmType: {
248+
fqClassName: "net.corda.core.identity.CordaX500Name",
249+
},
250+
jvmCtorArgs: [
251+
{
252+
jvmTypeKind: JvmTypeKind.Primitive,
253+
jvmType: {
254+
fqClassName: "java.lang.String",
255+
},
256+
primitiveValue: "ParticipantB",
257+
},
258+
{
259+
jvmTypeKind: JvmTypeKind.Primitive,
260+
jvmType: {
261+
fqClassName: "java.lang.String",
262+
},
263+
primitiveValue: "New York",
264+
},
265+
{
266+
jvmTypeKind: JvmTypeKind.Primitive,
267+
jvmType: {
268+
fqClassName: "java.lang.String",
269+
},
270+
primitiveValue: "US",
271+
},
272+
],
273+
},
274+
{
275+
jvmTypeKind: JvmTypeKind.Reference,
276+
jvmType: {
277+
fqClassName:
278+
"org.hyperledger.cactus.plugin.ledger.connector.corda.server.impl.PublicKeyImpl",
279+
},
280+
jvmCtorArgs: [
281+
{
282+
jvmTypeKind: JvmTypeKind.Primitive,
283+
jvmType: {
284+
fqClassName: "java.lang.String",
285+
},
286+
primitiveValue: partyBPublicKey?.algorithm,
287+
},
288+
{
289+
jvmTypeKind: JvmTypeKind.Primitive,
290+
jvmType: {
291+
fqClassName: "java.lang.String",
292+
},
293+
primitiveValue: partyBPublicKey?.format,
294+
},
295+
{
296+
jvmTypeKind: JvmTypeKind.Primitive,
297+
jvmType: {
298+
fqClassName: "java.lang.String",
299+
},
300+
primitiveValue: partyBPublicKey?.encoded,
301+
},
302+
],
303+
},
304+
],
305+
},
306+
],
307+
} as unknown) as InvokeContractV1Request;
308+
309+
const res = await apiClient.invokeContractV1(req);
310+
t.ok(res, "InvokeContractV1Request truthy OK");
311+
t.equal(res.status, 200, "InvokeContractV1Request status code === 200 OK");
312+
313+
const pluginOptions: IPluginLedgerConnectorCordaOptions = {
314+
instanceId: uuidv4(),
315+
corDappsDir: corDappsDirPartyA,
316+
sshConfigAdminShell: sshConfig,
317+
};
318+
319+
const plugin = new PluginLedgerConnectorCorda(pluginOptions);
320+
321+
const expressApp = express();
322+
expressApp.use(bodyParser.json({ limit: "250mb" }));
323+
const server = http.createServer(expressApp);
324+
const listenOptions: IListenOptions = {
325+
hostname: "0.0.0.0",
326+
port: 0,
327+
server,
328+
};
329+
const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo;
330+
test.onFinish(async () => await Servers.shutdown(server));
331+
const { address, port } = addressInfo;
332+
const apiHost = `http://${address}:${port}`;
333+
t.comment(
334+
`Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/get-prometheus-exporter-metrics`,
335+
);
336+
337+
const apiConfig = new Configuration({ basePath: apiHost });
338+
const apiClient1 = new CordaApi(apiConfig);
339+
340+
await plugin.getOrCreateWebServices();
341+
await plugin.registerWebServices(expressApp);
342+
343+
{
344+
plugin.transact();
345+
const promRes = await apiClient1.getPrometheusMetricsV1();
346+
const promMetricsOutput =
347+
"# HELP " +
348+
K_CACTUS_CORDA_TOTAL_TX_COUNT +
349+
" Total transactions executed\n" +
350+
"# TYPE " +
351+
K_CACTUS_CORDA_TOTAL_TX_COUNT +
352+
" gauge\n" +
353+
K_CACTUS_CORDA_TOTAL_TX_COUNT +
354+
'{type="' +
355+
K_CACTUS_CORDA_TOTAL_TX_COUNT +
356+
'"} 1';
357+
t.ok(promRes);
358+
t.ok(promRes.data);
359+
t.equal(promRes.status, 200);
360+
t.true(
361+
promRes.data.includes(promMetricsOutput),
362+
"Total Transaction Count of 1 recorded as expected. RESULT OK",
363+
);
364+
365+
// Executing transaction to increment the Total transaction count metrics
366+
plugin.transact();
367+
368+
const promRes1 = await apiClient1.getPrometheusMetricsV1();
369+
const promMetricsOutput1 =
370+
"# HELP " +
371+
K_CACTUS_CORDA_TOTAL_TX_COUNT +
372+
" Total transactions executed\n" +
373+
"# TYPE " +
374+
K_CACTUS_CORDA_TOTAL_TX_COUNT +
375+
" gauge\n" +
376+
K_CACTUS_CORDA_TOTAL_TX_COUNT +
377+
'{type="' +
378+
K_CACTUS_CORDA_TOTAL_TX_COUNT +
379+
'"} 2';
380+
t.ok(promRes1);
381+
t.ok(promRes1.data);
382+
t.equal(promRes1.status, 200);
383+
t.true(
384+
promRes1.data.includes(promMetricsOutput1),
385+
"Total Transaction Count of 2 recorded as expected. RESULT OK",
386+
);
387+
}
388+
389+
t.end();
390+
});

0 commit comments

Comments
 (0)