Skip to content

Commit af28daa

Browse files
committed
feat(quorum): private transaction support
fixes #951 Signed-off-by: Travis Payne <[email protected]> Signed-off-by: Peter Somogyvari <[email protected]>
1 parent a9ce910 commit af28daa

File tree

36 files changed

+833
-7
lines changed

36 files changed

+833
-7
lines changed

examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ export class SupplyChainAppDummyInfrastructure {
183183
const pluginRegistry = new PluginRegistry();
184184
pluginRegistry.add(this.keychain);
185185
const connector = new PluginLedgerConnectorQuorum({
186+
privateUrl: rpcApiHttpHost,
186187
instanceId: "PluginLedgerConnectorQuorum_Contract_Deployment",
187188
rpcApiHttpHost,
188189
logLevel: this.options.logLevel,

examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts

+1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ export class SupplyChainApp {
275275
});
276276

277277
const quorumConnector = new PluginLedgerConnectorQuorum({
278+
privateUrl: rpcApiHostB,
278279
instanceId: "PluginLedgerConnectorQuorum_B",
279280
rpcApiHttpHost: rpcApiHostB,
280281
logLevel: this.options.logLevel,

packages/cactus-plugin-ledger-connector-quorum/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@
7777
"prom-client": "13.2.0",
7878
"typescript-optional": "2.0.1",
7979
"web3": "1.5.2",
80-
"web3-eth-contract": "1.5.2"
80+
"web3-eth-contract": "1.5.2",
81+
"web3js-quorum": "21.7.0-rc1"
8182
},
8283
"devDependencies": {
8384
"@hyperledger/cactus-plugin-keychain-memory": "1.0.0-rc.3",

packages/cactus-plugin-ledger-connector-quorum/src/main/json/openapi.json

+37-1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,25 @@
292292
}
293293
}
294294
},
295+
"QuorumPrivateTransactionConfig" : {
296+
"type": "object",
297+
"required" : [
298+
"privateFor"
299+
],
300+
"properties" : {
301+
"privateFrom": {
302+
"type": "string",
303+
"nullable": false
304+
},
305+
"privateFor": {
306+
"type": "array",
307+
"default": [],
308+
"items": {},
309+
"nullable": false
310+
}
311+
312+
}
313+
},
295314
"Web3TransactionReceipt": {
296315
"type": "object",
297316
"required": [
@@ -427,6 +446,9 @@
427446
"minimum": 0,
428447
"default": 60000,
429448
"nullable": false
449+
},
450+
"privateTransactionConfig": {
451+
"$ref": "#/components/schemas/QuorumPrivateTransactionConfig"
430452
}
431453
}
432454
},
@@ -473,7 +495,15 @@
473495
"nullable": false
474496
},
475497
"gasPrice": {
476-
"type": "string",
498+
"type": "number",
499+
"nullable": false
500+
},
501+
"nonce": {
502+
"type": "number",
503+
"nullable": false
504+
},
505+
"value": {
506+
"type": "number",
477507
"nullable": false
478508
},
479509
"timeoutMs": {
@@ -493,6 +523,9 @@
493523
"type": "array",
494524
"default": [],
495525
"items": {}
526+
},
527+
"privateTransactionConfig": {
528+
"$ref": "#/components/schemas/QuorumPrivateTransactionConfig"
496529
}
497530
}
498531
},
@@ -717,6 +750,9 @@
717750
"$ref": "#/components/schemas/ContractJSON",
718751
"description": "For use when not using keychain, pass the contract in as this variable",
719752
"nullable": false
753+
},
754+
"privateTransactionConfig": {
755+
"$ref": "#/components/schemas/QuorumPrivateTransactionConfig"
720756
}
721757
}
722758
},

packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/generated/openapi/typescript-axios/api.ts

