diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-quorum/src/main/json/openapi.json index 58979a0221..5d667f745a 100644 --- a/packages/cactus-plugin-ledger-connector-quorum/src/main/json/openapi.json +++ b/packages/cactus-plugin-ledger-connector-quorum/src/main/json/openapi.json @@ -70,7 +70,6 @@ "required": [ "type", "ethAccount", - "keychainId", "keychainEntryKey" ], "properties": { @@ -434,6 +433,11 @@ "minimum": 0, "default": 60000, "nullable": false + }, + "contractJSON": { + "type": "object", + "description": "For use when not using keychain, pass the contract in as this variable", + "nullable": false } } }, @@ -448,6 +452,55 @@ } } }, + "DeployContractSolidityBytecodeJsonObjectV1Request": { + "type": "object", + "required": [ + "contractName", + "bytecode", + "web3SigningCredential", + "contractJson" + ], + "properties": { + "contractName": { + "type": "string", + "description": "The contract name for retrieve the contracts json on the keychain.", + "minLength": 1, + "maxLength": 100, + "nullable": false + }, + "web3SigningCredential": { + "$ref": "#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "bytecode": { + "type": "string", + "nullable": false, + "minLength": 1, + "maxLength": 24576, + "description": "See https://ethereum.stackexchange.com/a/47556 regarding the maximum length of the bytecode" + }, + "gas": { + "type": "number", + "nullable": false + }, + "gasPrice": { + "type": "string", + "nullable": false + }, + "timeoutMs": { + "type": "number", + "description": "The amount of milliseconds to wait for a transaction receipt with theaddress of the contract(which indicates successful deployment) beforegiving up and crashing.", + "minimum": 0, + "default": 60000, + "nullable": false + }, + "contractJSON": { + "type": "object", + "description": "For use when not using keychain, pass the contract in as this variable", + "nullable": false + } + } + }, "InvokeContractV1Request": { "type": "object", "required": [ @@ -541,6 +594,106 @@ "description": "The keychainId for retrieve the contracts json.", "minLength": 1, "maxLength": 100 + }, + "contractJSON":{ + "type": "object", + "description": "The contract object to be passed if not using keychain.", + "nullable": false + } + } + }, + "InvokeContractJsonObjectV1Request": { + "type": "object", + "required": [ + "contractName", + "signingCredential", + "invocationType", + "methodName", + "params", + "contractJson" + ], + "properties": { + "contractName": { + "type": "string", + "nullable": false + }, + "signingCredential": { + "$ref": "#/components/schemas/Web3SigningCredential", + "nullable": false + }, + "invocationType": { + "$ref": "#/components/schemas/EthContractInvocationType", + "nullable": false, + "description": "Indicates wether it is a CALL or a SEND type of invocation where only SEND ends up creating an actual transaction on the ledger." + }, + "methodName": { + "description": "The name of the contract method to invoke.", + "type": "string", + "nullable": false, + "minLength": 1, + "maxLength": 2048 + }, + "params": { + "description": "The list of arguments to pass in to the contract method being invoked.", + "type": "array", + "default": [], + "items": {} + }, + "contractAbi": { + "description": "The application binary interface of the solidity contract, optional parameter", + "type": "array", + "items": {}, + "nullable": false + }, + "contractAddress": { + "description": "Address of the solidity contract, optional parameter", + "type": "string", + "nullable": false + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "gas": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "gasPrice": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "nonce": { + "type": "number" + }, + "timeoutMs": { + "type": "number", + "description": "The amount of milliseconds to wait for a transaction receipt beforegiving up and crashing. Only has any effect if the invocation type is SEND", + "minimum": 0, + "default": 60000, + "nullable": false + }, + "contractJSON":{ + "type": "object", + "description": "The contract object to be passed if not using keychain.", + "nullable": false } } }, @@ -601,6 +754,40 @@ } } }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/deploy-contract-solidity-bytecode-json-object": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/deploy-contract-solidity-bytecode-json-object" + } + }, + "operationId": "deployContractSolBytecodeJsonObjectV1", + "summary": "Deploys the bytecode of a Solidity contract.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeployContractSolidityBytecodeJsonObjectV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeployContractSolidityBytecodeV1Response" + } + } + } + } + } + } + }, "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/run-transaction": { "post": { "x-hyperledger-cactus": { @@ -669,6 +856,40 @@ } } }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/invoke-contract-json-object": { + "post": { + "x-hyperledger-cactus": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/invoke-contract-json-object" + } + }, + "operationId": "invokeContractV1NoKeychain", + "summary": "Invokes a contract on a besu ledger", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvokeContractJsonObjectV1Request" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvokeContractV1Response" + } + } + } + } + } + } + }, "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/get-prometheus-exporter-metrics": { "get": { "x-hyperledger-cactus": { diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/generated/openapi/typescript-axios/api.ts index ed35900d52..b4db575055 100644 --- a/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -21,6 +21,55 @@ import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObj // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base'; +/** + * + * @export + * @interface DeployContractSolidityBytecodeJsonObjectV1Request + */ +export interface DeployContractSolidityBytecodeJsonObjectV1Request { + /** + * The contract name for retrieve the contracts json on the keychain. + * @type {string} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + contractName: string; + /** + * + * @type {Web3SigningCredential} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + web3SigningCredential: Web3SigningCredential; + /** + * See https://ethereum.stackexchange.com/a/47556 regarding the maximum length of the bytecode + * @type {string} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + bytecode: string; + /** + * + * @type {number} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + gas?: number; + /** + * + * @type {string} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + gasPrice?: string; + /** + * The amount of milliseconds to wait for a transaction receipt with theaddress of the contract(which indicates successful deployment) beforegiving up and crashing. + * @type {number} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + timeoutMs?: number; + /** + * For use when not using keychain, pass the contract in as this variable + * @type {object} + * @memberof DeployContractSolidityBytecodeJsonObjectV1Request + */ + contractJSON?: object; +} /** * * @export @@ -69,6 +118,12 @@ export interface DeployContractSolidityBytecodeV1Request { * @memberof DeployContractSolidityBytecodeV1Request */ timeoutMs?: number; + /** + * For use when not using keychain, pass the contract in as this variable + * @type {object} + * @memberof DeployContractSolidityBytecodeV1Request + */ + contractJSON?: object; } /** * @@ -94,6 +149,91 @@ export enum EthContractInvocationType { Call = 'CALL' } +/** + * + * @export + * @interface InvokeContractJsonObjectV1Request + */ +export interface InvokeContractJsonObjectV1Request { + /** + * + * @type {string} + * @memberof InvokeContractJsonObjectV1Request + */ + contractName: string; + /** + * + * @type {Web3SigningCredential} + * @memberof InvokeContractJsonObjectV1Request + */ + signingCredential: Web3SigningCredential; + /** + * + * @type {EthContractInvocationType} + * @memberof InvokeContractJsonObjectV1Request + */ + invocationType: EthContractInvocationType; + /** + * The name of the contract method to invoke. + * @type {string} + * @memberof InvokeContractJsonObjectV1Request + */ + methodName: string; + /** + * The list of arguments to pass in to the contract method being invoked. + * @type {Array} + * @memberof InvokeContractJsonObjectV1Request + */ + params: Array; + /** + * The application binary interface of the solidity contract, optional parameter + * @type {Array} + * @memberof InvokeContractJsonObjectV1Request + */ + contractAbi?: Array; + /** + * Address of the solidity contract, optional parameter + * @type {string} + * @memberof InvokeContractJsonObjectV1Request + */ + contractAddress?: string; + /** + * + * @type {string | number} + * @memberof InvokeContractJsonObjectV1Request + */ + value?: string | number; + /** + * + * @type {string | number} + * @memberof InvokeContractJsonObjectV1Request + */ + gas?: string | number; + /** + * + * @type {string | number} + * @memberof InvokeContractJsonObjectV1Request + */ + gasPrice?: string | number; + /** + * + * @type {number} + * @memberof InvokeContractJsonObjectV1Request + */ + nonce?: number; + /** + * The amount of milliseconds to wait for a transaction receipt beforegiving up and crashing. Only has any effect if the invocation type is SEND + * @type {number} + * @memberof InvokeContractJsonObjectV1Request + */ + timeoutMs?: number; + /** + * The contract object to be passed if not using keychain. + * @type {object} + * @memberof InvokeContractJsonObjectV1Request + */ + contractJSON?: object; +} /** * * @export @@ -178,6 +318,12 @@ export interface InvokeContractV1Request { * @memberof InvokeContractV1Request */ keychainId?: string; + /** + * The contract object to be passed if not using keychain. + * @type {object} + * @memberof InvokeContractV1Request + */ + contractJSON?: object; } /** * @@ -401,7 +547,7 @@ export interface Web3SigningCredentialCactusKeychainRef { * @type {string} * @memberof Web3SigningCredentialCactusKeychainRef */ - keychainId: string; + keychainId?: string; } /** * @@ -549,6 +695,40 @@ export interface Web3TransactionReceipt { */ export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeJsonObjectV1Request} [deployContractSolidityBytecodeJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deployContractSolBytecodeJsonObjectV1: async (deployContractSolidityBytecodeJsonObjectV1Request?: DeployContractSolidityBytecodeJsonObjectV1Request, options: any = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/deploy-contract-solidity-bytecode-json-object`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(deployContractSolidityBytecodeJsonObjectV1Request, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @summary Deploys the bytecode of a Solidity contract. @@ -647,6 +827,40 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractJsonObjectV1Request} [invokeContractJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeContractV1NoKeychain: async (invokeContractJsonObjectV1Request?: InvokeContractJsonObjectV1Request, options: any = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/invoke-contract-json-object`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(invokeContractJsonObjectV1Request, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @summary Executes a transaction on a quorum ledger @@ -691,6 +905,17 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati export const DefaultApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) return { + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeJsonObjectV1Request} [deployContractSolidityBytecodeJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request?: DeployContractSolidityBytecodeJsonObjectV1Request, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @summary Deploys the bytecode of a Solidity contract. @@ -723,6 +948,17 @@ export const DefaultApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.invokeContractV1(invokeContractV1Request, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractJsonObjectV1Request} [invokeContractJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async invokeContractV1NoKeychain(invokeContractJsonObjectV1Request?: InvokeContractJsonObjectV1Request, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.invokeContractV1NoKeychain(invokeContractJsonObjectV1Request, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @summary Executes a transaction on a quorum ledger @@ -744,6 +980,16 @@ export const DefaultApiFp = function(configuration?: Configuration) { export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = DefaultApiFp(configuration) return { + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeJsonObjectV1Request} [deployContractSolidityBytecodeJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request?: DeployContractSolidityBytecodeJsonObjectV1Request, options?: any): AxiosPromise { + return localVarFp.deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request, options).then((request) => request(axios, basePath)); + }, /** * * @summary Deploys the bytecode of a Solidity contract. @@ -773,6 +1019,16 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa invokeContractV1(invokeContractV1Request?: InvokeContractV1Request, options?: any): AxiosPromise { return localVarFp.invokeContractV1(invokeContractV1Request, options).then((request) => request(axios, basePath)); }, + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractJsonObjectV1Request} [invokeContractJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + invokeContractV1NoKeychain(invokeContractJsonObjectV1Request?: InvokeContractJsonObjectV1Request, options?: any): AxiosPromise { + return localVarFp.invokeContractV1NoKeychain(invokeContractJsonObjectV1Request, options).then((request) => request(axios, basePath)); + }, /** * * @summary Executes a transaction on a quorum ledger @@ -793,6 +1049,18 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa * @extends {BaseAPI} */ export class DefaultApi extends BaseAPI { + /** + * + * @summary Deploys the bytecode of a Solidity contract. + * @param {DeployContractSolidityBytecodeJsonObjectV1Request} [deployContractSolidityBytecodeJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request?: DeployContractSolidityBytecodeJsonObjectV1Request, options?: any) { + return DefaultApiFp(this.configuration).deployContractSolBytecodeJsonObjectV1(deployContractSolidityBytecodeJsonObjectV1Request, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary Deploys the bytecode of a Solidity contract. @@ -828,6 +1096,18 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration).invokeContractV1(invokeContractV1Request, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @summary Invokes a contract on a besu ledger + * @param {InvokeContractJsonObjectV1Request} [invokeContractJsonObjectV1Request] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public invokeContractV1NoKeychain(invokeContractJsonObjectV1Request?: InvokeContractJsonObjectV1Request, options?: any) { + return DefaultApiFp(this.configuration).invokeContractV1NoKeychain(invokeContractJsonObjectV1Request, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @summary Executes a transaction on a quorum ledger diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts index 204e0e8e2f..2c921f4d6b 100644 --- a/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts +++ b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/plugin-ledger-connector-quorum.ts @@ -36,12 +36,15 @@ import { } from "@hyperledger/cactus-common"; import { DeployContractSolidityBytecodeEndpoint } from "./web-services/deploy-contract-solidity-bytecode-endpoint"; +import { DeployContractSolidityBytecodeJsonObjectEndpoint } from "./web-services/deploy-contract-solidity-bytecode-endpoint-json-object"; import { DeployContractSolidityBytecodeV1Request, + DeployContractSolidityBytecodeJsonObjectV1Request, DeployContractSolidityBytecodeV1Response, EthContractInvocationType, InvokeContractV1Request, + InvokeContractJsonObjectV1Request, InvokeContractV1Response, RunTransactionRequest, RunTransactionResponse, @@ -53,6 +56,7 @@ import { import { RunTransactionEndpoint } from "./web-services/run-transaction-endpoint"; import { InvokeContractEndpoint } from "./web-services/invoke-contract-endpoint"; +import { InvokeContractJsonObjectEndpoint } from "./web-services/invoke-contract-endpoint-json-object"; import { isWeb3SigningCredentialNone } from "./model-type-guards"; import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter"; @@ -113,7 +117,7 @@ export class PluginLedgerConnectorQuorum ); this.web3 = new Web3(web3Provider); this.instanceId = options.instanceId; - this.pluginRegistry = options.pluginRegistry; + this.pluginRegistry = options.pluginRegistry as PluginRegistry; this.prometheusExporter = options.prometheusExporter || new PrometheusExporter({ pollingIntervalInMin: 1 }); @@ -177,6 +181,13 @@ export class PluginLedgerConnectorQuorum }); endpoints.push(endpoint); } + { + const endpoint = new DeployContractSolidityBytecodeJsonObjectEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } { const endpoint = new RunTransactionEndpoint({ connector: this, @@ -191,6 +202,13 @@ export class PluginLedgerConnectorQuorum }); endpoints.push(endpoint); } + { + const endpoint = new InvokeContractJsonObjectEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } { const opts: IGetPrometheusExporterMetricsEndpointV1Options = { connector: this, @@ -217,12 +235,12 @@ export class PluginLedgerConnectorQuorum return consensusHasTransactionFinality(currentConsensusAlgorithmFamily); } - public async invokeContract( + + public async getContractInfoKeychain( req: InvokeContractV1Request, - ): Promise { + ): Promise { const fnTag = `${this.className}#invokeContract()`; const contractName = req.contractName; - let contractInstance: InstanceType; if (req.keychainId != undefined) { const networkId = await this.web3.eth.net.getId(); @@ -240,6 +258,15 @@ export class PluginLedgerConnectorQuorum } const contractStr = await keychainPlugin.get(contractName); const contractJSON = JSON.parse(contractStr); + const contract = { + contractJSON: contractJSON, + signingCredential: req.signingCredential, + methodName: req.methodName, + params: req.params, + invocationType: req.invocationType, + }; + keychainPlugin.set(contractName, JSON.stringify(contractJSON)); + if ( contractJSON.networks === undefined || contractJSON.networks[networkId] === undefined || @@ -248,41 +275,94 @@ export class PluginLedgerConnectorQuorum if (isWeb3SigningCredentialNone(req.signingCredential)) { throw new Error(`${fnTag} Cannot deploy contract with pre-signed TX`); } - const web3SigningCredential = req.signingCredential as - | Web3SigningCredentialPrivateKeyHex - | Web3SigningCredentialCactusKeychainRef; - - const receipt = await this.transact({ - transactionConfig: { - data: `0x${contractJSON.bytecode}`, - from: web3SigningCredential.ethAccount, - gas: req.gas, - gasPrice: req.gasPrice, - }, - web3SigningCredential, - }); - - const address = { - address: receipt.transactionReceipt.contractAddress, - }; - const network = { [networkId]: address }; - contractJSON.networks = network; - keychainPlugin.set(contractName, JSON.stringify(contractJSON)); + } else if ( + req.keychainId == undefined && + req.contractAbi == undefined && + req.contractAddress == undefined + ) { + throw new Error( + `${fnTag} Cannot invoke a contract without contract instance, the keychainId param is needed`, + ); + } else { + return this.invokeContract(contract); } - const contract = new this.web3.eth.Contract( - contractJSON.abi, - contractJSON.networks[networkId].address, - ); - this.contracts[contractName] = contract; - } else if ( - req.keychainId == undefined && - req.contractAbi == undefined && - req.contractAddress == undefined + } + } + + public async getContractInfo( + req: InvokeContractJsonObjectV1Request, + ): Promise { + const fnTag = `${this.className}#invokeContractNoKeychain()`; + if (req.contractJSON != undefined) { + const networkId = await this.web3.eth.net.getId(); + const contractJSON = req.contractJSON as any; + const contract = req; + if ( + contractJSON.networks === undefined || + contractJSON.networks[networkId] === undefined || + contractJSON.networks[networkId].address === undefined + ) { + if (isWeb3SigningCredentialNone(req.signingCredential)) { + throw new Error(`${fnTag} Cannot deploy contract with pre-signed TX`); + } else { + throw new Error( + `${fnTag} Unsupported invocation type ${req.invocationType}`, + ); + } + } else if ( + req.contractJSON == undefined && + req.contractAbi == undefined && + req.contractAddress == undefined + ) { + throw new Error( + `${fnTag} Cannot invoke a contract without contract instance, the contractJson param is needed`, + ); + } else { + return this.invokeContract(contract); + } + } + } + + public async invokeContract(req: any): Promise { + const fnTag = `${this.className}#invokeContract()`; + const contractName = req.contractName; + let contractInstance: InstanceType; + + const networkId = await this.web3.eth.net.getId(); + const contractJSON = req.contractJSON; + if ( + contractJSON.networks === undefined || + contractJSON.networks[networkId] === undefined || + contractJSON.networks[networkId].address === undefined ) { - throw new Error( - `${fnTag} Cannot invoke a contract without contract instance, the keychainId param is needed`, - ); + if (isWeb3SigningCredentialNone(req.signingCredential)) { + throw new Error(`${fnTag} Cannot deploy contract with pre-signed TX`); + } + const web3SigningCredential = req.signingCredential as + | Web3SigningCredentialPrivateKeyHex + | Web3SigningCredentialCactusKeychainRef; + + const receipt = await this.transact({ + transactionConfig: { + data: `0x${contractJSON.bytecode}`, + from: web3SigningCredential.ethAccount, + gas: req.gas, + gasPrice: req.gasPrice, + }, + web3SigningCredential, + }); + + const address = { + address: receipt.transactionReceipt.contractAddress, + }; + const network = { [networkId]: address }; + contractJSON.networks = network; } + const contract = new this.web3.eth.Contract( + contractJSON.abi, + contractJSON.networks[networkId].address, + ); + this.contracts[contractName] = contract; contractInstance = this.contracts[contractName]; if (req.contractAbi != undefined) { @@ -451,13 +531,15 @@ export class PluginLedgerConnectorQuorum // locate the keychain plugin that has access to the keychain backend // denoted by the keychainID from the request. - const keychainPlugin = this.pluginRegistry.findOneByKeychainId(keychainId); + const keychainPlugin = this.pluginRegistry.findOneByKeychainId( + keychainId as string, + ); Checks.truthy(keychainPlugin, `${fnTag} keychain for ID:"${keychainId}"`); // Now use the found keychain plugin to actually perform the lookup of // the private key that we need to run the transaction. - const privateKeyHex = await keychainPlugin.get(keychainEntryKey); + const privateKeyHex = await keychainPlugin?.get(keychainEntryKey as string); return this.transactPrivateKey({ transactionConfig, @@ -555,4 +637,49 @@ export class PluginLedgerConnectorQuorum `${fnTag} Cannot deploy contract without keychainId and the contractName`, ); } + + public async deployContractJsonObject( + req: DeployContractSolidityBytecodeJsonObjectV1Request, + ): Promise { + const fnTag = `${this.className}#deployContractNoKeychain()`; + Checks.truthy(req, `${fnTag} req`); + if (req.contractJSON != undefined) { + const networkId = await this.web3.eth.net.getId(); + + const web3SigningCredential = req.web3SigningCredential as + | Web3SigningCredentialGethKeychainPassword + | Web3SigningCredentialPrivateKeyHex; + const receipt = await this.transact({ + transactionConfig: { + data: `0x${req.bytecode}`, + from: web3SigningCredential.ethAccount, + gas: req.gas, + gasPrice: req.gasPrice, + }, + web3SigningCredential, + }); + if ( + receipt.transactionReceipt.status && + receipt.transactionReceipt.contractAddress != undefined && + receipt.transactionReceipt.contractAddress != null + ) { + const address = { address: receipt.transactionReceipt.contractAddress }; + const contractJSON = req.contractJSON as any; + this.log.info(JSON.stringify(contractJSON)); + const contract = new this.web3.eth.Contract( + contractJSON.abi, + receipt.transactionReceipt.contractAddress, + ); + this.contracts[req.contractName] = contract; + + const network = { [networkId]: address }; + contractJSON.networks = network; + } + + return receipt; + } + throw new Error( + `${fnTag} Cannot deploy contract without keychainId and the contractName`, + ); + } } diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint-json-object.ts b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint-json-object.ts new file mode 100644 index 0000000000..9b0e72c7d5 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/deploy-contract-solidity-bytecode-endpoint-json-object.ts @@ -0,0 +1,109 @@ +import { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorQuorum } from "../plugin-ledger-connector-quorum"; +import { DeployContractSolidityBytecodeJsonObjectV1Request } from "../generated/openapi/typescript-axios"; +import OAS from "../../json/openapi.json"; + +export interface IDeployContractSolidityBytecodeOptionsJsonObject { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorQuorum; +} + +export class DeployContractSolidityBytecodeJsonObjectEndpoint + implements IWebServiceEndpoint { + public static readonly CLASS_NAME = + "DeployContractSolidityBytecodeEndpointJsonObject"; + + private readonly log: Logger; + + public get className(): string { + return DeployContractSolidityBytecodeJsonObjectEndpoint.CLASS_NAME; + } + + constructor( + public readonly options: IDeployContractSolidityBytecodeOptionsJsonObject, + ) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getOasPath() { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/deploy-contract-solidity-bytecode-json-object" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + const reqBody: DeployContractSolidityBytecodeJsonObjectV1Request = req.body; + try { + const resBody = await this.options.connector.deployContractJsonObject( + reqBody, + ); + res.json(resBody); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/invoke-contract-endpoint-json-object.ts b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/invoke-contract-endpoint-json-object.ts new file mode 100644 index 0000000000..00af8359ff --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/invoke-contract-endpoint-json-object.ts @@ -0,0 +1,103 @@ +import { Express, Request, Response } from "express"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, +} from "@hyperledger/cactus-common"; +import { + IEndpointAuthzOptions, + IExpressRequestHandler, + IWebServiceEndpoint, +} from "@hyperledger/cactus-core-api"; +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; + +import { PluginLedgerConnectorQuorum } from "../plugin-ledger-connector-quorum"; + +import OAS from "../../json/openapi.json"; + +export interface IInvokeContractEndpointJsonObjectOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorQuorum; +} + +export class InvokeContractJsonObjectEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "InvokeContractJsonObjectEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return InvokeContractJsonObjectEndpoint.CLASS_NAME; + } + + constructor( + public readonly options: IInvokeContractEndpointJsonObjectOptions, + ) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getOasPath() { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/invoke-contract-json-object" + ]; + } + + public getPath(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.path; + } + + public getVerbLowerCase(): string { + const apiPath = this.getOasPath(); + return apiPath.post["x-hyperledger-cactus"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.getOasPath().post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + const reqBody = req.body; + try { + const resBody = await this.options.connector.getContractInfo(reqBody); + res.json(resBody); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + res.status(500).json({ + message: "Internal Server Error", + error: ex?.stack || ex?.message, + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/invoke-contract-endpoint.ts b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/invoke-contract-endpoint.ts index c71aad404d..a15ea9a8ef 100644 --- a/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/invoke-contract-endpoint.ts +++ b/packages/cactus-plugin-ledger-connector-quorum/src/main/typescript/web-services/invoke-contract-endpoint.ts @@ -86,7 +86,9 @@ export class InvokeContractEndpoint implements IWebServiceEndpoint { this.log.debug(reqTag); const reqBody = req.body; try { - const resBody = await this.options.connector.invokeContract(reqBody); + const resBody = await this.options.connector.getContractInfoKeychain( + reqBody, + ); res.json(resBody); } catch (ex) { this.log.error(`Crash while serving ${reqTag}`, ex); diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts new file mode 100644 index 0000000000..71f71f4474 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json-json-object.test.ts @@ -0,0 +1,393 @@ +import test, { Test } from "tape-promise/tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { K_CACTUS_QUORUM_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; + +import { + EthContractInvocationType, + PluginLedgerConnectorQuorum, + Web3SigningCredentialType, + DefaultApi as QuorumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const testCase = "Quorum Ledger Connector Plugin"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration } from "@hyperledger/cactus-core-api"; + +const logLevel: LogLevelDesc = "INFO"; +const contractName = "HelloWorld"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + const containerImageName = "hyperledger/cactus-quorum-all-in-one"; + const ledgerOptions = { containerImageName, containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const quorumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(quorumGenesisOptions); + t.ok(quorumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + quorumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = quorumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + + // Instantiate connector with the keychain plugin that already has the + // private key we want to use for one of our tests + const connector: PluginLedgerConnectorQuorum = new PluginLedgerConnectorQuorum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new QuorumApi(apiConfig); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContractJsonObject({ + contractName: HelloWorldContractJson.contractName, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); + + const { callOutput: getNameOut2 } = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + test("get prometheus exporter metrics", async (t2: Test) => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_QUORUM_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_QUORUM_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_QUORUM_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_QUORUM_TOTAL_TX_COUNT + + '"} 3'; + t2.ok(res); + t2.ok(res.data); + t2.equal(res.status, 200); + t2.true( + res.data.includes(promMetricsOutput), + "Total Transaction Count of 3 recorded as expected. RESULT OK.", + ); + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts index 27c41d5523..7cbc9d9743 100644 --- a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts +++ b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-deploy-contract-from-json.test.ts @@ -170,12 +170,13 @@ test(testCase, async (t: Test) => { "contractAddress typeof string OK", ); - const { callOutput: helloMsg } = await connector.invokeContract({ + const { callOutput: helloMsg } = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Call, methodName: "sayHello", + keychainId: keychainPlugin.getKeychainId(), params: [], signingCredential: { ethAccount: firstHighNetWorthAccount, @@ -192,12 +193,13 @@ test(testCase, async (t: Test) => { test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { const newName = `DrCactus${uuidV4()}`; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], signingCredential: { ethAccount: firstHighNetWorthAccount, @@ -209,12 +211,13 @@ test(testCase, async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], gas: 1000000, signingCredential: { @@ -233,12 +236,13 @@ test(testCase, async (t: Test) => { ); } - const getNameOut = await connector.invokeContract({ + const getNameOut = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], signingCredential: { ethAccount: firstHighNetWorthAccount, @@ -248,19 +252,22 @@ test(testCase, async (t: Test) => { }); t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); - const { callOutput: getNameOut2 } = await connector.invokeContract({ - contractName, - contractAbi: HelloWorldContractJson.abi, - contractAddress, - invocationType: EthContractInvocationType.Call, - methodName: "getName", - params: [], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: "", - type: Web3SigningCredentialType.GethKeychainPassword, + const { callOutput: getNameOut2 } = await connector.getContractInfoKeychain( + { + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, }, - }); + ); t2.equal( getNameOut2, newName, @@ -300,12 +307,13 @@ test(testCase, async (t: Test) => { test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { const newName = `DrCactus${uuidV4()}`; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], signingCredential: { ethAccount: testEthAccount.address, @@ -317,12 +325,13 @@ test(testCase, async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], gas: 1000000, signingCredential: { @@ -340,12 +349,13 @@ test(testCase, async (t: Test) => { "setName() invocation with invalid nonce", ); } - const { callOutput: getNameOut } = await connector.invokeContract({ + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Call, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], gas: 1000000, signingCredential: { @@ -356,12 +366,13 @@ test(testCase, async (t: Test) => { }); t2.equal(getNameOut, newName, `getName() output reflects the update OK`); - const getNameOut2 = await connector.invokeContract({ + const getNameOut2 = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], gas: 1000000, signingCredential: { @@ -385,12 +396,13 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.CactusKeychainRef, }; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], gas: 1000000, signingCredential, @@ -399,12 +411,13 @@ test(testCase, async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], gas: 1000000, signingCredential: { @@ -422,24 +435,26 @@ test(testCase, async (t: Test) => { "setName() invocation with invalid nonce", ); } - const { callOutput: getNameOut } = await connector.invokeContract({ + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Call, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], gas: 1000000, signingCredential, }); t2.equal(getNameOut, newName, `getName() output reflects the update OK`); - const getNameOut2 = await connector.invokeContract({ + const getNameOut2 = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], gas: 1000000, signingCredential, @@ -461,13 +476,13 @@ test(testCase, async (t: Test) => { K_CACTUS_QUORUM_TOTAL_TX_COUNT + '{type="' + K_CACTUS_QUORUM_TOTAL_TX_COUNT + - '"} 5'; + '"} 6'; t2.ok(res); t2.ok(res.data); t2.equal(res.status, 200); t2.true( res.data.includes(promMetricsOutput), - "Total Transaction Count of 5 recorded as expected. RESULT OK.", + "Total Transaction Count of 6 recorded as expected. RESULT OK.", ); t2.end(); }); diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts new file mode 100644 index 0000000000..1ced3d2d25 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract-json-object.test.ts @@ -0,0 +1,303 @@ +import test, { Test } from "tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorQuorum, + Web3SigningCredentialType, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const logLevel: LogLevelDesc = "INFO"; +const contractName = "HelloWorld"; + +test("Quorum Ledger Connector Plugin", async (t: Test) => { + const containerImageVersion = "2021-01-08-7a055c3"; // Quorum v2.3.0, Tessera v0.10.0 + const containerImageName = "hyperledger/cactus-quorum-all-in-one"; + const ledgerOptions = { containerImageName, containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const quorumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(quorumGenesisOptions); + t.ok(quorumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + quorumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = quorumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + const connector: PluginLedgerConnectorQuorum = new PluginLedgerConnectorQuorum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContractJsonObject({ + contractName: HelloWorldContractJson.contractName, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); + + const { callOutput: getNameOut2 } = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract.test.ts b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract.test.ts index 8b9282580f..42d4bca298 100644 --- a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract.test.ts +++ b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v2.3.0-invoke-contract.test.ts @@ -126,7 +126,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { "contractAddress typeof string OK", ); - const { callOutput: helloMsg } = await connector.invokeContract({ + const { callOutput: helloMsg } = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Call, @@ -147,7 +147,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { const newName = `DrCactus${uuidV4()}`; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -163,7 +163,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -186,7 +186,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { ); } - const getNameOut = await connector.invokeContract({ + const getNameOut = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -200,18 +200,20 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { }); t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); - const { callOutput: getNameOut2 } = await connector.invokeContract({ - contractName, - keychainId: keychainPlugin.getKeychainId(), - invocationType: EthContractInvocationType.Call, - methodName: "getName", - params: [], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: "", - type: Web3SigningCredentialType.GethKeychainPassword, + const { callOutput: getNameOut2 } = await connector.getContractInfoKeychain( + { + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, }, - }); + ); t2.equal( getNameOut2, newName, @@ -251,7 +253,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { const newName = `DrCactus${uuidV4()}`; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -267,7 +269,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -289,7 +291,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { "setName() invocation with invalid nonce", ); } - const { callOutput: getNameOut } = await connector.invokeContract({ + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Call, @@ -304,7 +306,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { }); t2.equal(getNameOut, newName, `getName() output reflects the update OK`); - const getNameOut2 = await connector.invokeContract({ + const getNameOut2 = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -332,7 +334,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { type: Web3SigningCredentialType.CactusKeychainRef, }; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -345,7 +347,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -367,7 +369,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { "setName() invocation with invalid nonce", ); } - const { callOutput: getNameOut } = await connector.invokeContract({ + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Call, @@ -378,7 +380,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { }); t2.equal(getNameOut, newName, `getName() output reflects the update OK`); - const getNameOut2 = await connector.invokeContract({ + const getNameOut2 = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts new file mode 100644 index 0000000000..2b136612d9 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json-json-object.test.ts @@ -0,0 +1,391 @@ +import test, { Test } from "tape-promise/tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { + LogLevelDesc, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { K_CACTUS_QUORUM_TOTAL_TX_COUNT } from "../../../../../main/typescript/prometheus-exporter/metrics"; + +import { + EthContractInvocationType, + PluginLedgerConnectorQuorum, + Web3SigningCredentialType, + DefaultApi as QuorumApi, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; + +const testCase = "Quorum Ledger Connector Plugin"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import { AddressInfo } from "net"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const logLevel: LogLevelDesc = "INFO"; +const contractName = "HelloWorld"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const containerImageName = "hyperledger/cactus-quorum-all-in-one"; + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + const ledgerOptions = { containerImageName, containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const quorumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(quorumGenesisOptions); + t.ok(quorumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + quorumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = quorumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + + const connector: PluginLedgerConnectorQuorum = new PluginLedgerConnectorQuorum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "0.0.0.0", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + test.onFinish(async () => await Servers.shutdown(server)); + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + t.comment( + `Metrics URL: ${apiHost}/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-quorum/get-prometheus-exporter-metrics`, + ); + + const apiConfig = new Configuration({ basePath: apiHost }); + const apiClient = new QuorumApi(apiConfig); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContractJsonObject({ + contractName: HelloWorldContractJson.contractName, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); + + const { callOutput: getNameOut2 } = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.getContractInfo({ + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + test("get prometheus exporter metrics", async (t2: Test) => { + const res = await apiClient.getPrometheusMetricsV1(); + const promMetricsOutput = + "# HELP " + + K_CACTUS_QUORUM_TOTAL_TX_COUNT + + " Total transactions executed\n" + + "# TYPE " + + K_CACTUS_QUORUM_TOTAL_TX_COUNT + + " gauge\n" + + K_CACTUS_QUORUM_TOTAL_TX_COUNT + + '{type="' + + K_CACTUS_QUORUM_TOTAL_TX_COUNT + + '"} 3'; + t2.ok(res); + t2.ok(res.data); + t2.equal(res.status, 200); + t2.true( + res.data.includes(promMetricsOutput), + "Total Transaction Count of 3 recorded as expected. RESULT OK.", + ); + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json.test.ts b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json.test.ts index 827ad286ff..b9d50f2998 100644 --- a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json.test.ts +++ b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-deploy-contract-from-json.test.ts @@ -171,12 +171,13 @@ test(testCase, async (t: Test) => { "contractAddress typeof string OK", ); - const { callOutput: helloMsg } = await connector.invokeContract({ + const { callOutput: helloMsg } = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Call, methodName: "sayHello", + keychainId: keychainPlugin.getKeychainId(), params: [], signingCredential: { ethAccount: firstHighNetWorthAccount, @@ -193,12 +194,13 @@ test(testCase, async (t: Test) => { test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { const newName = `DrCactus${uuidV4()}`; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], signingCredential: { ethAccount: firstHighNetWorthAccount, @@ -210,12 +212,13 @@ test(testCase, async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], gas: 1000000, signingCredential: { @@ -234,12 +237,13 @@ test(testCase, async (t: Test) => { ); } - const getNameOut = await connector.invokeContract({ + const getNameOut = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], signingCredential: { ethAccount: firstHighNetWorthAccount, @@ -249,19 +253,22 @@ test(testCase, async (t: Test) => { }); t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); - const { callOutput: getNameOut2 } = await connector.invokeContract({ - contractName, - contractAbi: HelloWorldContractJson.abi, - contractAddress, - invocationType: EthContractInvocationType.Call, - methodName: "getName", - params: [], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: "", - type: Web3SigningCredentialType.GethKeychainPassword, + const { callOutput: getNameOut2 } = await connector.getContractInfoKeychain( + { + contractName, + contractAbi: HelloWorldContractJson.abi, + contractAddress, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, }, - }); + ); t2.equal( getNameOut2, newName, @@ -301,12 +308,13 @@ test(testCase, async (t: Test) => { test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { const newName = `DrCactus${uuidV4()}`; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], signingCredential: { ethAccount: testEthAccount.address, @@ -318,12 +326,13 @@ test(testCase, async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], gas: 1000000, signingCredential: { @@ -341,12 +350,13 @@ test(testCase, async (t: Test) => { "setName() invocation with invalid nonce", ); } - const { callOutput: getNameOut } = await connector.invokeContract({ + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Call, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], gas: 1000000, signingCredential: { @@ -357,12 +367,13 @@ test(testCase, async (t: Test) => { }); t2.equal(getNameOut, newName, `getName() output reflects the update OK`); - const getNameOut2 = await connector.invokeContract({ + const getNameOut2 = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], gas: 1000000, signingCredential: { @@ -386,12 +397,13 @@ test(testCase, async (t: Test) => { type: Web3SigningCredentialType.CactusKeychainRef, }; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], gas: 1000000, signingCredential, @@ -400,12 +412,13 @@ test(testCase, async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "setName", + keychainId: keychainPlugin.getKeychainId(), params: [newName], gas: 1000000, signingCredential: { @@ -423,24 +436,26 @@ test(testCase, async (t: Test) => { "setName() invocation with invalid nonce", ); } - const { callOutput: getNameOut } = await connector.invokeContract({ + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Call, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], gas: 1000000, signingCredential, }); t2.equal(getNameOut, newName, `getName() output reflects the update OK`); - const getNameOut2 = await connector.invokeContract({ + const getNameOut2 = await connector.getContractInfoKeychain({ contractName, contractAbi: HelloWorldContractJson.abi, contractAddress, invocationType: EthContractInvocationType.Send, methodName: "getName", + keychainId: keychainPlugin.getKeychainId(), params: [], gas: 1000000, signingCredential, @@ -462,13 +477,13 @@ test(testCase, async (t: Test) => { K_CACTUS_QUORUM_TOTAL_TX_COUNT + '{type="' + K_CACTUS_QUORUM_TOTAL_TX_COUNT + - '"} 5'; + '"} 6'; t2.ok(res); t2.ok(res.data); t2.equal(res.status, 200); t2.true( res.data.includes(promMetricsOutput), - "Total Transaction Count of 5 recorded as expected. RESULT OK.", + "Total Transaction Count of 6 recorded as expected. RESULT OK.", ); t2.end(); }); diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts new file mode 100644 index 0000000000..f34253a4e4 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract-json-object.test.ts @@ -0,0 +1,303 @@ +import test, { Test } from "tape"; +import Web3 from "web3"; +import { v4 as uuidV4 } from "uuid"; + +import { LogLevelDesc } from "@hyperledger/cactus-common"; + +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; + +import { + EthContractInvocationType, + PluginLedgerConnectorQuorum, + Web3SigningCredentialType, +} from "../../../../../main/typescript/public-api"; + +import { + QuorumTestLedger, + IQuorumGenesisOptions, + IAccount, +} from "@hyperledger/cactus-test-tooling"; +import { PluginRegistry } from "@hyperledger/cactus-core"; + +const logLevel: LogLevelDesc = "INFO"; +const contractName = "HelloWorld"; + +test("Quorum Ledger Connector Plugin", async (t: Test) => { + const containerImageVersion = "2021-05-03-quorum-v21.4.1"; + const containerImageName = "hyperledger/cactus-quorum-all-in-one"; + const ledgerOptions = { containerImageName, containerImageVersion }; + const ledger = new QuorumTestLedger(ledgerOptions); + test.onFinish(async () => { + await ledger.stop(); + await ledger.destroy(); + }); + await ledger.start(); + + const rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + const quorumGenesisOptions: IQuorumGenesisOptions = await ledger.getGenesisJsObject(); + t.ok(quorumGenesisOptions); + t.ok(quorumGenesisOptions.alloc); + + const highNetWorthAccounts: string[] = Object.keys( + quorumGenesisOptions.alloc, + ).filter((address: string) => { + const anAccount: IAccount = quorumGenesisOptions.alloc[address]; + const theBalance = parseInt(anAccount.balance, 10); + return theBalance > 10e7; + }); + const [firstHighNetWorthAccount] = highNetWorthAccounts; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidV4()); + const connector: PluginLedgerConnectorQuorum = new PluginLedgerConnectorQuorum( + { + instanceId: uuidV4(), + rpcApiHttpHost, + logLevel, + pluginRegistry: new PluginRegistry(), + }, + ); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContractJsonObject({ + contractName: HelloWorldContractJson.contractName, + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + contractJSON: HelloWorldContractJson, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "sayHello", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + nonce: 2, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const getNameOut = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); + + const { callOutput: getNameOut2 } = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK", + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidV4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.None, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { + const newName = `DrCactus${uuidV4()}`; + const setNameOut = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + contractJSON: HelloWorldContractJson, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + nonce: 1, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.getContractInfo({ + contractName, + invocationType: EthContractInvocationType.Send, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + contractJSON: HelloWorldContractJson, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + t.end(); +}); diff --git a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract.test.ts b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract.test.ts index a559b2f902..628cea1512 100644 --- a/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract.test.ts +++ b/packages/cactus-plugin-ledger-connector-quorum/src/test/typescript/integration/plugin-ledger-connector-quorum/deploy-contract/v21.4.1-invoke-contract.test.ts @@ -126,7 +126,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { "contractAddress typeof string OK", ); - const { callOutput: helloMsg } = await connector.invokeContract({ + const { callOutput: helloMsg } = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Call, @@ -147,7 +147,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { test("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { const newName = `DrCactus${uuidV4()}`; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -163,7 +163,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -186,7 +186,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { ); } - const getNameOut = await connector.invokeContract({ + const getNameOut = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -200,18 +200,20 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { }); t2.ok(getNameOut.success, `getName() SEND invocation produced receipt OK`); - const { callOutput: getNameOut2 } = await connector.invokeContract({ - contractName, - keychainId: keychainPlugin.getKeychainId(), - invocationType: EthContractInvocationType.Call, - methodName: "getName", - params: [], - signingCredential: { - ethAccount: firstHighNetWorthAccount, - secret: "", - type: Web3SigningCredentialType.GethKeychainPassword, + const { callOutput: getNameOut2 } = await connector.getContractInfoKeychain( + { + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Call, + methodName: "getName", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, }, - }); + ); t2.equal( getNameOut2, newName, @@ -251,7 +253,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { test("invoke Web3SigningCredentialType.PrivateKeyHex", async (t2: Test) => { const newName = `DrCactus${uuidV4()}`; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -267,7 +269,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -289,7 +291,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { "setName() invocation with invalid nonce", ); } - const { callOutput: getNameOut } = await connector.invokeContract({ + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Call, @@ -304,7 +306,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { }); t2.equal(getNameOut, newName, `getName() output reflects the update OK`); - const getNameOut2 = await connector.invokeContract({ + const getNameOut2 = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -332,7 +334,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { type: Web3SigningCredentialType.CactusKeychainRef, }; - const setNameOut = await connector.invokeContract({ + const setNameOut = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -345,7 +347,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); try { - const setNameOutInvalid = await connector.invokeContract({ + const setNameOutInvalid = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send, @@ -367,7 +369,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { "setName() invocation with invalid nonce", ); } - const { callOutput: getNameOut } = await connector.invokeContract({ + const { callOutput: getNameOut } = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Call, @@ -378,7 +380,7 @@ test("Quorum Ledger Connector Plugin", async (t: Test) => { }); t2.equal(getNameOut, newName, `getName() output reflects the update OK`); - const getNameOut2 = await connector.invokeContract({ + const getNameOut2 = await connector.getContractInfoKeychain({ contractName, keychainId: keychainPlugin.getKeychainId(), invocationType: EthContractInvocationType.Send,