├── .gitignore ├── LICENSE ├── README.md ├── deploy-transact ├── README.md ├── contracts │ ├── Directory.sol │ ├── simplestorage.sol │ └── simplestorage_v5.sol ├── lib │ ├── azure-signing.js │ ├── besu-node-signing.js │ ├── ext-signing.js │ ├── node-signing.js │ └── utils.js ├── package.json └── test.js └── log-analyzers ├── README.md ├── geth-poa-logs.js ├── images ├── tx-rates.jpg └── txpool.jpg └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | deploy-transact/*.bin 3 | deploy-transact/package-lock.json 4 | deploy-transact/contracts/*.bin 5 | log-analyzers/package-lock.json 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kaleido-samples 2 | Various samples to demonstrate interacting with the Kaleido platform 3 | 4 | ## deploy-transact 5 | A simple node.js command line program that demonstrates standard interactions with a Kaleido node ingress: 6 | * deploy a smart contract 7 | * submit a transaction to the smart contract 8 | * query the smart contract for current value 9 | * submit an externally signed transaction using the web3 wallet module 10 | * deploy private smart contracts (if the target node is Quorum) 11 | * submit private transactions (if the target node is Quorum) 12 | 13 | ## log-analyzers 14 | A set of tools that parse Geth or Quorum logs to generate line charts for transaction rates and transaction pool status -------------------------------------------------------------------------------- /deploy-transact/README.md: -------------------------------------------------------------------------------- 1 | # Simple node.js example of Geth/Quorum/Besu transaction submission 2 | 3 | **Note**: These commands have been tested with Node version 8. 4 | 5 | ### Run it: 6 | ``` 7 | npm install 8 | node test.js --url= --verbose --deploy 9 | ``` 10 | 11 | The above command deploys a simple smart contract `simplestorage.sol` to the target node and sets the initial value to `10`. 12 | 13 | You should see output similar to the following: 14 | ``` 15 | 1. Connecting to target node: https://:@u0ydoxu2pu-u0tjcmaal4-rpc.us-east-2.kaleido.io 16 | Found account in the target node: 0xFf8c6060d3D4930E81a4c923b96B05FC039901F4 17 | 2. Deploying smart contract 18 | Smart contract deployed, ready to take calls at "0xb53C9f258FdD76B78D154CcEd33EFe09f9Bd9B69" 19 | ``` 20 | 21 | ### Query the current value 22 | Feel frees to switch to a different node in the same consortia for any of the following commands as they are supposed to work the same on any of the consortia nodes. 23 | 24 | ``` 25 | node test.js --url= --contract= --query 26 | ``` 27 | 28 | ### Set a new value 29 | ``` 30 | node test.js --url= --contract= --set= 31 | ``` 32 | 33 | ### Print out the DataStored event 34 | ``` 35 | node test.js --url= --ws= --contract= --set= 36 | ``` 37 | 38 | ### Deploy a private smart contract 39 | ``` 40 | node test.js --url= --deploy --privateFor='[""]' 41 | ``` 42 | **Note:** the privateFor argument value must be delimited with single quotes, and use double quotes for each of the target private addresses, in order for the script to successfully parse 43 | 44 | ### Besu Privacy Groups 45 | 46 | #### Create a besu privacy group 47 | ``` 48 | node test.js --url= --privacy_groups --create --besu_private --addresses= 49 | ``` 50 | 51 | #### Delete a besu privacy group 52 | ``` 53 | node test.js --url= --privacy_groups --destroy --besu_private --privacyGroupId= 54 | ``` 55 | 56 | #### find besu privacy groups 57 | ``` 58 | node test.js --url= --privacy_groups --find --besu_private --addresses= 59 | ``` 60 | 61 | 62 | ### Deploy a private smart contract to a Besu node 63 | ``` 64 | node test.js --url= --deploy --besu_private --privateFor= --privateFrom= 65 | ``` 66 | 67 | Using privacy group id 68 | ``` 69 | node test.js --url= --deploy --besu_private --privacyGroupId= --privateFrom= --chainId= 70 | ``` 71 | **NOTE:** List receiver addresses seperated by comma(,) 72 | 73 | ### Send a private transaction 74 | ``` 75 | node test.js --url= --contract= --set= --privateFor='[""]' 76 | ``` 77 | 78 | ### Send a private transaction to a Besu node 79 | ``` 80 | node test.js --url= --contract= --set= --besu_private --privateFor= --privateFrom= 81 | ``` 82 | 83 | Using privacy group id 84 | ``` 85 | node test.js --url= --contract= --set= --besu_private --privacyGroupId= --privateFrom= --chainId= 86 | ``` 87 | 88 | ### Query a private transaction from a Besu node 89 | ``` 90 | node test.js --url= --contract= --query --besu_private --privateFor= --privateFrom= 91 | ``` 92 | 93 | Using privacy group id 94 | ``` 95 | node test.js --url= --contract= --query --besu_private --privacyGroupId= --privateFrom= 96 | ``` 97 | 98 | ### Use an external account to deploy contract and sign a transaction 99 | ``` 100 | node test.js --sign --url= --deploy 101 | node test.js --sign --url= --contract= --set= 102 | ``` 103 | 104 | ### Use hdwallet account to deploy contract and sign a transaction 105 | 106 | **Note:** Kaleido's hdwallet service must be provisioned and a wallet created to use this option 107 | 108 | ``` 109 | node test.js --sign --hdwalletUrl= --hdwalletId= --hdwalletAccountIndex= --url= --deploy 110 | node test.js --sign --hdwalletUrl= --hdwalletId= --hdwalletAccountIndex= --url= --contract= --set= 111 | ``` 112 | 113 | ### Use Azure Key Vault Signing Key to deploy contract and sign a transaction 114 | 115 | Provision a key vault resource in Azure and add a key of type "EC" using curve "SECP256K1". 116 | 117 | Register an OAuth App in the Azure Active Directory so that this test client and authenticate with the Azure API gateway. 118 | 119 | Set the following environment variables to capture the OAuth configurations: 120 | * CLIENT_ID: Application (client) ID 121 | * CLINET_SECRET: client secret 122 | * DIRECTORY_ID: Directory (tenant) ID 123 | 124 | The following commands use these same values for the key instance: 125 | * key vault resource name: eth-kv-test, it's also the first segment of the "DNS Name" https://eth-kv-test.vault.azure.net/ 126 | * key name: eth-signing-key-1 127 | * key version: 3c63feb0d41d458b9c02c8d23a6b3e88 128 | 129 | ``` 130 | node test.js --azure --servicename eth-kv-test --keyname eth-signing-key-1 --keyversion 3c63feb0d41d458b9c02c8d23a6b3e88 --url --deploy 131 | node test.js --azure --servicename eth-kv-test --keyname eth-signing-key-1 --keyversion 3c63feb0d41d458b9c02c8d23a6b3e88 --url --contract --set 132 | ``` 133 | -------------------------------------------------------------------------------- /deploy-transact/contracts/Directory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract Directory { 4 | // Logged when the owner of a node assigns a new owner to that node. 5 | event NewOwner(bytes32 indexed parentNode, bytes32 indexed nodeHash, address owner, string label, string proof); 6 | 7 | // Logged when the owner of a node creates a new owner as a subnode. 8 | event NewUser(bytes32 indexed userId, bytes32 indexed orgId, string name, address owner); 9 | 10 | // Logged when the owner of a node transfers ownership to a new account. 11 | event Transfer(bytes32 indexed nodeHash, address owner); 12 | 13 | // Logged when a new on-chain address is stored or a previous one is updated 14 | event NewAccountValue(bytes32 indexed parentNode, string name, address value, string version); 15 | 16 | // Logged when the accountsReverse array cannot be updated because account is already claimed by 17 | // an owner different than msg.sender 18 | event CannotOverwriteAccount(string errMessage, address account, address accountOwner, address messageSender); 19 | 20 | struct AccountValue { 21 | address value; 22 | string versionDescription; 23 | } 24 | 25 | struct Account { 26 | string fullPathName; 27 | string name; 28 | string parentName; 29 | mapping(uint => AccountValue) values; // versionIndex to address 30 | uint valuesCount; 31 | bool exists; 32 | } 33 | 34 | struct User { 35 | bytes32 org; 36 | address owner; 37 | address parentOwner; 38 | address profile; 39 | string name; 40 | bool exists; 41 | } 42 | 43 | struct Node { 44 | bytes32 parent; 45 | address owner; 46 | address profile; 47 | string label; 48 | string proof; 49 | bytes32[] usersIndex; 50 | bytes32[] childrenIndex; 51 | bool exists; 52 | } 53 | 54 | struct AccountReverseDetails { 55 | bytes32 parent; // parentNode namehash 56 | string name; // human readable name of account 57 | bool claimed; 58 | } 59 | 60 | bytes32[] nodesIndex; 61 | mapping(bytes32 => Node) nodes; 62 | 63 | bytes32[] usersIndex; 64 | mapping(bytes32 => User) users; 65 | 66 | uint accountsCount; 67 | mapping(bytes32 => mapping(bytes32 => Account)) accounts; // namehashes to Accounts 68 | mapping(address => AccountReverseDetails) accountsReverse; // addresses to namehashes 69 | 70 | address genericProfileContract; 71 | 72 | string contractVersion; 73 | 74 | // Permits modifications only by the owner of the specified node. 75 | modifier only_owner(bytes32 nodeHash) { 76 | require(nodes[nodeHash].owner == msg.sender, "Not authorized to edit this node in the directory tree"); 77 | _; 78 | } 79 | 80 | /** 81 | * @dev Constructs a new Directory 82 | */ 83 | constructor(address profile) public { 84 | genericProfileContract = profile; 85 | nodes[0x0].owner = msg.sender; 86 | nodes[0x0].profile = genericProfileContract; 87 | nodes[0x0].label = ""; 88 | contractVersion = "2.0.0"; 89 | } 90 | 91 | /*******************************************************************************/ 92 | /************************ ACCOUNTS FUNCTIONS ***********************************/ 93 | /*******************************************************************************/ 94 | 95 | function generateHash(string name) internal pure returns (bytes32) { 96 | return keccak256(abi.encodePacked(name)); 97 | } 98 | 99 | /** 100 | * dev Sets value for given key as current value for caller 101 | * param key: namehash of human readable string 102 | * param _value: public key or other info 103 | */ 104 | function setAccount(bytes32 parentNode, string name, address _value) public { 105 | bytes32 grandParent = nodes[parentNode].parent; 106 | require(nodes[parentNode].owner == msg.sender || nodes[grandParent].owner == msg.sender, "Not authorized to edit this account"); 107 | 108 | setAccountVersion(parentNode, name, _value, "0"); 109 | } 110 | 111 | /** 112 | * dev Sets value and versionDescription for given key for caller 113 | * param parentNode: namehash of human readable string 114 | * param name: human readable string of node name 115 | * param value: public key or other info 116 | * param versionDescription: versionDescription of the key 117 | */ 118 | function setAccountVersion(bytes32 parentNode, string name, address value, string versionText) public { 119 | bytes32 grandParent = nodes[parentNode].parent; 120 | require(nodes[parentNode].owner == msg.sender || nodes[grandParent].owner == msg.sender, "Not authorized to edit this account"); 121 | 122 | AccountValue memory val; 123 | val.value = value; 124 | val.versionDescription = versionText; 125 | 126 | bytes32 key = generateHash(name); 127 | Account storage acct = accounts[parentNode][key]; 128 | acct.name = name; 129 | acct.parentName = nodes[parentNode].label; 130 | acct.values[acct.valuesCount] = val; 131 | acct.valuesCount++; 132 | 133 | if (!acct.exists) { 134 | accountsCount++; 135 | acct.exists = true; 136 | } 137 | 138 | emit NewAccountValue(parentNode, name, value, versionText); 139 | 140 | if (accountsReverse[value].claimed && nodes[accountsReverse[value].parent].owner != nodes[parentNode].owner) { 141 | emit CannotOverwriteAccount("Cannot overwrite account", value, nodes[parentNode].owner, msg.sender); 142 | } else { 143 | accountsReverse[value].parent = parentNode; 144 | accountsReverse[value].name = name; 145 | accountsReverse[value].claimed = true; 146 | } 147 | } 148 | 149 | function releaseAccount(bytes32 parentNode, address value) public { 150 | bytes32 grandParent = nodes[parentNode].parent; 151 | require(nodes[parentNode].owner == msg.sender || nodes[grandParent].owner == msg.sender, "Not authorized to edit this account"); 152 | 153 | accountsReverse[value].claimed = false; 154 | } 155 | 156 | /** 157 | * dev Gets latest value (e.g. public key) for given namehash 158 | * param key: namehash of human readable string 159 | */ 160 | function getLatestAccount(bytes32 parentNode, bytes32 key) view public returns (bytes32, string, string, address, string) { 161 | return getAccountVersion(parentNode, key, accounts[parentNode][key].valuesCount - 1); 162 | } 163 | 164 | function getLatestAccountByName(bytes32 parentNode, string name) view public returns (bytes32, string, string, address, string) { 165 | return getAccountVersion(parentNode, generateHash(name), accounts[parentNode][generateHash(name)].valuesCount - 1); 166 | } 167 | 168 | function getAccountVersionByName(bytes32 parentNode, string name, uint256 index) view public returns (bytes32, string, string, address, string) { 169 | return getAccountVersion(parentNode, generateHash(name), index); 170 | } 171 | 172 | /** 173 | * dev Gets value (e.g. public key) for given namehash at given index 174 | * param key: namehash of human readable string 175 | * param index: index to particular value within array 176 | */ 177 | function getAccountVersion(bytes32 parentNode, bytes32 key, uint256 index) view public returns (bytes32, string, string, address, string) { 178 | require(accounts[parentNode][key].exists, "Key does not exist."); 179 | require(index >= 0 && index < accounts[parentNode][key].valuesCount, "Key index does not exist."); 180 | 181 | return ( 182 | parentNode, 183 | accounts[parentNode][key].parentName, 184 | accounts[parentNode][key].name, 185 | accounts[parentNode][key].values[index].value, 186 | accounts[parentNode][key].values[index].versionDescription 187 | ); 188 | } 189 | 190 | function accountLookup(address owner) public view returns (bytes32, string, string, address, string) { 191 | return getLatestAccountByName(accountsReverse[owner].parent, accountsReverse[owner].name); 192 | } 193 | 194 | function versionsByName(bytes32 parentNode, string name) view public returns (uint256) { 195 | return versionsByKey(parentNode, generateHash(name)); 196 | } 197 | 198 | function versionsByKey(bytes32 parentNode, bytes32 key) view public returns (uint256) { 199 | return accounts[parentNode][key].valuesCount; 200 | } 201 | 202 | function existsByName(bytes32 parentNode, string name) view public returns (bool) { 203 | return existsByKey(parentNode, generateHash(name)); 204 | } 205 | 206 | function existsByKey(bytes32 parentNode, bytes32 key) view public returns (bool) { 207 | return (accounts[parentNode][key].valuesCount > 0); 208 | } 209 | 210 | function getAccountCount(bytes32 parentNode, bytes32 key) view public returns (uint256) { 211 | return accounts[parentNode][key].valuesCount; 212 | } 213 | 214 | function getAccountByIndex(bytes32 parentNode, bytes32 key, uint256 index) view public returns (string, address, string) { 215 | require(index < accounts[parentNode][key].valuesCount, 'index out of range'); 216 | return ( 217 | accounts[parentNode][key].name, 218 | accounts[parentNode][key].values[index].value, 219 | accounts[parentNode][key].values[index].versionDescription 220 | ); 221 | } 222 | 223 | /*******************************************************************************/ 224 | /********************** DIRECTORY TREE FUNCTIONS *******************************/ 225 | /*******************************************************************************/ 226 | 227 | /** 228 | * @dev Transfers ownership of a node to a new address. May only be called by the current owner of the node. 229 | * @param nodeHash The node to transfer ownership of. 230 | * @param owner The address of the new owner. 231 | */ 232 | function setNodeOwner(bytes32 nodeHash, address owner) public only_owner(nodeHash) { 233 | emit Transfer(nodeHash, owner); 234 | nodes[nodeHash].owner = owner; 235 | } 236 | 237 | /** 238 | * @dev Transfers ownership of an org keccak256(nodeHash, label) to a new address. May only be called by the owner of the parent node. 239 | * @param parentNode The parent node. 240 | * @param label The hash of the label specifying the subnode. 241 | * @param owner The address of the new owner. 242 | */ 243 | function setNodeDetails(bytes32 parentNode, string label, string proof, address owner) public only_owner(parentNode) { 244 | setNodeDetailsEx(parentNode, label, proof, owner, genericProfileContract); 245 | } 246 | 247 | function setNodeDetailsEx(bytes32 parentNode, string label, string proof, address owner, address profile) public only_owner(parentNode) { 248 | bytes32 orgNode = keccak256(abi.encodePacked(parentNode, keccak256(abi.encodePacked(label)))); 249 | emit NewOwner(parentNode, orgNode, owner, label, proof); 250 | 251 | nodes[orgNode].proof = proof; 252 | nodes[orgNode].label = string(abi.encodePacked(nodes[parentNode].label, "/", label)); 253 | nodes[orgNode].owner = owner; 254 | nodes[orgNode].parent = parentNode; 255 | nodes[orgNode].profile = profile; 256 | 257 | users[orgNode].owner = owner; 258 | users[orgNode].org = parentNode; 259 | users[orgNode].name = label; 260 | users[orgNode].profile = profile; 261 | 262 | if (!nodes[orgNode].exists) { 263 | nodes[orgNode].exists = true; 264 | nodesIndex.push(orgNode); 265 | nodes[parentNode].childrenIndex.push(orgNode); 266 | } 267 | 268 | // Update accounts and accountsReverse arrays 269 | setAccount(orgNode, "admin", owner); 270 | } 271 | 272 | function setUserDetails(bytes32 nodeHash, string name, address owner) public { 273 | setUserDetailsEx(nodeHash, name, owner); 274 | } 275 | 276 | function setUserDetailsEx(bytes32 nodeHash, string name, address owner) public { 277 | bytes32 userId = keccak256(abi.encodePacked(nodeHash, keccak256(abi.encodePacked(name)))); 278 | require(nodes[nodeHash].owner == msg.sender || users[userId].owner == msg.sender, "Not authorized to edit this user"); 279 | 280 | emit NewUser(userId, nodeHash, name, owner); 281 | 282 | // set user 283 | users[userId].owner = owner; 284 | users[userId].parentOwner = msg.sender; 285 | users[userId].org = nodeHash; 286 | users[userId].name = name; 287 | 288 | // Update accounts and accountsReverse arrays 289 | setAccount(nodeHash, name, owner); 290 | 291 | // setup indices 292 | if (!users[userId].exists) { 293 | users[userId].exists = true; 294 | nodes[nodeHash].usersIndex.push(userId); 295 | usersIndex.push(userId); 296 | } 297 | } 298 | 299 | function nodeDetails(bytes32 nodeHash) public view returns (address, string, bytes32, string, uint256, uint256, address) { 300 | return ( 301 | nodes[nodeHash].owner, 302 | nodes[nodeHash].label, 303 | nodes[nodeHash].parent, 304 | nodes[nodeHash].proof, 305 | nodes[nodeHash].usersIndex.length, 306 | nodes[nodeHash].childrenIndex.length, 307 | nodes[nodeHash].profile 308 | ); 309 | } 310 | 311 | function nodeLabel(bytes32 nodeHash) public view returns (string) { 312 | return nodes[nodeHash].label; 313 | } 314 | 315 | function nodeOwner(bytes32 nodeHash) public view returns (address) { 316 | return nodes[nodeHash].owner; 317 | } 318 | 319 | function nodeProof(bytes32 nodeHash) public view returns (string) { 320 | return nodes[nodeHash].proof; 321 | } 322 | 323 | function user(bytes32 userId) public view returns (bytes32, bytes32, address, string) { 324 | return ( 325 | userId, 326 | users[userId].org, 327 | users[userId].owner, 328 | users[userId].name 329 | ); 330 | } 331 | 332 | function userOwner(bytes32 userId) public view returns (address) { 333 | return users[userId].owner; 334 | } 335 | 336 | function userProfile(bytes32 userId) public view returns (address) { 337 | return users[userId].profile; 338 | } 339 | 340 | function userName(bytes32 userId) public view returns (string) { 341 | return users[userId].name; 342 | } 343 | 344 | function nodeUsersCount(bytes32 nodeHash) public view returns (uint256) { 345 | return nodes[nodeHash].usersIndex.length; 346 | } 347 | 348 | function nodeChildrenCount(bytes32 nodeHash) public view returns (uint256) { 349 | return nodes[nodeHash].childrenIndex.length; 350 | } 351 | 352 | function nodeUser(bytes32 nodeHash, uint8 index) public view returns (bytes32, bytes32, address, string) { 353 | require(index < nodes[nodeHash].usersIndex.length); 354 | bytes32 userId = nodes[nodeHash].usersIndex[index]; 355 | return user(userId); 356 | } 357 | 358 | function nodeChild(bytes32 parentNode, uint8 index) public view returns (bytes32, string) { 359 | require(index < nodes[parentNode].childrenIndex.length); 360 | bytes32 nodeId = nodes[parentNode].childrenIndex[index]; 361 | return ( 362 | nodeId, 363 | nodes[nodeId].label 364 | ); 365 | } 366 | 367 | // functions to allow iteration over all nodes or paging through a 368 | // list of nodes 369 | function nodesCount() public view returns (uint256) { 370 | return nodesIndex.length; 371 | } 372 | 373 | function nodeKey(uint8 index) public view returns (bytes32) { 374 | require(index < nodesIndex.length); 375 | return nodesIndex[index]; 376 | } 377 | 378 | function usersCount() public view returns (uint256) { 379 | return usersIndex.length; 380 | } 381 | 382 | function userKey(uint8 index) public view returns (bytes32) { 383 | require(index < usersIndex.length); 384 | return usersIndex[index]; 385 | } 386 | 387 | function getContractVersion() public view returns (string) { 388 | return contractVersion; 389 | } 390 | 391 | } -------------------------------------------------------------------------------- /deploy-transact/contracts/simplestorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.2; 2 | 3 | contract simplestorage { 4 | uint public storedData; 5 | 6 | event DataStored ( 7 | uint data 8 | ); 9 | 10 | constructor(uint initVal) public { 11 | storedData = initVal; 12 | } 13 | 14 | function set(uint x) public returns (uint value) { 15 | require(x < 100, "Value can not be over 100"); 16 | storedData = x; 17 | 18 | emit DataStored(x); 19 | 20 | return storedData; 21 | } 22 | 23 | function get() view public returns (uint retVal) { 24 | return storedData; 25 | } 26 | 27 | function query() view public returns (uint retVal) { 28 | return storedData; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /deploy-transact/contracts/simplestorage_v5.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract simplestorage { 4 | uint public storedData; 5 | 6 | event DataStored ( 7 | uint data 8 | ); 9 | 10 | constructor(uint initVal) public { 11 | storedData = initVal; 12 | } 13 | 14 | function set(uint x) public returns (uint value) { 15 | require(x < 100, "Value can not be over 100"); 16 | storedData = x; 17 | 18 | emit DataStored(x); 19 | 20 | return storedData; 21 | } 22 | 23 | function get() public view returns (uint retVal) { 24 | return storedData; 25 | } 26 | 27 | function query() public view returns (uint retVal) { 28 | return storedData; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /deploy-transact/lib/azure-signing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Tx = require('ethereumjs-tx'); 4 | const Web3 = require('web3'); 5 | const request = require('request'); 6 | const ExternalSigning = require('./ext-signing.js'); 7 | 8 | var KeyVault = require('azure-keyvault'); 9 | var AuthenticationContext = require('adal-node').AuthenticationContext; 10 | 11 | const argv = require('yargs').argv; 12 | const serviceName = argv.servicename; 13 | const keyName = argv.keyname; 14 | const keyVersion = argv.keyversion; 15 | 16 | class AzureKeyVaultSigning extends ExternalSigning { 17 | constructor(url, contractName) { 18 | super(url, contractName); 19 | 20 | this.clientId = process.env.CLIENT_ID; 21 | this.clientSecret = process.env.CLIENT_SECRET; 22 | this.directoryId = process.env.DIRECTORY_ID; 23 | 24 | if (!this.clientId) { 25 | throw new Error('Missing environment variable "CLIENT_ID"'); 26 | } 27 | 28 | if (!this.clientSecret) { 29 | throw new Error('Missing environment variable "CLIENT_SECRET"'); 30 | } 31 | 32 | if (!this.directoryId) { 33 | throw new Error('Missing environment variable "DIRECTORY_ID"'); 34 | } 35 | 36 | // Authenticator - retrieves the access token 37 | let self = this; 38 | this.authenticator = function (challenge, callback) { 39 | // Create a new authentication context. 40 | var context = new AuthenticationContext(challenge.authorization); 41 | 42 | // Use the context to acquire an authentication token. 43 | return context.acquireTokenWithClientCredentials(challenge.resource, self.clientId, self.clientSecret, function (err, tokenResponse) { 44 | if (err) throw err; 45 | // Calculate the value to be set in the request's Authorization header and resume the call. 46 | var authorizationValue = tokenResponse.tokenType + ' ' + tokenResponse.accessToken; 47 | 48 | return callback(null, authorizationValue); 49 | }); 50 | }; 51 | 52 | let creds = new KeyVault.KeyVaultCredentials(this.authenticator); 53 | this.client = new KeyVault.KeyVaultClient(creds); 54 | } 55 | 56 | async getAccount() { 57 | // must be built from the raw EC public key parameters retrieved from the key vault 58 | // reference: blog by Tomislav Markovski 59 | // https://tomislav.tech/2018-01-31-ethereum-keyvault-generating-keys/ 60 | let keyObject; 61 | try { 62 | keyObject = await this.client.getKey(`https://${serviceName}.vault.azure.net`, keyName, keyVersion); 63 | } catch(err) { 64 | throw new Error('Failed to retrieve the signing key from Azure', err); 65 | } 66 | 67 | let input = Buffer.concat([keyObject.key.x, keyObject.key.y]).toString('hex'); 68 | let hash = this.web3.utils.keccak256('0x' + input); 69 | return { 70 | address: '0x' + hash.slice(26) 71 | }; 72 | } 73 | 74 | async signTx(signedTx) { 75 | // signature on the tx is over the hash of the tx 76 | let hash = signedTx.hash(false); 77 | // now ask Azure to sign the hash 78 | let res; 79 | 80 | // reference: blog by Tomislav Markovski 81 | // https://tomislav.tech/2018-02-05-ethereum-keyvault-signing-transactions/ 82 | try { 83 | // The output of this will be a 64 byte array. The first 32 are the value for R and the rest is S. 84 | res = await this.client.sign(`https://${serviceName}.vault.azure.net`, keyName, keyVersion, 'ECDSA256', Buffer.from(hash)); 85 | } catch(err) { 86 | throw new Error('Failed to get signature from the signing service', err); 87 | } 88 | 89 | // standard ethereum signature object has "r", "s" and "v" 90 | let sig = { 91 | r: res.result.slice(0, 32), 92 | s: res.result.slice(32) 93 | } 94 | 95 | // find the recovery ID by trying the possible values (0, 1, 2, 3) with "recover" 96 | let account = await this.getAccount(); 97 | console.log(`\tRetrieved account from Azure key vault: ${account.address}`); 98 | let recoverId; 99 | for (let i of [0, 1, 2, 3]) { 100 | let recovered = this.web3.eth.accounts.recover({ 101 | messageHash: '0x' + hash.toString('hex'), 102 | v: '0x' + (i + 27).toString(16), 103 | r: '0x' + sig.r.toString('hex'), 104 | s: '0x' + sig.s.toString('hex') 105 | }); 106 | 107 | if (recovered.toLowerCase() === account.address.toLowerCase()) { 108 | recoverId = i; 109 | break; 110 | } 111 | } 112 | 113 | sig.v = recoverId + 27; 114 | 115 | if (signedTx._chainId > 0) { 116 | sig.v += signedTx._chainId * 2 + 8; 117 | } 118 | Object.assign(signedTx, sig); 119 | 120 | return signedTx; 121 | } 122 | } 123 | 124 | module.exports = AzureKeyVaultSigning; 125 | -------------------------------------------------------------------------------- /deploy-transact/lib/besu-node-signing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EEAClient = require("web3-eea"); 4 | const Web3 = require('web3'); 5 | const axios = require('axios'); 6 | const solc = require('solc'); 7 | const join = require('path').join; 8 | const fs = require('fs-extra'); 9 | const argv = require('yargs').argv; 10 | const verbose = argv.verbose; 11 | const { getContract }= require('./utils.js'); 12 | const NodeSigning = require('./node-signing.js'); 13 | const chainId = argv.chainId || 2018; 14 | class PantheonNodeSigningHandler extends NodeSigning{ 15 | constructor(url, contractName) { 16 | super(url,contractName); 17 | this.web3 = new EEAClient(new Web3(url), chainId); 18 | this.abi; 19 | this.bytecode; 20 | this.account; 21 | } 22 | 23 | async getPrivateNonce(account, privacyGroupId, privateFrom, privateFor) { 24 | let privacyOptions = {}; 25 | if(privacyGroupId !== undefined) { 26 | privacyOptions.privacyGroupId = privacyGroupId; 27 | } else { 28 | privacyOptions.privateFor = privateFor.split(','); 29 | } 30 | privacyOptions.privateFrom = privateFrom; 31 | return this.web3.priv.getTransactionCount({ 32 | ...privacyOptions, 33 | from: account 34 | }); 35 | } 36 | 37 | async getPrivacyGroups(addresses){ 38 | let res = await this.web3.priv.findPrivacyGroup({ 39 | addresses: addresses.split(',') 40 | }); 41 | console.log(`Privacy groups for ${addresses} is ${JSON.stringify(res)}`); 42 | } 43 | 44 | async createPrivacyGroup(addresses){ 45 | console.log(`Supplied addresses: ${addresses.split(',')}`); 46 | let res = await this.web3.priv.createPrivacyGroup({ 47 | addresses: addresses.split(',') 48 | }); 49 | console.log(`Privacy group with ID ${res} created for ${addresses}`); 50 | } 51 | 52 | async deletePrivacyGroup(privacyGroupId){ 53 | let res = await this.web3.priv.deletePrivacyGroup({ 54 | privacyGroupId 55 | }); 56 | console.log(`Privacy group with ID ${privacyGroupId} deleted.`); 57 | } 58 | 59 | async deployContract(privateFor,privateFrom,privacyGroupId=undefined) { 60 | console.log(`Deploying contract.. PrivateFor: ${privateFor}, PrivateFrom: ${privateFrom}, Privacy group ID ${privacyGroupId}.`); 61 | this.account = await this.getAccount(); 62 | let contractDetails = await getContract(null,this.contractName,null,true); 63 | this.abi = contractDetails.abi; 64 | this.bytecode = contractDetails.bytecode; 65 | let rpcInstance = axios.create({ 66 | baseURL: `${this.url}`, 67 | }); 68 | // get private nonce for the account 69 | let privateNonce = await this.getPrivateNonce(this.account, privacyGroupId, privateFrom,privateFor); 70 | let body ={ 71 | "jsonrpc":"2.0", 72 | "method":"eea_sendTransaction", 73 | "params":[{ 74 | "from": this.account, 75 | "data": this.bytecode, 76 | "nonce": `0x${privateNonce.toString(16)}`, 77 | "privateFrom":privateFrom, 78 | "restriction": "restricted" 79 | }], 80 | "id":1 81 | }; 82 | if(privacyGroupId !== undefined) { 83 | body.params[0].privacyGroupId = privacyGroupId; 84 | } else { 85 | body.params[0].privateFor = privateFor.split(','); 86 | } 87 | console.log('=> Deploying smart contract'); 88 | try{ 89 | let res = await rpcInstance.post('', body); 90 | if(verbose){ 91 | console.log(`Received repsonse`,res.data); 92 | } 93 | let txHash = res.data.result; 94 | let txReceipt = await this.web3.priv.getTransactionReceipt(txHash, privateFrom); 95 | if(verbose){ 96 | console.log(`Transaction receipt`,txReceipt); 97 | } 98 | console.log(`\tSmart contract deployed, ready to take calls at "${txReceipt.contractAddress}"`); 99 | }catch(error){ 100 | console.error('\tFailed to deploy the smart contract. Error: ' + error); 101 | process.exit(1); 102 | } 103 | } 104 | 105 | async sendTransaction(contractAddress, newValue, privateFor,privateFrom, privacyGroupId=undefined) { 106 | console.log(`Send Transaction.. PrivateFor: ${privateFor}, PrivateFrom: ${privateFrom}, Privacy group ID ${privacyGroupId}.`); 107 | this.account = await this.getAccount(); 108 | let contractDetails = await getContract(null,this.contractName,null,true); 109 | this.abi = contractDetails.abi; 110 | this.bytecode = contractDetails.bytecode; 111 | const func = this.abi.find(f => f.name === 'set'); 112 | const callData = this.web3.eth.abi.encodeFunctionCall(func, ['' + newValue]); 113 | let privateNonce = await this.getPrivateNonce(this.account, privacyGroupId, privateFrom,privateFor); 114 | let rpcInstance = axios.create({ 115 | baseURL: `${this.url}`, 116 | }); 117 | let body ={ 118 | "jsonrpc":"2.0", 119 | "method":"eea_sendTransaction", 120 | "params":[{ 121 | "from": this.account, 122 | "to": contractAddress, 123 | "data": callData, 124 | "nonce":`0x${privateNonce.toString(16)}`, 125 | "privateFrom":privateFrom, 126 | "restriction": "restricted" 127 | }], 128 | "id":1 129 | }; 130 | if(privacyGroupId !== undefined) { 131 | body.params[0].privacyGroupId = privacyGroupId; 132 | } else { 133 | body.params[0].privateFor = privateFor.split(','); 134 | } 135 | console.log('=> Setting state to new value'); 136 | try{ 137 | let res = await rpcInstance.post('', body); 138 | if(verbose){ 139 | console.log(`Received repsonse`,res.data); 140 | } 141 | let txHash = res.data.result; 142 | let txReceipt = await this.web3.priv.getTransactionReceipt(txHash, privateFrom); 143 | if(verbose){ 144 | console.log(`Transaction receipt`,txReceipt); 145 | } 146 | console.log(`\tNew value set to: ${newValue}`); 147 | console.log('\nDONE!\n'); 148 | }catch(err){ 149 | console.log(`Unexpected response`, err); 150 | } 151 | 152 | } 153 | 154 | async queryTransaction(contractAddress, privateFor, privateFrom, privacyGroupId=undefined) { 155 | this.account = await this.getAccount(); 156 | let contractDetails = await getContract(null,this.contractName,null,true); 157 | this.abi = contractDetails.abi; 158 | this.bytecode = contractDetails.bytecode; 159 | const func = this.abi.find(f => f.name === 'query'); 160 | const callData = this.web3.eth.abi.encodeFunctionCall(func,[]); 161 | let privateNonce = await this.getPrivateNonce(this.account, privacyGroupId, privateFrom,privateFor); 162 | let rpcInstance = axios.create({ 163 | baseURL: `${this.url}`, 164 | }); 165 | let body ={ 166 | "jsonrpc":"2.0", 167 | "method":"eea_sendTransaction", 168 | "params":[{ 169 | "from": this.account, 170 | "to": contractAddress, 171 | "data": callData, 172 | "nonce": `0x${privateNonce.toString(16)}`, 173 | "privateFrom":privateFrom, 174 | "restriction": "restricted" 175 | }], 176 | "id":1 177 | }; 178 | if(privacyGroupId !== undefined) { 179 | body.params[0].privacyGroupId = privacyGroupId; 180 | } else { 181 | body.params[0].privateFor = privateFor.split(','); 182 | } 183 | console.log(`=> Calling smart contract at "${contractAddress}" for current state value`); 184 | try{ 185 | let res = await rpcInstance.post('', body); 186 | if(verbose){ 187 | console.log(`Received repsonse`,res.data); 188 | } 189 | let txHash = res.data.result; 190 | let txReceipt = await this.web3.priv.getTransactionReceipt(txHash, privateFrom); 191 | let value = txReceipt.output; 192 | console.log('\tSmart contract current state: %j', value); 193 | console.log('\nDONE!\n'); 194 | }catch(err){ 195 | console.log(`Unexpected response`, err); 196 | } 197 | } 198 | } 199 | 200 | module.exports = PantheonNodeSigningHandler; 201 | -------------------------------------------------------------------------------- /deploy-transact/lib/ext-signing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const join = require('path').join; 4 | const fs = require('fs-extra'); 5 | const Tx = require('ethereumjs-tx'); 6 | const Web3 = require('web3'); 7 | const os = require('os'); 8 | const request = require('request'); 9 | 10 | const { getContract } = require('./utils.js'); 11 | 12 | const argv = require('yargs').argv; 13 | const verbose = argv.verbose; 14 | const chainId = argv.chainId; 15 | const hdwalletUrl = argv.hdwalletUrl; 16 | const hdwalletId = argv.hdwalletId; 17 | const hdwalletAccountIndex = argv.hdwalletAccountIndex; 18 | 19 | class ExternalSigningHandler { 20 | constructor(url, contractName) { 21 | this.web3 = new Web3(new Web3.providers.HttpProvider(url)); 22 | this.url = url; 23 | this.contractName = contractName; 24 | } 25 | 26 | isHDWallet() { 27 | return hdwalletId && hdwalletUrl && hdwalletAccountIndex >= 0; 28 | } 29 | 30 | async getAccount() { 31 | if (this.isHDWallet()) { 32 | console.log('=> Fetching hd wallet account to use') 33 | try { 34 | let result = await request(`${hdwalletUrl}/wallets/${hdwalletId}/accounts/${hdwalletAccountIndex}`); 35 | return JSON.parse(result); 36 | } catch (err) { 37 | console.err('\tFailed to find accounts in HD Wallet'); 38 | throw err; 39 | } 40 | } else { 41 | // look inside the home folder for a previously saved local account 42 | let localAccountJSON = join(os.homedir(), '.web3keystore', 'local-account.json'); 43 | await fs.ensureDir(join(os.homedir(), '.web3keystore')); 44 | 45 | let account; 46 | try { 47 | console.log(`=> Loading local account from keystore ${localAccountJSON}`); 48 | fs.statSync(localAccountJSON); 49 | const accountJSON = JSON.parse(fs.readFileSync(localAccountJSON).toString()); 50 | account = this.web3.eth.accounts.decrypt(accountJSON, ''); 51 | } catch(err) { 52 | console.log("\tLocal account does not exist. Will be generated."); 53 | account = this.web3.eth.accounts.create(); 54 | const accountJSON = this.web3.eth.accounts.encrypt(account.privateKey, ''); 55 | fs.writeFileSync(localAccountJSON, JSON.stringify(accountJSON)); 56 | } 57 | 58 | console.log(`\tFound account in the keystore: ${account.address}`); 59 | return account; 60 | } 61 | } 62 | 63 | async deployContract(privateFor) { 64 | if (privateFor) 65 | throw new Error('Private transactions are not supported with external signing'); 66 | 67 | let theContract = getContract(this.web3, this.contractName); 68 | let callData = theContract.encodeABI(); 69 | 70 | let callback = (newInstance) => { 71 | // smart contract deployed, ready to invoke it 72 | console.log(`\tSmart contract deployed, ready to take calls at "${newInstance.contractAddress}"`); 73 | }; 74 | 75 | await this._sendTransaction(null, callData, callback); 76 | } 77 | 78 | async sendTransaction(contractAddress, newValue, privateFor) { 79 | if (privateFor) 80 | throw new Error('Private transactions are not supported with external signing'); 81 | 82 | let theContract = getContract(this.web3, this.contractName, contractAddress); 83 | const abi = theContract.options.jsonInterface; 84 | const func = abi.find(f => f.name === 'set'); 85 | const callData = this.web3.eth.abi.encodeFunctionCall(func, ['' + newValue]); // 2nd function in the abi is the "set" 86 | 87 | let callback = (receipt) => { 88 | if (receipt.status) { 89 | console.log(`\tSet new value to ${newValue}`); 90 | console.log('\nDONE!\n'); 91 | } else { 92 | console.err('\tTransaction failed'); 93 | } 94 | }; 95 | 96 | await this._sendTransaction(contractAddress, callData, callback); 97 | } 98 | 99 | async _sendTransaction(contractAddress, callData, callback) { 100 | const account = await this.getAccount(); 101 | let nonce = await this.web3.eth.getTransactionCount(account.address); 102 | 103 | let params = { 104 | data: callData 105 | }; 106 | 107 | let defaultGas = 50000; 108 | if (contractAddress) { 109 | params.to = contractAddress; 110 | defaultGas = 500000; 111 | } 112 | 113 | params.gas = await this.estimateGas(params, defaultGas); 114 | 115 | params.nonce = '0x' + nonce.toString(16); 116 | params.gasPrice = 0; 117 | 118 | if (chainId) 119 | params.chainId = chainId; 120 | 121 | let signedTx = new Tx(params); 122 | 123 | console.log(`=> Externally signing the contract deploy`); 124 | 125 | signedTx = await this.signTx(signedTx, account); 126 | let serializedTx = signedTx.serialize(); 127 | 128 | let payload = '0x' + serializedTx.toString('hex'); 129 | console.log(`\tSigned payload: ${payload}\n`); 130 | 131 | this.web3.eth.sendSignedTransaction(payload) 132 | .on('receipt', (receipt) => { 133 | if (verbose) 134 | console.log(receipt); 135 | }) 136 | .on('error', (err) => { 137 | console.error('Failed to execute the transaction. Error: ' + err); 138 | process.exit(1); 139 | }) 140 | .then(callback); 141 | } 142 | 143 | signTx(signedTx, account) { 144 | let privateKey = Buffer.from(this.isHDWallet() ? account.privateKey : account.privateKey.slice(2), 'hex'); 145 | signedTx.sign(privateKey); 146 | return Promise.resolve(signedTx); 147 | } 148 | 149 | async estimateGas(param, defaultValue) { 150 | let gas; 151 | try { 152 | console.log('=> Estimating gas cost'); 153 | gas = await this.web3.eth.estimateGas(param); 154 | console.log(`\t${gas} (to be inflated by 10%)`); 155 | gas += Math.ceil(gas * 0.1); 156 | } catch(err) { 157 | console.error(`\tFailed to estimate gas, defaulting to ${defaultValue}`, err); 158 | gas = defaultValue; 159 | } 160 | 161 | return gas; 162 | } 163 | } 164 | 165 | module.exports = ExternalSigningHandler; 166 | -------------------------------------------------------------------------------- /deploy-transact/lib/node-signing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Web3 = require('web3'); 4 | const https = require('https'); 5 | const { getContract, estimateGas, getChainId } = require('./utils.js'); 6 | const argv = require('yargs').argv; 7 | const verbose = argv.verbose; 8 | 9 | class NodeSigningHandler { 10 | constructor(url, contractName) { 11 | const web3agent = https.Agent({ 12 | keepAlive: true, 13 | maxSocket: 5, 14 | }); 15 | 16 | const options = { 17 | agent: { 18 | https: web3agent, 19 | } 20 | }; 21 | this.web3 = new Web3(new Web3.providers.HttpProvider(url, options)); 22 | this.url = url; 23 | this.contractName = contractName; 24 | } 25 | 26 | async getAccount() { 27 | console.log(`=> Connecting to target node: ${this.url}`); 28 | let accounts = await this.web3.eth.getAccounts(); 29 | if (!accounts || accounts.length === 0) { 30 | console.error("\tCan't find accounts in the target node"); 31 | process.exit(1); 32 | } 33 | 34 | console.log(`\tFound account in the target node: ${accounts[0]}`); 35 | return accounts[0]; 36 | } 37 | 38 | async deployContract(privateFor) { 39 | let theContract = getContract(this.web3, this.contractName); 40 | let account = await this.getAccount(); 41 | 42 | let params = { 43 | from: account, 44 | gasPrice: 0, 45 | gas: await estimateGas(theContract, 500000), 46 | transactionConfirmationBlocks: 1, 47 | chain: await getChainId(this.web3), 48 | }; 49 | 50 | if (privateFor) { 51 | params.privateFor = JSON.parse(privateFor); 52 | } 53 | 54 | console.log('=> Deploying smart contract'); 55 | theContract.send(params) 56 | .on('receipt', (receipt) => { 57 | if (verbose) 58 | console.log(receipt); 59 | }) 60 | .on('error', (err) => { 61 | console.error('\tFailed to deploy the smart contract. Error: ' + err); 62 | process.exit(1); 63 | }) 64 | .then((newInstance) => { 65 | // smart contract deployed, ready to invoke it 66 | console.log(`\tSmart contract deployed, ready to take calls at "${newInstance._address}"`); 67 | }); 68 | } 69 | 70 | async sendTransaction(contractAddress, newValue, privateFor) { 71 | let theContract = getContract(this.web3, this.contractName, contractAddress); 72 | let account = await this.getAccount(); 73 | 74 | let params = { 75 | from: account, 76 | gas: await estimateGas(theContract.methods.set(newValue), 500000) 77 | }; 78 | 79 | if (privateFor) { 80 | params.privateFor = JSON.parse(privateFor); 81 | } 82 | 83 | console.log('=> Setting state to new value'); 84 | theContract.methods.set(newValue).send(params) 85 | .on('receipt', (receipt) => { 86 | if (verbose) 87 | console.log(receipt); 88 | console.log(`\tNew value set to: ${newValue}`); 89 | console.log('\n\tDONE!\n'); 90 | }) 91 | .on('error', (err, receipt) => { 92 | console.error(`\tTransaction failed ${JSON.stringify(receipt)}. Error: ${err.toString()}`); 93 | process.exit(1); 94 | }); 95 | 96 | } 97 | 98 | async getTransactionOutput(contractAddress, newValue) { 99 | let theContract = getContract(this.web3, this.contractName, contractAddress); 100 | let account = await this.getAccount(); 101 | 102 | let params = { 103 | from: account, 104 | gas: await estimateGas(theContract.methods.set(newValue), 500000) 105 | }; 106 | 107 | console.log('=> Invoking eth_call with new value'); 108 | theContract.methods.set(newValue).call(params) 109 | .on('error', (err) => { 110 | console.error(`\teth_call failed, Error: ${err.toString()}`); 111 | process.exit(1); 112 | }) 113 | .then((result) => { 114 | console.log(`\tNew value set to: ${JSON.stringify(result, null, 2)}`); 115 | console.log('\n\tDONE!\n'); 116 | }) 117 | 118 | console.log('\nDONE!\n'); 119 | } 120 | } 121 | 122 | module.exports = NodeSigningHandler; -------------------------------------------------------------------------------- /deploy-transact/lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs-extra'); 4 | const join = require('path').join; 5 | const solc = require('solc'); 6 | 7 | function getContract(web3, contractName, address, besu_private=false) { 8 | let tsSrc = fs.statSync(join(__dirname, `../contracts/${contractName}.sol`)); 9 | let tsBin; 10 | 11 | try { 12 | tsBin = fs.statSync(join(__dirname, `../contracts/${contractName}.bin`)); 13 | } catch(err) { 14 | console.log("Compiled contract does not exist. Will be generated."); 15 | } 16 | 17 | let compiled; 18 | if (!tsBin || tsSrc.mtimeMs > tsBin.mtimeMs) { 19 | // source file has been modified since the last compile 20 | let data = fs.readFileSync(join(__dirname, `../contracts/${contractName}.sol`), 'utf8'); 21 | let input = { 22 | language: 'Solidity', 23 | sources: {}, 24 | settings: { 25 | outputSelection: { 26 | '*': { 27 | '*': ['*'] 28 | } 29 | } 30 | } 31 | }; 32 | input.sources[`${contractName}.sol`] = { 33 | content: data 34 | }; 35 | 36 | try { 37 | compiled = JSON.parse(solc.compile(JSON.stringify(input))); 38 | } catch(err) { 39 | console.log(`Could not compile ${JSON.stringify(err)}`); 40 | } 41 | 42 | fs.writeFileSync(join(__dirname, `../contracts/${contractName}.bin`), JSON.stringify(compiled)); 43 | } else { 44 | compiled = JSON.parse(fs.readFileSync(join(__dirname, `../contracts/${contractName}.bin`), 'utf8').toString()); 45 | } 46 | 47 | let contract = compiled.contracts[`${contractName}.sol`][`${contractName}`]; 48 | let abi = contract.abi; 49 | let bytecode = '0x' + contract.evm.bytecode.object; 50 | 51 | if(besu_private){ 52 | return {"bytecode":bytecode,"abi":abi}; 53 | } 54 | 55 | let ret = new web3.eth.Contract(abi, address); 56 | if (!address) { 57 | // this is a new deployment, build the deploy object 58 | ret = ret.deploy({ 59 | data: bytecode, 60 | arguments: [10] 61 | }); 62 | } 63 | 64 | return ret; 65 | } 66 | 67 | async function estimateGas(contractOrMethod, defaultValue) { 68 | let gas; 69 | try { 70 | gas = await contractOrMethod.estimateGas(); 71 | console.log(`\t${gas} (to be inflated by 10%)`); 72 | gas += Math.ceil(gas * 0.1); 73 | } catch(err) { 74 | console.error(`\tFailed to estimate gas, defaulting to ${defaultValue}`, err); 75 | gas = defaultValue; 76 | } 77 | console.log('=> Estimated gas cost', gas); 78 | return gas; 79 | } 80 | 81 | async function getChainId(web3) { 82 | let id = await web3.eth.net.getId(); 83 | console.log(`=> Chain ID: ${id}`); 84 | return id; 85 | } 86 | 87 | module.exports = { 88 | getContract, 89 | estimateGas, 90 | getChainId 91 | }; 92 | -------------------------------------------------------------------------------- /deploy-transact/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quorum-tests", 3 | "version": "0.0.1", 4 | "description": "Quorum tests", 5 | "scripts": { 6 | "run": "node ./test.js" 7 | }, 8 | "dependencies": { 9 | "@azure/keyvault": "^0.1.0", 10 | "@azure/ms-rest-nodeauth": "^1.0.1", 11 | "adal-node": "^0.1.28", 12 | "azure-keyvault": "^3.0.4", 13 | "ethereumjs-tx": "^1.3.3", 14 | "fs-extra": "^10.0.0", 15 | "solc": "^0.5.2", 16 | "web3": "^1.5.2", 17 | "web3-eea": "^0.11.0", 18 | "web3-utils": "^1.5.2", 19 | "yargs": "^17.1.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /deploy-transact/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const yargs = require('yargs/yargs'); 4 | const { hideBin } = require('yargs/helpers'); 5 | const argv = yargs(hideBin(process.argv)) 6 | .option('contract', { 7 | type: 'string' 8 | }).argv; 9 | const Web3 = require('web3'); 10 | const https = require('https'); 11 | const { getContract } = require('./lib/utils.js'); 12 | 13 | const contractAddress = argv.contract; 14 | const url = argv.url; 15 | const ws = argv.ws; 16 | const query = argv.query; 17 | const deploy = argv.deploy; 18 | const set = argv.set; 19 | const privateFor = argv.privateFor; 20 | const privateFrom = argv.privateFrom; 21 | const externallySign = argv.sign; 22 | const azure = argv.azure; 23 | const besu_private = argv.besu_private; 24 | const privacy_groups = argv.privacy_groups; 25 | const find = argv.find; 26 | const create = argv.create; 27 | const destroy = argv.destroy; 28 | 29 | const addresses = argv.addresses; 30 | // besu privacy group 31 | const privacyGroupId = argv.privacyGroupId; 32 | let contractName = 'simplestorage'; 33 | 34 | if (query) { 35 | if (besu_private) { 36 | getSigner().queryTransaction(contractAddress, privateFor, privateFrom, privacyGroupId); 37 | 38 | } else { 39 | // must also pass in the contract address 40 | if (!contractAddress) { 41 | console.error('For querying smart contract states, you must pass in the contract address using the "--contract=" argument'); 42 | process.exit(1); 43 | } 44 | 45 | console.log(`=> Calling smart contract at "${contractAddress}" for current state value`); 46 | let web3 = new Web3(new Web3.providers.HttpProvider(url)); 47 | let theContract = getContract(web3, contractName, contractAddress); 48 | 49 | theContract.methods.query().call() 50 | .then((value) => { 51 | console.log('\tSmart contract current state: %j', value); 52 | console.log('\nDONE!\n'); 53 | }); 54 | } 55 | 56 | } else if (set) { 57 | // must also pass in the contract address 58 | if (!contractAddress) { 59 | console.error('For querying smart contract states, you must pass in the contract address using the "--contract=" argument'); 60 | process.exit(1); 61 | } 62 | 63 | let newValue = set; 64 | if(besu_private) { 65 | getSigner().sendTransaction(contractAddress, newValue, privateFor, privateFrom, privacyGroupId); 66 | } else { 67 | getSigner().sendTransaction(contractAddress, newValue, privateFor, privateFrom); 68 | } 69 | listen(); 70 | 71 | } else if (deploy) { 72 | if(besu_private) { 73 | getSigner().deployContract(privateFor,privateFrom,privacyGroupId); 74 | } else { 75 | getSigner().deployContract(privateFor,privateFrom); 76 | } 77 | } else if(besu_private && privacy_groups) { 78 | if(find) 79 | getSigner().getPrivacyGroups(addresses); 80 | if(create) 81 | getSigner().createPrivacyGroup(addresses); 82 | if(destroy) 83 | getSigner().deletePrivacyGroup(privacyGroupId); 84 | } 85 | 86 | function getSigner() { 87 | let Clazz; 88 | if (externallySign) { 89 | Clazz = require('./lib/ext-signing.js'); 90 | } else if (azure) { 91 | Clazz = require('./lib/azure-signing.js'); 92 | } else if(besu_private){ 93 | Clazz = require('./lib/besu-node-signing.js'); 94 | }else { 95 | Clazz = require('./lib/node-signing.js'); 96 | } 97 | 98 | return new Clazz(url, contractName); 99 | } 100 | 101 | function listen() { 102 | if (ws) { 103 | const web3agent = https.Agent({ 104 | keepAlive: true, 105 | maxSocket: 5, 106 | }); 107 | 108 | const options = { 109 | agent: { 110 | https: web3agent, 111 | } 112 | }; 113 | 114 | let web3 = new Web3(new Web3.providers.WebsocketProvider(ws, options)); 115 | let theContract = getContract(web3, contractName, contractAddress); 116 | theContract.once('DataStored', (err, event) => { 117 | if (err) 118 | console.error('Error subscribing to "DataStored" events', err); 119 | else 120 | console.log('Event published', event); 121 | }); 122 | } 123 | } -------------------------------------------------------------------------------- /log-analyzers/README.md: -------------------------------------------------------------------------------- 1 | # Log Analyzers 2 | 3 | Parses logs from the Quorum and Geth nodes and generate charts for these metrics: 4 | * chart 1 5 | * transactions included in blocks by time stamp 6 | * total transactions included in blocks in 30sec intervals 7 | 8 | * chart 2 9 | * transactions pool queues: executables, non-executables, stales 10 | 11 | ## Run It 12 | 13 | Due to a security vulnerability in a dependency of the latest version of plotly, it's not included in the package.json. Make sure to install it manually as shown below. 14 | 15 | ``` 16 | npm install plotly 17 | npm install 18 | PLOT_USER= PLOT_PWD= INPUT= node geth-poa-logs.js 19 | ``` 20 | 21 | You should see output like the following: 22 | ``` 23 | ... 24 | Txs at 05-10|14:09:02: 28 25 | Txs at 05-10|14:09:07: 100 26 | Txs at 05-10|14:09:07: 61 27 | Txs at 05-10|14:09:12: 57 28 | Txs at 05-10|14:09:12: 72 29 | Txs at 05-10|14:09:17: 80 30 | Txs at 05-10|14:09:17: 75 31 | Txs at 05-10|14:09:22: 38 32 | Txs at 05-10|14:09:27: 77 33 | Txs at 05-10|14:09:27: 17 34 | Txs at 05-10|14:09:33: 33 35 | Txs at 05-10|14:09:33: 93 36 | Txs at 05-10|14:09:34: 30 37 | Txs at 05-10|14:09:34: 107 38 | Txs at 05-10|14:09:37: 101 39 | Txs at 05-10|14:09:37: 46 40 | Txs at 05-10|14:09:42: 51 41 | Txs at 05-10|14:09:42: 62 42 | Txs at 05-10|14:09:42: 32 43 | Txs at 05-10|14:09:42: 4 44 | Txs at 05-10|14:09:48: 113 45 | Pool at 05-10|14:09:54: executable - 22, non-executable - 0, stale - 2 46 | Pool at 05-10|14:10:02: executable - 83, non-executable - 0, stale - 26 47 | Pool at 05-10|14:10:10: executable - 107, non-executable - 0, stale - 1 48 | Pool at 05-10|14:10:18: executable - 85, non-executable - 0, stale - 11 49 | Pool at 05-10|14:10:26: executable - 127, non-executable - 0, stale - 16 50 | Pool at 05-10|14:10:34: executable - 66, non-executable - 0, stale - 6 51 | Pool at 05-10|14:10:42: executable - 79, non-executable - 0, stale - 23 52 | Pool at 05-10|14:10:50: executable - 108, non-executable - 20, stale - 34 53 | Pool at 05-10|14:10:58: executable - 66, non-executable - 32, stale - 23 54 | Txs at 05-10|14:09:49: 92 55 | Txs at 05-10|14:09:49: 32 56 | Txs at 05-10|14:09:52: 4 57 | Txs at 05-10|14:09:52: 32 58 | Txs at 05-10|14:09:52: 5 59 | Txs at 05-10|14:09:57: 3 60 | Txs at 05-10|14:09:57: 62 61 | ... 62 | Chart available at: https://plot.ly/~/49 63 | Chart available at: https://plot.ly/~/51 64 | ``` 65 | 66 | ## Example charts created by the tool: 67 | 68 | Showing transaction rates per block and per 30sec period aggregates: 69 | ![tx-rates.jpg](images/tx-rates.jpg) 70 | 71 | Showing transaction pool status: 72 | ![txpool status](images/txpool.jpg) 73 | 74 | -------------------------------------------------------------------------------- /log-analyzers/geth-poa-logs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const argv = require('yargs').argv; 5 | const { Writable } = require('stream'); 6 | const Plotly = require('plotly'); 7 | 8 | const INTERVAL = (argv.interval || 30) * 1000; 9 | 10 | 11 | let plotly; 12 | if (process.env.PLOT_USER && process.env.PLOT_PWD) { 13 | plotly = Plotly(process.env.PLOT_USER, process.env.PLOT_PWD); 14 | } 15 | 16 | const input = argv.input; 17 | if (!input) { 18 | console.error('Input log file required. Use "--input" to tell the program which file to process.'); 19 | process.exit(1); 20 | } 21 | 22 | const src = fs.createReadStream(input); 23 | 24 | let pooltimes = [], executables = [], nonexecutables = [], stales = [], txtimes = [], txs = []; 25 | const outStream = new Writable({ 26 | write(chunk, encoding, callback) { 27 | let lines = chunk.toString(); 28 | let matches = lines.match(/\[([0-9]{2}-[0-9]{2}\|[0-9]{2}:[0-9]{2}:[0-9]{2})\]\s+Transaction pool status report\s+executable=(\d+)\s+queued=(\d+)\s+stales=(\d+)/g); 29 | if (matches) { 30 | // there should be more than one matches, now parse each match to get the details 31 | for (let i=0; i { 57 | let exec = { 58 | x: pooltimes, 59 | y: executables, 60 | name: 'txpool - executables', 61 | type: 'scatter' 62 | }; 63 | 64 | let nonexec = { 65 | x: pooltimes, 66 | y: nonexecutables, 67 | name: 'txpool - non-executables', 68 | type: 'scatter' 69 | }; 70 | 71 | let stale = { 72 | x: pooltimes, 73 | y: stales, 74 | name: 'txpool - stales', 75 | type: 'scatter' 76 | }; 77 | 78 | let averageTimestamps = [], averageTimes = [], averageTxs = []; 79 | for (let i=0; i