diff --git a/packages/cactus-plugin-ledger-connector-besu/package.json b/packages/cactus-plugin-ledger-connector-besu/package.json index 3306e472c6..bd5d6ef994 100644 --- a/packages/cactus-plugin-ledger-connector-besu/package.json +++ b/packages/cactus-plugin-ledger-connector-besu/package.json @@ -78,6 +78,7 @@ "web3-eea": "0.10.0" }, "devDependencies": { + "@hyperledger/cactus-plugin-keychain-memory": "^0.2.0", "@hyperledger/cactus-test-tooling": "0.2.0", "@types/express": "4.17.8", "@types/joi": "14.3.4" diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/json/generated/openapi-spec.json b/packages/cactus-plugin-ledger-connector-besu/src/main/json/generated/openapi-spec.json index 0bbaab6fc3..75572b49e0 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/json/generated/openapi-spec.json +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/json/generated/openapi-spec.json @@ -39,6 +39,9 @@ { "$ref": "#/components/schemas/Web3SigningCredentialGethKeychainPassword" }, + { + "$ref": "#/components/schemas/Web3SigningCredentialCactusKeychainRef" + }, { "$ref": "#/components/schemas/Web3SigningCredentialPrivateKeyHex" }, @@ -78,6 +81,39 @@ } } }, + "Web3SigningCredentialCactusKeychainRef": { + "type": "object", + "required": [ + "type", + "ethAccount", + "keychainId", + "keychainEntryKey" + ], + "properties": { + "type": { + "$ref": "#/components/schemas/Web3SigningCredentialType" + }, + "ethAccount": { + "type": "string", + "description": "The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication.", + "minLength": 64, + "maxLength": 64, + "nullable": false + }, + "keychainEntryKey": { + "type": "string", + "description": "The key to use when looking up the the keychain entry holding the secret pointed to by the keychainEntryKey parameter.", + "minLength": 0, + "maxLength": 1024 + }, + "keychainId": { + "type": "string", + "description": "The keychain ID to use when looking up the the keychain plugin instance that will be used to retrieve the secret pointed to by the keychainEntryKey parameter.", + "minLength": 0, + "maxLength": 1024 + } + } + }, "Web3SigningCredentialPrivateKeyHex": { "type": "object", "required": [ @@ -119,6 +155,7 @@ "Web3SigningCredentialType": { "type": "string", "enum": [ + "CACTUS_KEYCHAIN_REF", "GETH_KEYCHAIN_PASSWORD", "PRIVATE_KEY_HEX", "NONE" diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/generated/openapi/typescript-axios/api.ts index 2c6c4dee55..f072eeffe0 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -319,8 +319,39 @@ export interface SolidityContractJsonArtifact { * @type Web3SigningCredential * @export */ -export type Web3SigningCredential = Web3SigningCredentialGethKeychainPassword | Web3SigningCredentialNone | Web3SigningCredentialPrivateKeyHex; +export type Web3SigningCredential = Web3SigningCredentialCactusKeychainRef | Web3SigningCredentialGethKeychainPassword | Web3SigningCredentialNone | Web3SigningCredentialPrivateKeyHex; +/** + * + * @export + * @interface Web3SigningCredentialCactusKeychainRef + */ +export interface Web3SigningCredentialCactusKeychainRef { + /** + * + * @type {Web3SigningCredentialType} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + type: Web3SigningCredentialType; + /** + * The ethereum account (public key) that the credential belongs to. Basically the username in the traditional terminology of authentication. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + ethAccount: string; + /** + * The key to use when looking up the the keychain entry holding the secret pointed to by the keychainEntryKey parameter. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + keychainEntryKey: string; + /** + * The keychain ID to use when looking up the the keychain plugin instance that will be used to retrieve the secret pointed to by the keychainEntryKey parameter. + * @type {string} + * @memberof Web3SigningCredentialCactusKeychainRef + */ + keychainId: string; +} /** * * @export @@ -390,6 +421,7 @@ export interface Web3SigningCredentialPrivateKeyHex { * @enum {string} */ export enum Web3SigningCredentialType { + CACTUSKEYCHAINREF = 'CACTUS_KEYCHAIN_REF', GETHKEYCHAINPASSWORD = 'GETH_KEYCHAIN_PASSWORD', PRIVATEKEYHEX = 'PRIVATE_KEY_HEX', NONE = 'NONE' diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/openapi-spec.ts b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/openapi-spec.ts index c8d0962d92..d0630b3162 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/openapi-spec.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/openapi-spec.ts @@ -43,6 +43,9 @@ export const CACTUS_OPEN_API_JSON: OpenAPIV3.Document = { $ref: "#/components/schemas/Web3SigningCredentialGethKeychainPassword", }, + { + $ref: "#/components/schemas/Web3SigningCredentialCactusKeychainRef", + }, { $ref: "#/components/schemas/Web3SigningCredentialPrivateKeyHex" }, { $ref: "#/components/schemas/Web3SigningCredentialNone" }, ], @@ -76,6 +79,43 @@ export const CACTUS_OPEN_API_JSON: OpenAPIV3.Document = { }, }, }, + Web3SigningCredentialCactusKeychainRef: { + type: "object", + required: ["type", "ethAccount", "keychainId", "keychainEntryKey"], + properties: { + type: { + $ref: "#/components/schemas/Web3SigningCredentialType", + }, + ethAccount: { + type: "string", + description: + "The ethereum account (public key) that the credential " + + " belongs to. Basically the username in the traditional " + + " terminology of authentication.", + minLength: 64, + maxLength: 64, + nullable: false, + }, + keychainEntryKey: { + type: "string", + description: + "The key to use when looking up the" + + " the keychain entry holding the secret pointed to by the " + + " keychainEntryKey parameter.", + minLength: 0, + maxLength: 1024, + }, + keychainId: { + type: "string", + description: + "The keychain ID to use when looking up the" + + " the keychain plugin instance that will be used to retrieve" + + " the secret pointed to by the keychainEntryKey parameter.", + minLength: 0, + maxLength: 1024, + }, + }, + }, Web3SigningCredentialPrivateKeyHex: { type: "object", required: ["type", "ethAccount", "secret"], @@ -114,7 +154,12 @@ export const CACTUS_OPEN_API_JSON: OpenAPIV3.Document = { }, Web3SigningCredentialType: { type: "string", - enum: ["GETH_KEYCHAIN_PASSWORD", "PRIVATE_KEY_HEX", "NONE"], + enum: [ + "CACTUS_KEYCHAIN_REF", + "GETH_KEYCHAIN_PASSWORD", + "PRIVATE_KEY_HEX", + "NONE", + ], }, EthContractInvocationType: { type: "string", diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts index 52a7b53d45..2b69484381 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts @@ -17,6 +17,8 @@ import { PluginAspect, ICactusPlugin, ICactusPluginOptions, + PluginRegistry, + IPluginKeychain, } from "@hyperledger/cactus-core-api"; import { @@ -36,6 +38,7 @@ import { InvokeContractV1Response, RunTransactionRequest, RunTransactionResponse, + Web3SigningCredentialCactusKeychainRef, Web3SigningCredentialGethKeychainPassword, Web3SigningCredentialPrivateKeyHex, Web3SigningCredentialType, @@ -48,6 +51,7 @@ import { isWeb3SigningCredentialNone } from "./model-type-guards"; export interface IPluginLedgerConnectorBesuOptions extends ICactusPluginOptions { rpcApiHttpHost: string; + pluginRegistry: PluginRegistry; logLevel?: LogLevelDesc; } @@ -64,6 +68,7 @@ export class PluginLedgerConnectorBesu private readonly instanceId: string; private readonly log: Logger; private readonly web3: Web3; + private readonly pluginRegistry: PluginRegistry; private httpServer: Server | SecureServer | null = null; public static readonly CLASS_NAME = "PluginLedgerConnectorBesu"; @@ -77,6 +82,7 @@ export class PluginLedgerConnectorBesu Checks.truthy(options, `${fnTag} arg options`); Checks.truthy(options.rpcApiHttpHost, `${fnTag} options.rpcApiHttpHost`); Checks.truthy(options.instanceId, `${fnTag} options.instanceId`); + Checks.truthy(options.pluginRegistry, `${fnTag} options.pluginRegistry`); const level = this.options.logLevel || "INFO"; const label = this.className; @@ -87,6 +93,7 @@ export class PluginLedgerConnectorBesu ); this.web3 = new Web3(web3Provider); this.instanceId = options.instanceId; + this.pluginRegistry = options.pluginRegistry; } public getInstanceId(): string { @@ -203,6 +210,9 @@ export class PluginLedgerConnectorBesu const fnTag = `${this.className}#transact()`; switch (req.web3SigningCredential.type) { + case Web3SigningCredentialType.CACTUSKEYCHAINREF: { + return this.transactCactusKeychainRef(req); + } case Web3SigningCredentialType.GETHKEYCHAINPASSWORD: { return this.transactGethKeychain(req); } @@ -290,6 +300,39 @@ export class PluginLedgerConnectorBesu } } + public async transactCactusKeychainRef( + req: RunTransactionRequest + ): Promise { + const fnTag = `${this.className}#transactCactusKeychainRef()`; + const { transactionConfig, web3SigningCredential } = req; + const { + ethAccount, + keychainEntryKey, + keychainId, + } = web3SigningCredential as Web3SigningCredentialCactusKeychainRef; + + // locate the keychain plugin that has access to the keychain backend + // denoted by the keychainID from the request. + const keychainPlugin = this.pluginRegistry + .findManyByAspect(PluginAspect.KEYCHAIN) + .find((k) => k.getKeychainId() === keychainId); + + 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); + + return this.transactPrivateKey({ + transactionConfig, + web3SigningCredential: { + ethAccount, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + secret: privateKeyHex, + }, + }); + } + public async pollForTxReceipt( txHash: string, timeoutMs: number = 60000 diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.json b/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.json index a08daad15b..d72a25ee2e 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.json +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.json @@ -2,120 +2,186 @@ "contractName": "HelloWorld", "abi": [ { - "constant": true, + "inputs": [], + "name": "getName", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "sayHello", "outputs": [ { + "internalType": "string", "name": "", "type": "string" } ], - "payable": false, "stateMutability": "pure", "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "newName", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ], - "metadata": "{\"compiler\":{\"version\":\"0.5.1+commit.c8a2cb62\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"sayHello\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.sol\":\"HelloWorld\"},\"evmVersion\":\"byzantium\",\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.sol\":{\"keccak256\":\"0xf22ee0ef01b08a43f95f4f92fb00972915e42cc65ff4502361646f12c7148762\",\"urls\":[\"bzzr://e70661255d8f1d3155a7e999c74577de66bb7e67bb93012ecbbfbd4381f4f504\"]}},\"version\":1}", - "bytecode": "608060405234801561001057600080fd5b50610124806100206000396000f3fe6080604052600436106038577c01000000000000000000000000000000000000000000000000000000006000350463ef5fb05b8114603d575b600080fd5b348015604857600080fd5b50604f60c1565b6040805160208082528351818301528351919283929083019185019080838360005b8381101560875781810151838201526020016071565b50505050905090810190601f16801560b35780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051808201909152600c81527f48656c6c6f20576f726c6421000000000000000000000000000000000000000060208201529056fea165627a7a72305820163679abbba7fb3b971fb45c42705166d72dce361ffbee6d7f65c733b022ce420029", - "deployedBytecode": "6080604052600436106038577c01000000000000000000000000000000000000000000000000000000006000350463ef5fb05b8114603d575b600080fd5b348015604857600080fd5b50604f60c1565b6040805160208082528351818301528351919283929083019185019080838360005b8381101560875781810151838201526020016071565b50505050905090810190601f16801560b35780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051808201909152600c81527f48656c6c6f20576f726c6421000000000000000000000000000000000000000060208201529056fea165627a7a72305820163679abbba7fb3b971fb45c42705166d72dce361ffbee6d7f65c733b022ce420029", - "sourceMap": "25:112:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;25:112:0;;;;;;;", - "deployedSourceMap": "25:112:0:-;;;;;;;;;;;;;;;;;;;;;48:87;;8:9:-1;5:2;;;30:1;27;20:12;5:2;48:87:0;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;48:87:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;110:21;;;;;;;;;;;;;;;;;48:87;:::o", - "sourcePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.sol", + "metadata": "{\"compiler\":{\"version\":\"0.7.2+commit.51b20bc0\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"getName\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sayHello\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"newName\",\"type\":\"string\"}],\"name\":\"setName\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol\":\"HelloWorld\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol\":{\"keccak256\":\"0x0b78fa11f33f7936a80da194c49f04198e38947e3f98f3a7b765b4adb4c455c1\",\"urls\":[\"bzz-raw://12697aa12341c70ed7a411a27a17398dcb2d4336a14dac51845e2123acf174c7\",\"dweb:/ipfs/QmPhH1UbHtUeeen9W2qMDwEVVWAtVJSMN29Nch5q8Gax1D\"]}},\"version\":1}", + "bytecode": "60c0604052600d60808190526c4361707461696e43616374757360981b60a090815261002e9160009190610041565b5034801561003b57600080fd5b506100d4565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061008257805160ff19168380011785556100af565b828001600101855582156100af579182015b828111156100af578251825591602001919060010190610094565b506100bb9291506100bf565b5090565b5b808211156100bb57600081556001016100c0565b61030f806100e36000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806317d7de7c14610046578063c47f0027146100c3578063ef5fb05b1461016b575b600080fd5b61004e610173565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610088578181015183820152602001610070565b50505050905090810190601f1680156100b55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610169600480360360208110156100d957600080fd5b8101906020810181356401000000008111156100f457600080fd5b82018360208201111561010657600080fd5b8035906020019184600183028401116401000000008311171561012857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610209945050505050565b005b61004e610220565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ff5780601f106101d4576101008083540402835291602001916101ff565b820191906000526020600020905b8154815290600101906020018083116101e257829003601f168201915b5050505050905090565b805161021c906000906020840190610246565b5050565b60408051808201909152600c81526b48656c6c6f20576f726c642160a01b602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028757805160ff19168380011785556102b4565b828001600101855582156102b4579182015b828111156102b4578251825591602001919060010190610299565b506102c09291506102c4565b5090565b5b808211156102c057600081556001016102c556fea2646970667358221220965216915797db694e698c024753d560e1989f2aebb14463f9225b2297003b2c64736f6c63430007020033", + "deployedBytecode": "608060405234801561001057600080fd5b50600436106100415760003560e01c806317d7de7c14610046578063c47f0027146100c3578063ef5fb05b1461016b575b600080fd5b61004e610173565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610088578181015183820152602001610070565b50505050905090810190601f1680156100b55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610169600480360360208110156100d957600080fd5b8101906020810181356401000000008111156100f457600080fd5b82018360208201111561010657600080fd5b8035906020019184600183028401116401000000008311171561012857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610209945050505050565b005b61004e610220565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ff5780601f106101d4576101008083540402835291602001916101ff565b820191906000526020600020905b8154815290600101906020018083116101e257829003601f168201915b5050505050905090565b805161021c906000906020840190610246565b5050565b60408051808201909152600c81526b48656c6c6f20576f726c642160a01b602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028757805160ff19168380011785556102b4565b828001600101855582156102b4579182015b828111156102b4578251825591602001919060010190610299565b506102c09291506102c4565b5090565b5b808211156102c057600081556001016102c556fea2646970667358221220965216915797db694e698c024753d560e1989f2aebb14463f9225b2297003b2c64736f6c63430007020033", + "sourceMap": "463:37:0:-:0;439:322;463:37;;439:322;463:37;;;-1:-1:-1;;;463:37:0;;;;;;-1:-1:-1;;463:37:0;;:::i;:::-;;439:322;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;439:322:0;;;-1:-1:-1;439:322:0;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;", + "deployedSourceMap": "439:322:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;598:81;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;683:76;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;683:76:0;;-1:-1:-1;683:76:0;;-1:-1:-1;;;;;683:76:0:i;:::-;;505:89;;;:::i;598:81::-;670:4;663:11;;;;;;;;-1:-1:-1;;663:11:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;638:13;;663:11;;670:4;;663:11;;670:4;663:11;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;598:81;:::o;683:76::-;740:14;;;;:4;;:14;;;;;:::i;:::-;;683:76;:::o;505:89::-;568:21;;;;;;;;;;;;-1:-1:-1;;;568:21:0;;;;505:89;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;", + "sourcePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol", "compiler": { "name": "solc", - "version": "0.5.1+commit.c8a2cb62" + "version": "0.7.2+commit.51b20bc0" }, "ast": { - "absolutePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.sol", + "absolutePath": "/home/peter/a/blockchain/blockchain-integration-framework/packages/cactus-plugin-ledger-connector-quorum/src/test/solidity/hello-world-contract/HelloWorld.sol", "exportedSymbols": { "HelloWorld": [ - 10 + 31 ] }, - "id": 11, + "id": 32, "nodeType": "SourceUnit", "nodes": [ { "id": 1, "literals": [ "solidity", - ">", - "0.5", + ">=", + "0.7", ".0" ], "nodeType": "PragmaDirective", - "src": "0:23:0" + "src": "413:24:0" }, { + "abstract": false, "baseContracts": [], "contractDependencies": [], "contractKind": "contract", - "documentation": null, "fullyImplemented": true, - "id": 10, + "id": 31, "linearizedBaseContracts": [ - 10 + 31 ], "name": "HelloWorld", "nodeType": "ContractDefinition", "nodes": [ + { + "constant": false, + "id": 4, + "mutability": "mutable", + "name": "name", + "nodeType": "VariableDeclaration", + "scope": 31, + "src": "463:37:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string" + }, + "typeName": { + "id": 2, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "463:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": { + "hexValue": "4361707461696e436163747573", + "id": 3, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "485:15:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_bdd2f21877c99489ddcc32737686677f40d460368c7982ce22ce4f72b41b0312", + "typeString": "literal_string \"CaptainCactus\"" + }, + "value": "CaptainCactus" + }, + "visibility": "private" + }, { "body": { - "id": 8, + "id": 11, "nodeType": "Block", - "src": "105:30:0", + "src": "562:32:0", "statements": [ { "expression": { - "argumentTypes": null, "hexValue": "48656c6c6f20576f726c6421", - "id": 6, + "id": 9, "isConstant": false, "isLValue": false, "isPure": true, "kind": "string", "lValueRequested": false, "nodeType": "Literal", - "src": "117:14:0", - "subdenomination": null, + "src": "575:14:0", "typeDescriptions": { "typeIdentifier": "t_stringliteral_3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0", "typeString": "literal_string \"Hello World!\"" }, "value": "Hello World!" }, - "functionReturnParameters": 5, - "id": 7, + "functionReturnParameters": 8, + "id": 10, "nodeType": "Return", - "src": "110:21:0" + "src": "568:21:0" } ] }, - "documentation": null, - "id": 9, + "functionSelector": "ef5fb05b", + "id": 12, "implemented": true, "kind": "function", "modifiers": [], "name": "sayHello", "nodeType": "FunctionDefinition", "parameters": { - "id": 2, + "id": 5, "nodeType": "ParameterList", "parameters": [], - "src": "66:2:0" + "src": "523:2:0" }, "returnParameters": { - "id": 5, + "id": 8, "nodeType": "ParameterList", "parameters": [ { "constant": false, - "id": 4, + "id": 7, + "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", - "scope": 9, - "src": "90:13:0", + "scope": 12, + "src": "547:13:0", "stateVariable": false, "storageLocation": "memory", "typeDescriptions": { @@ -123,45 +189,229 @@ "typeString": "string" }, "typeName": { - "id": 3, + "id": 6, "name": "string", "nodeType": "ElementaryTypeName", - "src": "90:6:0", + "src": "547:6:0", "typeDescriptions": { "typeIdentifier": "t_string_storage_ptr", "typeString": "string" } }, - "value": null, "visibility": "internal" } ], - "src": "89:15:0" + "src": "546:15:0" }, - "scope": 10, - "src": "48:87:0", + "scope": 31, + "src": "505:89:0", "stateMutability": "pure", - "superFunction": null, + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 19, + "nodeType": "Block", + "src": "655:24:0", + "statements": [ + { + "expression": { + "id": 17, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4, + "src": "670:4:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "functionReturnParameters": 16, + "id": 18, + "nodeType": "Return", + "src": "663:11:0" + } + ] + }, + "functionSelector": "17d7de7c", + "id": 20, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getName", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 13, + "nodeType": "ParameterList", + "parameters": [], + "src": "614:2:0" + }, + "returnParameters": { + "id": 16, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 15, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "scope": 20, + "src": "638:13:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 14, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "638:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "637:15:0" + }, + "scope": 31, + "src": "598:81:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 29, + "nodeType": "Block", + "src": "732:27:0", + "statements": [ + { + "expression": { + "id": 27, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 25, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4, + "src": "740:4:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 26, + "name": "newName", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 22, + "src": "747:7:0", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + "src": "740:14:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "id": 28, + "nodeType": "ExpressionStatement", + "src": "740:14:0" + } + ] + }, + "functionSelector": "c47f0027", + "id": 30, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setName", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 23, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 22, + "mutability": "mutable", + "name": "newName", + "nodeType": "VariableDeclaration", + "scope": 30, + "src": "700:21:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 21, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "700:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "699:23:0" + }, + "returnParameters": { + "id": 24, + "nodeType": "ParameterList", + "parameters": [], + "src": "732:0:0" + }, + "scope": 31, + "src": "683:76:0", + "stateMutability": "nonpayable", + "virtual": false, "visibility": "public" } ], - "scope": 11, - "src": "25:112:0" + "scope": 32, + "src": "439:322:0" } ], - "src": "0:138:0" + "src": "413:349:0" }, "functionHashes": { - "sayHello()": "ef5fb05b" + "getName()": "17d7de7c", + "sayHello()": "ef5fb05b", + "setName(string)": "c47f0027" }, "gasEstimates": { "creation": { - "codeDepositCost": "58400", - "executionCost": "111", - "totalCost": "58511" + "codeDepositCost": "156600", + "executionCost": "infinite", + "totalCost": "infinite" }, "external": { - "sayHello()": "infinite" + "getName()": "infinite", + "sayHello()": "infinite", + "setName(string)": "infinite" } } } \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.sol b/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.sol index 4575dc9866..befd843936 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.sol +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/HelloWorld.sol @@ -5,10 +5,22 @@ // With that said, there shouldn't be any reason to recompile this, like ever... // ***************************************************************************** -pragma solidity >0.5.0; +pragma solidity >=0.7.0; contract HelloWorld { - function sayHello () public pure returns (string memory) { - return 'Hello World!'; - } + string private name = "CaptainCactus"; + + function sayHello () public pure returns (string memory) { + return 'Hello World!'; + } + + function getName() public view returns (string memory) + { + return name; + } + + function setName(string memory newName) public + { + name = newName; + } } diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts index be5643be64..ec3eb32fd3 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/deploy-contract-from-json.test.ts @@ -1,15 +1,21 @@ import test, { Test } from "tape"; import { v4 as uuidv4 } from "uuid"; +import { PluginRegistry } from "@hyperledger/cactus-core-api"; import { EthContractInvocationType, Web3SigningCredentialType, PluginLedgerConnectorBesu, PluginFactoryLedgerConnector, + Web3SigningCredentialCactusKeychainRef, } from "../../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; import { BesuTestLedger } from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; test("deploys contract via .json file", async (t: Test) => { + const logLevel: LogLevelDesc = "TRACE"; const besuTestLedger = new BesuTestLedger(); await besuTestLedger.start(); @@ -19,41 +25,284 @@ test("deploys contract via .json file", async (t: Test) => { }); const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); - const besuKeyPair = await besuTestLedger.getBesuKeyPair(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/1.5.1/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = "627306090abaB3A6e1400e9345bC60c78a8BEf57"; + const besuKeyPair = { + privateKey: + "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", + }; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); const factory = new PluginFactoryLedgerConnector(); const connector: PluginLedgerConnectorBesu = await factory.create({ rpcApiHttpHost, instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), }); - const { transactionReceipt } = await connector.deployContract({ - bytecode: HelloWorldContractJson.bytecode, + await connector.transact({ web3SigningCredential: { - ethAccount: "", + ethAccount: firstHighNetWorthAccount, secret: besuKeyPair.privateKey, type: Web3SigningCredentialType.PRIVATEKEYHEX, }, - gas: 1000000, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, }); - t.ok(transactionReceipt, "TX Receipt truthy OK"); - const { contractAddress } = transactionReceipt; - t.ok(contractAddress, "TX Receipt Contract Address truthy OK"); + 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"); - const { callOutput } = await connector.invokeContract({ - contractAbi: HelloWorldContractJson.abi, - contractAddress: contractAddress as string, - invocationType: EthContractInvocationType.CALL, - methodName: "sayHello", - params: [], - web3SigningCredential: { - type: Web3SigningCredentialType.NONE, - }, + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContract({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + }); + 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.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.CALL, + methodName: "sayHello", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string" + ); + }); + + // FIXME: Stop skipping this test once the 'personal' API is enabled in the + // Besu Test Ledger image by default. + test.skip("invoke Web3SigningCredentialType.GETHKEYCHAINPASSWORD", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + const setNameOut = await connector.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.SEND, + methodName: "setName", + params: [newName], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GETHKEYCHAINPASSWORD, + }, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + const getNameOut = await connector.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.SEND, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GETHKEYCHAINPASSWORD, + }, + }); + t2.ok( + getNameOut.transactionReceipt, + `getName() SEND invocation produced receipt OK` + ); + + const { callOutput: getNameOut2 } = await connector.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.CALL, + methodName: "getName", + params: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: "", + type: Web3SigningCredentialType.GETHKEYCHAINPASSWORD, + }, + }); + t2.equal( + getNameOut2, + newName, + "setName() invocation #2 output is truthy OK" + ); + + t2.end(); }); - t.ok(callOutput, "sayHello() call output truthy OK"); - t.equal(callOutput, "Hello World!", "sayHello() says Hello World! OK"); + 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.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.SEND, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + const { callOutput: getNameOut } = await connector.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.CALL, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.SEND, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.CACTUSKEYCHAINREF", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + + const web3SigningCredential: Web3SigningCredentialCactusKeychainRef = { + ethAccount: testEthAccount.address, + keychainEntryKey, + keychainId: keychainPlugin.getKeychainId(), + type: Web3SigningCredentialType.CACTUSKEYCHAINREF, + }; + + const setNameOut = await connector.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.SEND, + methodName: "setName", + params: [newName], + gas: 1000000, + web3SigningCredential, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + const { callOutput: getNameOut } = await connector.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.CALL, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.invokeContract({ + contractAddress, + contractAbi: HelloWorldContractJson.abi, + invocationType: EthContractInvocationType.SEND, + methodName: "getName", + params: [], + gas: 1000000, + web3SigningCredential, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + t2.end(); + }); t.end(); });