+51-2
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,22 @@ export interface DeployContractSolidityBytecodeV1Request {
183183
gas?: number;
184184
/**
185185
*
186-
* @type {string}
186+
* @type {number}
187187
* @memberof DeployContractSolidityBytecodeV1Request
188188
*/
189-
gasPrice?: string;
189+
gasPrice?: number;
190+
/**
191+
*
192+
* @type {number}
193+
* @memberof DeployContractSolidityBytecodeV1Request
194+
*/
195+
nonce?: number;
196+
/**
197+
*
198+
* @type {number}
199+
* @memberof DeployContractSolidityBytecodeV1Request
200+
*/
201+
value?: number;
190202
/**
191203
* The amount of milliseconds to wait for a transaction receipt with theaddress of the contract(which indicates successful deployment) beforegiving up and crashing.
192204
* @type {number}
@@ -205,6 +217,12 @@ export interface DeployContractSolidityBytecodeV1Request {
205217
* @memberof DeployContractSolidityBytecodeV1Request
206218
*/
207219
constructorArgs?: Array<any>;
220+
/**
221+
*
222+
* @type {QuorumPrivateTransactionConfig}
223+
* @memberof DeployContractSolidityBytecodeV1Request
224+
*/
225+
privateTransactionConfig?: QuorumPrivateTransactionConfig;
208226
}
209227
/**
210228
*
@@ -302,6 +320,12 @@ export interface InvokeContractJsonObjectV1Request {
302320
* @memberof InvokeContractJsonObjectV1Request
303321
*/
304322
contractJSON: ContractJSON;
323+
/**
324+
*
325+
* @type {QuorumPrivateTransactionConfig}
326+
* @memberof InvokeContractJsonObjectV1Request
327+
*/
328+
privateTransactionConfig?: QuorumPrivateTransactionConfig;
305329
}
306330
/**
307331
*
@@ -401,6 +425,25 @@ export interface InvokeContractV1Response {
401425
*/
402426
success: boolean;
403427
}
428+
/**
429+
*
430+
* @export
431+
* @interface QuorumPrivateTransactionConfig
432+
*/
433+
export interface QuorumPrivateTransactionConfig {
434+
/**
435+
*
436+
* @type {string}
437+
* @memberof QuorumPrivateTransactionConfig
438+
*/
439+
privateFrom?: string;
440+
/**
441+
*
442+
* @type {Array<any>}
443+
* @memberof QuorumPrivateTransactionConfig
444+
*/
445+
privateFor: Array<any>;
446+
}
404447
/**
405448
*
406449
* @export
@@ -482,6 +525,12 @@ export interface RunTransactionRequest {
482525
* @memberof RunTransactionRequest
483526
*/
484527
timeoutMs?: number;
528+
/**
529+
*
530+
* @type {QuorumPrivateTransactionConfig}
531+
* @memberof RunTransactionRequest
532+
*/
533+
privateTransactionConfig?: QuorumPrivateTransactionConfig;
485534
}
486535
/**
487536
*

packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts

+70-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Server as SecureServer } from "https";
33

44
import { Express } from "express";
55
import Web3 from "web3";
6+
import Web3JsQuorum from "web3js-quorum";
7+
68
// The strange way of obtaining the contract class here is like this because
79
// web3-eth internally sub-classes the Contract class at runtime
810
// @see https://stackoverflow.com/a/63639280/698470
@@ -66,6 +68,7 @@ import {
6668
export interface IPluginLedgerConnectorQuorumOptions
6769
extends ICactusPluginOptions {
6870
rpcApiHttpHost: string;
71+
privateUrl: string;
6972
logLevel?: LogLevelDesc;
7073
prometheusExporter?: PrometheusExporter;
7174
pluginRegistry: PluginRegistry;
@@ -84,8 +87,10 @@ export class PluginLedgerConnectorQuorum
8487
private readonly pluginRegistry: PluginRegistry;
8588
public prometheusExporter: PrometheusExporter;
8689
private readonly instanceId: string;
90+
private readonly privateUrl: string;
8791
private readonly log: Logger;
8892
private readonly web3: Web3;
93+
private readonly web3JsQuorum: any;
8994
private httpServer: Server | SecureServer | null = null;
9095

9196
private endpoints: IWebServiceEndpoint[] | undefined;
@@ -106,10 +111,18 @@ export class PluginLedgerConnectorQuorum
106111
const label = this.className;
107112
this.log = LoggerProvider.getOrCreate({ level, label });
108113

114+
this.privateUrl = options.privateUrl;
115+
109116
const web3Provider = new Web3.providers.HttpProvider(
110117
this.options.rpcApiHttpHost,
111118
);
112119
this.web3 = new Web3(web3Provider);
120+
this.web3JsQuorum = Web3JsQuorum(
121+
this.web3,
122+
{ privateUrl: this.privateUrl },
123+
true,
124+
);
125+
113126
this.instanceId = options.instanceId;
114127
this.pluginRegistry = options.pluginRegistry as PluginRegistry;
115128
this.prometheusExporter =
@@ -337,6 +350,7 @@ export class PluginLedgerConnectorQuorum
337350
const txReq: RunTransactionRequest = {
338351
transactionConfig,
339352
web3SigningCredential,
353+
privateTransactionConfig: req.privateTransactionConfig,
340354
timeoutMs: req.timeoutMs || 60000,
341355
};
342356
const out = await this.transact(txReq);
@@ -411,6 +425,11 @@ export class PluginLedgerConnectorQuorum
411425
const {
412426
secret,
413427
} = web3SigningCredential as Web3SigningCredentialGethKeychainPassword;
428+
429+
if (txIn.privateTransactionConfig) {
430+
return this.transactPrivate(txIn);
431+
}
432+
414433
try {
415434
const txHash = await sendTransaction(transactionConfig, secret);
416435
const transactionReceipt = await this.pollForTxReceipt(txHash);
@@ -432,6 +451,10 @@ export class PluginLedgerConnectorQuorum
432451
secret,
433452
} = web3SigningCredential as Web3SigningCredentialPrivateKeyHex;
434453

454+
if (req.privateTransactionConfig) {
455+
return this.transactPrivate(req);
456+
}
457+
435458
const signedTx = await this.web3.eth.accounts.signTransaction(
436459
transactionConfig,
437460
secret,
@@ -447,11 +470,56 @@ export class PluginLedgerConnectorQuorum
447470
}
448471
}
449472

473+
public async transactPrivate(
474+
req: RunTransactionRequest,
475+
): Promise<RunTransactionResponse> {
476+
const { web3SigningCredential } = req;
477+
const {
478+
secret,
479+
} = web3SigningCredential as Web3SigningCredentialPrivateKeyHex;
480+
481+
const address = JSON.parse(req.transactionConfig.from as string).address;
482+
483+
const transactionReceipt = await this.web3JsQuorum.eth.sendTransaction({
484+
from: address,
485+
to: req.transactionConfig.to,
486+
value: req.transactionConfig.value,
487+
gasLimit: req.transactionConfig.gas,
488+
gasPrice: req.transactionConfig.gasPrice,
489+
data: req.transactionConfig.data?.startsWith("0x")
490+
? req.transactionConfig.data.slice(2)
491+
: req.transactionConfig.data,
492+
nonce: req.transactionConfig.nonce,
493+
privateFrom: req.privateTransactionConfig?.privateFrom,
494+
privateFor: req.privateTransactionConfig?.privateFor,
495+
privateKey: secret,
496+
isPrivate: true,
497+
});
498+
499+
return { transactionReceipt };
500+
}
501+
502+
public async getPrivateTxReceipt(
503+
privateFrom: string,
504+
txHash: string,
505+
): Promise<RunTransactionResponse> {
506+
const txPoolReceipt = {} as any;
507+
508+
console.log(privateFrom);
509+
console.log(txHash);
510+
511+
return { transactionReceipt: txPoolReceipt };
512+
}
513+
450514
public async transactCactusKeychainRef(
451515
req: RunTransactionRequest,
452516
): Promise<RunTransactionResponse> {
453517
const fnTag = `${this.className}#transactCactusKeychainRef()`;
454-
const { transactionConfig, web3SigningCredential } = req;
518+
const {
519+
transactionConfig,
520+
web3SigningCredential,
521+
privateTransactionConfig,
522+
} = req;
455523
const {
456524
ethAccount,
457525
keychainEntryKey,
@@ -484,6 +552,7 @@ export class PluginLedgerConnectorQuorum
484552
}
485553

486554
return this.transactPrivateKey({
555+
privateTransactionConfig,
487556
transactionConfig,
488557
web3SigningCredential: {
489558
ethAccount,

packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/invoke-contract-endpoint.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { registerWebServiceEndpoint } from "@hyperledger/cactus-core";
1717
import { PluginLedgerConnectorQuorum } from "../plugin-ledger-connector-quorum";
1818

1919
import OAS from "../../json/openapi.json";
20+
import { InvokeContractV1Request } from "../generated/openapi/typescript-axios/api";
2021

2122
export interface IInvokeContractEndpointOptions {
2223
logLevel?: LogLevelDesc;
@@ -84,7 +85,7 @@ export class InvokeContractEndpoint implements IWebServiceEndpoint {
8485
public async handleRequest(req: Request, res: Response): Promise<void> {
8586
const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`;
8687
this.log.debug(reqTag);
87-
const reqBody = req.body;
88+
const reqBody: InvokeContractV1Request = req.body;
8889
try {
8990
const resBody = await this.options.connector.getContractInfoKeychain(
9091
reqBody,

packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/run-transaction-endpoint.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { registerWebServiceEndpoint } from "@hyperledger/cactus-core";
1717
import { PluginLedgerConnectorQuorum } from "../plugin-ledger-connector-quorum";
1818

1919
import OAS from "../../json/openapi.json";
20+
import { RunTransactionRequest } from "../generated/openapi/typescript-axios/api";
2021

2122
export interface IRunTransactionEndpointOptions {
2223
logLevel?: LogLevelDesc;
@@ -84,7 +85,7 @@ export class RunTransactionEndpoint implements IWebServiceEndpoint {
8485
public async handleRequest(req: Request, res: Response): Promise<void> {
8586
const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`;
8687
this.log.debug(reqTag);
87-
const reqBody = req.body;
88+
const reqBody: RunTransactionRequest = req.body;
8889
try {
8990
const resBody = await this.options.connector.transact(reqBody);
9091
res.json({ success: true, data: resBody });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"address":"f0e2db6c8dc6c681bb5d6ad121a107f300e9b2b5","crypto":{"cipher":"aes-128-ctr","ciphertext":"f2af258ee3733513333652be19197ae7eace4b5e79a346cf25b02a857e6043f3","cipherparams":{"iv":"587d7faaa6403b8a73273d0ad58dd71f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"b93c7d69c5bb0a760c3b7fdf791c47896a552c5c977648b392a24d708674dcf3"},"mac":"d83bcb555c92fc5a32ceacabbb6b99f59515ec3986b9fe5995c67e027bd750c8"},"id":"5392d73f-08dd-42b8-bca9-6f6d35c419d9","version":3}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0x13a52aab892e1322e8b52506276363d4754c122e
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
b9a4bd1539c15bcc83fa9078fe89200b6e9e802ae992f13cd83c853f16e8bed4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
98496800174b3c73ae33cba59f8f5e686cd488f7897c2edb52e2cf46383d75cd03dbb58dde07185bc0953f98800ca9a89f4b5ef450c5e51292ea08ec6130ee0c
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"data":{"bytes":"Wl+xSyXVuuqzpvznOS7dOobhcn4C5auxkFRi7yLtgtA="},"type":"unlocked"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BULeR8JyUWhiuuCMU/HLA0Q5pzkYT+cHII3ZKBey3Bo=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"data":{"bytes":"wGEar7J9G0JAgdisp61ZChyrJWeW2QPyKvecjjeVHOY="},"type":"unlocked"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8SjRHlUBe4hAmTk3KDeJ96RhN+s10xRrHDrxEi1O5W0=

packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/openapi/openapi-validation-no-keychain.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ test(testCase, async (t: Test) => {
7676
rpcApiHttpHost,
7777
logLevel,
7878
pluginRegistry: new PluginRegistry(),
79+
privateUrl: rpcApiHttpHost,
7980
},
8081
);
8182

0 commit comments

Comments
 (0)