From 5e933c10cbddfceb9b544c6a5cefbfe101594548 Mon Sep 17 00:00:00 2001 From: Varad Ramamoorthy Date: Wed, 30 Jun 2021 16:07:54 -0400 Subject: [PATCH 01/15] expose operations port (#453) Signed-off-by: Varad Ramamoorthy --- test-network/docker/docker-compose-ca.yaml | 6 ++++++ test-network/docker/docker-compose-test-net.yaml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/test-network/docker/docker-compose-ca.yaml b/test-network/docker/docker-compose-ca.yaml index 95034448..67f17a0c 100644 --- a/test-network/docker/docker-compose-ca.yaml +++ b/test-network/docker/docker-compose-ca.yaml @@ -20,8 +20,10 @@ services: - FABRIC_CA_SERVER_CA_NAME=ca-org1 - FABRIC_CA_SERVER_TLS_ENABLED=true - FABRIC_CA_SERVER_PORT=7054 + - FABRIC_CA_SERVER_OPERATIONS_LISTENADDRESS=0.0.0.0:17054 ports: - "7054:7054" + - "17054:17054" command: sh -c 'fabric-ca-server start -b admin:adminpw -d' volumes: - ../organizations/fabric-ca/org1:/etc/hyperledger/fabric-ca-server @@ -38,8 +40,10 @@ services: - FABRIC_CA_SERVER_CA_NAME=ca-org2 - FABRIC_CA_SERVER_TLS_ENABLED=true - FABRIC_CA_SERVER_PORT=8054 + - FABRIC_CA_SERVER_OPERATIONS_LISTENADDRESS=0.0.0.0:18054 ports: - "8054:8054" + - "18054:18054" command: sh -c 'fabric-ca-server start -b admin:adminpw -d' volumes: - ../organizations/fabric-ca/org2:/etc/hyperledger/fabric-ca-server @@ -56,8 +60,10 @@ services: - FABRIC_CA_SERVER_CA_NAME=ca-orderer - FABRIC_CA_SERVER_TLS_ENABLED=true - FABRIC_CA_SERVER_PORT=9054 + - FABRIC_CA_SERVER_OPERATIONS_LISTENADDRESS=0.0.0.0:19054 ports: - "9054:9054" + - "19054:19054" command: sh -c 'fabric-ca-server start -b admin:adminpw -d' volumes: - ../organizations/fabric-ca/ordererOrg:/etc/hyperledger/fabric-ca-server diff --git a/test-network/docker/docker-compose-test-net.yaml b/test-network/docker/docker-compose-test-net.yaml index 695073fc..35d29eef 100644 --- a/test-network/docker/docker-compose-test-net.yaml +++ b/test-network/docker/docker-compose-test-net.yaml @@ -45,6 +45,7 @@ services: - ORDERER_ADMIN_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt] - ORDERER_ADMIN_TLS_CLIENTROOTCAS=[/var/hyperledger/orderer/tls/ca.crt] - ORDERER_ADMIN_LISTENADDRESS=0.0.0.0:7053 + - ORDERER_OPERATIONS_LISTENADDRESS=0.0.0.0:17050 working_dir: /opt/gopath/src/github.com/hyperledger/fabric command: orderer volumes: @@ -55,6 +56,7 @@ services: ports: - 7050:7050 - 7053:7053 + - 17050:17050 networks: - test @@ -83,6 +85,7 @@ services: - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org1.example.com:7051 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051 - CORE_PEER_LOCALMSPID=Org1MSP + - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:17051 volumes: - /var/run/docker.sock:/host/var/run/docker.sock - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp @@ -92,6 +95,7 @@ services: command: peer node start ports: - 7051:7051 + - 17051:17051 networks: - test @@ -120,6 +124,7 @@ services: - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org2.example.com:9051 - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org2.example.com:9051 - CORE_PEER_LOCALMSPID=Org2MSP + - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:19051 volumes: - /var/run/docker.sock:/host/var/run/docker.sock - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp:/etc/hyperledger/fabric/msp @@ -129,6 +134,7 @@ services: command: peer node start ports: - 9051:9051 + - 19051:19051 networks: - test From 04806b604d86731fbaeaf84ec32afe71cf6ee9b2 Mon Sep 17 00:00:00 2001 From: yuppieghost Date: Tue, 6 Jul 2021 15:36:17 +0800 Subject: [PATCH 02/15] Update asset_transfer_ledger_chaincode.js (#452) fabric 2.x use **txId ** in res.value Signed-off-by: yuppieghost --- .../chaincode-javascript/lib/asset_transfer_ledger_chaincode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js b/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js index 91e67126..33a1aee3 100644 --- a/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js +++ b/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js @@ -320,7 +320,7 @@ class Chaincode extends Contract { let jsonRes = {}; console.log(res.value.value.toString('utf8')); if (isHistory && isHistory === true) { - jsonRes.TxId = res.value.tx_id; + jsonRes.TxId = res.value.txId; jsonRes.Timestamp = res.value.timestamp; try { jsonRes.Value = JSON.parse(res.value.value.toString('utf8')); From 0d64a1b70cb360fcb9d693ac2f891f69a7e43759 Mon Sep 17 00:00:00 2001 From: Dave Kelsey <25582377+davidkel@users.noreply.github.com> Date: Tue, 6 Jul 2021 11:48:34 +0100 Subject: [PATCH 03/15] New HSM Typescript Sample (#455) Signed-off-by: D Co-authored-by: D --- .../application-typescript-hsm/.gitignore | 15 + .../application-typescript-hsm/README.md | 263 +++++++++++++++++ .../application-typescript-hsm/package.json | 50 ++++ .../application-typescript-hsm/softhsm2.conf | 5 + .../application-typescript-hsm/src/app.ts | 278 ++++++++++++++++++ .../src/utils/AppUtil.ts | 56 ++++ .../src/utils/CAUtil.ts | 94 ++++++ .../application-typescript-hsm/tsconfig.json | 18 ++ .../application-typescript-hsm/tslint.json | 24 ++ ci/azure-pipelines.yml | 2 + ci/scripts/run-test-network-basic.sh | 16 + 11 files changed, 821 insertions(+) create mode 100644 asset-transfer-basic/application-typescript-hsm/.gitignore create mode 100644 asset-transfer-basic/application-typescript-hsm/README.md create mode 100644 asset-transfer-basic/application-typescript-hsm/package.json create mode 100644 asset-transfer-basic/application-typescript-hsm/softhsm2.conf create mode 100644 asset-transfer-basic/application-typescript-hsm/src/app.ts create mode 100644 asset-transfer-basic/application-typescript-hsm/src/utils/AppUtil.ts create mode 100644 asset-transfer-basic/application-typescript-hsm/src/utils/CAUtil.ts create mode 100644 asset-transfer-basic/application-typescript-hsm/tsconfig.json create mode 100644 asset-transfer-basic/application-typescript-hsm/tslint.json diff --git a/asset-transfer-basic/application-typescript-hsm/.gitignore b/asset-transfer-basic/application-typescript-hsm/.gitignore new file mode 100644 index 00000000..48285d12 --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/.gitignore @@ -0,0 +1,15 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + + +# Coverage directory used by tools like istanbul +coverage + +# Dependency directories +node_modules/ +jspm_packages/ + +# Compiled TypeScript files +dist + diff --git a/asset-transfer-basic/application-typescript-hsm/README.md b/asset-transfer-basic/application-typescript-hsm/README.md new file mode 100644 index 00000000..8b87a82c --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/README.md @@ -0,0 +1,263 @@ +# Asset Transfer Basic typescript HSM sample + +This sample takes the Asset Transfer basic example and re-works it to focus on how +you would use a Hardware Security Modules within your node client application. + +## About the Sample + +This typescript sample application is able to use any of the asset transfer basic +chaincode samples. It will show how to register users with a Fabric CA and enroll users which will store keys in an HSM (In this case the sample uses SoftHSM which is an HSM implementation that should be used for development and testing purposes only). It also demonstrates setting up a wallet that will store identities that can then be used to transact on the fabric network which are HSM managed. + +## C Compilers + +In order for the client application to run successfully you must ensure you have C compilers and Python 3 (Note that Python 2 may still work however Python 2 is out of support and could stop working in the future) installed otherwise the node dependency `pkcs11js` will not be built and the application will fail. The failure will look like + +``` +Loaded the network configuration located at /home/dave/temp/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/connection-org1.json +******** FAILED to run the application: Error: Cannot find module 'pkcs11js' +Require stack: +- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/lib/impl/bccsp_pkcs11.js +- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/lib/Utils.js +- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/index.js +- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-ca-client/lib/FabricCAServices.js +- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-ca-client/index.js +- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/dist/app.js +``` + +how to install the required C Compilers and Python will depend on your operating system and version. + +## Configuring and running a Hardware Security Module + +This sample sets the hsmOptions for the wallet as follows + +```javascript +const softHSMOptions: HsmOptions = { + lib: await findSoftHSMPKCS11Lib(), + pin: process.env.PKCS11_PIN || '98765432', + label: process.env.PKCS11_LABEL || 'ForFabric', +}; +``` + +which is specific to using SoftHSM which has been initialised with a token labelled `ForFabric` +and a user pin of `98765432`, however you can override these values to use your own HSM by either +editting the application or use these environment variables to pass in the values: + +* PKCS11_LIB - path to the your specific HSM Library +* PKCS11_PIN - your HSM pin +* PKCS11_LABEL - your HSM label + +Alternatively you could install SoftHSM to try out the application as described in the next sections + +### Install SoftHSM + +In order to run the application in the absence of a real HSM, a software +emulator of the PKCS#11 interface is required. +For more information please refer to [SoftHSM](https://www.opendnssec.org/softhsm/). + +SoftHSM can either be installed using the package manager for your host system: + +* Ubuntu: `sudo apt install softhsm2` +* macOS: `brew install softhsm` +* Windows: **unsupported** + +Or compiled and installed from source: + +1. install openssl 1.0.0+ or botan 1.10.0+ +2. download the source code from +3. `tar -xvf softhsm-2.5.0.tar.gz` +4. `cd softhsm-2.5.0` +5. `./configure --disable-gost` (would require additional libraries, turn it off unless you need 'gost' algorithm support for the Russian market) +6. `make` +7. `sudo make install` + +### Specify the SoftHSM configuration file + +A configuration file for SoftHSM is provided in this sample directory. This file +uses /tmp as the location for SoftHSM to store it's data which means (depending on +your operating system configuration) the data could be deleted at some point, for example +when you reboot your system. If this data is lost then you will have to delete the wallet +created. An alternative is to change this file to store SoftHSM data in a permanent location +on your file system. +To use this configuration file you need to export an environment variable to point to it +for example, if you are in the application directory then you can use the following command + +```bash +export SOFTHSM2_CONF=$PWD/softhsm2.conf +``` + +Ensure you have this set when initializing the token as well as running the application + +### Initialize a token to store keys in SoftHSM + +If you have not initialized a token previously (or it has been deleted) then you will need to perform this one time operation + +```bash +softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 +``` + +The Security Officer PIN, specified with the `--so-pin` flag, can be used to re-initialize the token, +and the user PIN (see below), specified with the `--pin` flag, is used by applications to access the token for +generating and retrieving keys. + +## Running the sample + +Use the Fabric test network utility (`network.sh`) to start a Fabric network and deploy one +one of the sample chaincodes. + +Follow these step in order. +- Start the test network with a Certificate Authority and create a channel, so assuming you are in the ensuring you are in the `application-typescript-hsm` directory + +``` +cd ../../test-network +./network.sh down +./network.sh up createChannel -c mychannel -ca +``` + +- Deploy the chaincode (smart contract) + +``` +# to deploy the javascript version +./network.sh deployCC -ccs 1 -ccv 1 -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl javascript -ccp ./../asset-transfer-basic/chaincode-javascript/ -ccn basic +``` + +- Run the application + +``` +cd ../asset-transfer-basic/application-typescript-hsm +npm install +npm run build +node dist/app.js +``` + +### Expected successful execution + +If the sample runs successfully then it will output several messages showing the various actions and finally display the message + +``` +*** The sample ran successfully *** +``` + +One the first run of the sample, a CA admin id from Org1 will be enrolled from the CA. The certificate for this admin will be stored in the application wallet but the private key will have been stored in the HSM, it will not be in the application wallet. A User in Org1 will be registered in the the Org1 CA and then enrolled. Again only it's certificate will be stored in the application wallet. +All signing of transactions done by the application (driven by the node sdk) will actually be done by the HSM rather than within the node sdk as the private key will never be directly available outside of the HSM. + +This sample can be run multiple times even while the same network remains up. As the certificates are already in the application wallet it will not have to enroll a CA admin or register and enroll a user. + +### Possible issues you could encounter running the sample + +If you see this error: + +``` +******** FAILED to run the application: Pkcs11Error: CKR_GENERAL_ERROR +``` +Make sure you have exported SOFTHSM2_CONF correctly and it points to a valid SoftHSM configuration file that references +a directory containing a initialised SoftHSM token + + +If you see this error: + +``` +2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied +******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied +``` + +Then the current certificates in your wallet are not valid for the network you are trying to interact with. This would happen if you had +shutdown the fabric network using `network.sh down` and created a new network because this causes all the root certificates to be recreated +To address this problem, you can delete the `wallet` directory in the `dist` folder (`fabric-samples/asset-transfer-basic/application-typescript-hsm/dist/wallet`) of this sample to create new certificates. Because new keypairs are generated these will be stored in SoftHSM and the existing old ones will not be referenced + +If you see this error + +``` +Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]] +******** FAILED to run the application: Error: Identity not found in wallet: appUser +``` + +Then the most likely cause is you have deleted your wallet. You would need to either +- stop and create a new fabric network using the `network.sh down` and then following the above instructions to start a new fabric network (but you should not re-initialize the softhsm token) +- or change the application such that it registers a new user instead of `appUser`. This is because be default a registered user can only be enrolled once (using the userid and it's secret) + +If you see this error + +``` +******** FAILED to run the application: Error: _pkcs11OpenSession[336]: PKCS11 label ForFabric cannot be found in the slot list +``` + +Then the SoftHSM token directory has been deleted (could be due to the /tmp file being cleaned up if you use the sample softhsm2.conf file provided). +You will either need to +- delete your existing wallet, bring down the network as described in `Clean up` section and recreate the network including re-initializing the softhsm token +- or you could just re-initialise the softhsm token but you will need to change the application so that it registers a new user instead of `appUser` + +If you see this error (note the number following `SKI` will not be the same) + +``` +******** FAILED to run the application: Error: _pkcs11SkiToHandle[572]: no key with SKI 27f3557183cd5f26384ab69968ba74944c94c0e24f681c4fadd6502886891da0 found +``` + +Then the certificates in your wallet do not have corresponding keys in SoftHSM. You can should bring down the network and delete your current wallet (as described in `Clean up` section) and create the network again + + +If you see either of these errors when the application ends + +``` +free(): double free detected in tcache 2 +Aborted +``` + +or + +``` +node[61480]: ../src/node_http2.cc:521:void node::http2::Http2Session::CheckAllocatedSize(size_t) const: Assertion `(current_nghttp2_memory_) >= (previous_size)' failed. + 1: 0xa1a640 node::Abort() [node] + 2: 0xa1a6be [node] + 3: 0xa55e2a node::mem::NgLibMemoryManager::ReallocImpl(void*, unsigned long, void*) [node] + 4: 0xa55ed3 node::mem::NgLibMemoryManager::FreeImpl(void*, void*) [node] + 5: 0x18b0388 nghttp2_session_close_stream [node] + 6: 0x18b76ea nghttp2_session_mem_recv [node] + 7: 0xa54937 node::http2::Http2Session::ConsumeHTTP2Data() [node] + 8: 0xa54d5e node::http2::Http2Session::OnStreamRead(long, uv_buf_t const&) [node] + 9: 0xb6a651 node::TLSWrap::ClearOut() [node] +10: 0xb6bcdb node::TLSWrap::OnStreamRead(long, uv_buf_t const&) [node] +11: 0xaf54b1 [node] +12: 0x137fed9 [node] +13: 0x1380500 [node] +14: 0x1386de5 [node] +15: 0x137458f uv_run [node] +16: 0xa5d7a6 node::NodeMainInstance::Run() [node] +17: 0x9eab6c node::Start(int, char**) [node] +18: 0x7fd7612180b3 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6] +19: 0x981fe5 [node] +Aborted (core dumped) +``` + +then this is due to a bug in `node`. May sure you are using the latest supported version of node, however at the time of writing (node 14.17.1 & node 12.22.1) a fix had not been released by node.js + +### Enabling Node SDK logging + +To see the SDK workings, try setting the logging to show on the console before running +``` +export HFC_LOGGING='{"debug":"console"}' +``` +or log to a file +``` +export HFC_LOGGING='{"debug":"./debug.log"}' +``` + +## Clean up + +When you are finished, you can bring down the test network. +The command will remove all the nodes of the test network, and delete any +ledger data that you created: + +```bash +cd ../../test-network +./network.sh down +``` +Be sure to delete the wallet directory create by the application. +The stored identities will no longer be valid when restarting the network. +For example if you are in the application-typescript-hsm directory + +```bash +rm -fr dist/wallet +``` + +## Suggestions +When typescript node applications log problems or terminate with a stack trace you normally would have to look at the compiled .js code to find the code that was executed in the stack trace. It would be easier if you could find the corresponding lines in the typescript source file. One solution to this problem can be found here https://github.com/evanw/node-source-map-support which will convert stack trace output to corresponding typescript file lines using the generated source maps. \ No newline at end of file diff --git a/asset-transfer-basic/application-typescript-hsm/package.json b/asset-transfer-basic/application-typescript-hsm/package.json new file mode 100644 index 00000000..202bfeab --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/package.json @@ -0,0 +1,50 @@ +{ + "name": "asset-transfer-basic", + "version": "1.0.0", + "description": "Asset Transfer Basic contract implemented in TypeScript", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "scripts": { + "lint": "tslint -c tslint.json 'src/**/*.ts'", + "pretest": "npm run lint", + "start": "npm run build && node dist/app.js", + "build": "tsc", + "build:watch": "tsc -w", + "prepublishOnly": "npm run build" + }, + "engineStrict": true, + "author": "Hyperledger", + "license": "Apache-2.0", + "dependencies": { + "fabric-ca-client": "^2.2.8", + "fabric-network": "^2.2.8" + }, + "devDependencies": { + "tslint": "^5.11.0", + "typescript": "^3.1.6" + }, + "nyc": { + "extension": [ + ".ts", + ".tsx" + ], + "exclude": [ + "coverage/**", + "dist/**" + ], + "reporter": [ + "text-summary", + "html" + ], + "all": true, + "check-coverage": true, + "statements": 100, + "branches": 100, + "functions": 100, + "lines": 100 + } +} diff --git a/asset-transfer-basic/application-typescript-hsm/softhsm2.conf b/asset-transfer-basic/application-typescript-hsm/softhsm2.conf new file mode 100644 index 00000000..86687cf7 --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/softhsm2.conf @@ -0,0 +1,5 @@ +directories.tokendir = /tmp/ +objectstore.backend = file + +# ERROR, WARNING, INFO, DEBUG +log.level = INFO \ No newline at end of file diff --git a/asset-transfer-basic/application-typescript-hsm/src/app.ts b/asset-transfer-basic/application-typescript-hsm/src/app.ts new file mode 100644 index 00000000..46b8ffed --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/src/app.ts @@ -0,0 +1,278 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ +import * as FabricCAServices from 'fabric-ca-client'; +import { Contract, Gateway, GatewayOptions, HsmOptions, HsmX509Provider, Wallets } from 'fabric-network'; +import * as fs from 'fs'; +import * as path from 'path'; +import { buildCCPOrg1, prettyJSONString } from './utils//AppUtil'; +import { enrollUserToWallet, registerUser, UserToEnroll, UserToRegister } from './utils/CAUtil'; + +const walletPath = path.join(__dirname, 'wallet'); + +// define information about the channel and chaincode id that will be driven by this application +const channelName = 'mychannel'; +const chaincodeId = 'basic'; + +// define the CA Registrar +const mspOrg1 = 'Org1MSP'; +const org1CAAdminUserId = 'admin'; +const org1CAAdminSecret = 'adminpw'; + +// define the user in org1 to use +const org1UserId = 'appUser'; +const org1Secret = 'appUserSecret'; +const org1Affiliation = 'org1.department1'; + +type Request = 'submit' | 'evaluate'; + +interface TransactionToSendFormat { + request: Request; + txName: string; + txArgs: string[]; + description?: string; +} + +// The set of transactions to be invoked +const transactionsToSend: TransactionToSendFormat[] = [ + { + request: 'submit', + txName: 'InitLedger', + txArgs: [], + description: '\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger', + }, + { + request: 'evaluate', + txName: 'GetAllAssets', + txArgs: [], + description: '\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger', + }, + { + request: 'submit', + txName: 'CreateAsset', + txArgs: ['asset13', 'yellow', '5', 'Tom', '1300'], + description: '\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments', + }, + { + request: 'evaluate', + txName: 'ReadAsset', + txArgs: ['asset13'], + description: '\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID', + }, + { + request: 'evaluate', + txName: 'AssetExists', + txArgs: ['asset1'], + description: '\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with given assetID exist', + }, + { + request: 'submit', + txName: 'UpdateAsset', + txArgs: ['asset1', 'blue', '5', 'Tomoko', '350'], + description: '\n--> Submit Transaction: UpdateAsset asset1, change the appraisedValue to 350', + }, + { + request: 'evaluate', + txName: 'ReadAsset', + txArgs: ['asset1'], + description: '\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes', + }, + { + request: 'submit', + txName: 'UpdateAsset', + txArgs: ['asset70', 'blue', '5', 'Tomoko', '300'], + description: '\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error', + }, + { + request: 'submit', + txName: 'TransferAsset', + txArgs: ['asset1', 'Tom'], + description: '\n--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom', + }, + { + request: 'evaluate', + txName: 'ReadAsset', + txArgs: ['asset1'], + description: '\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes', + }, +]; + +/** + * A test application to show basic queries operations with any of the asset-transfer-basic chaincodes + * -- How to submit a transaction + * -- How to query and check the results + * + * To see the SDK workings, try setting the logging to show on the console before running + * export HFC_LOGGING='{"debug":"console"}' + */ +async function main() { + try { + // build an in memory object with the network configuration (also known as a connection profile) + const ccp = buildCCPOrg1(); + + // softhsm pkcs11 options to use + const softHSMOptions: HsmOptions = { + lib: await findSoftHSMPKCS11Lib(), + pin: process.env.PKCS11_PIN || '98765432', + label: process.env.PKCS11_LABEL || 'ForFabric', + }; + + // setup the wallet to hold the credentials used by this application + // Here we add the HSM provider to the provider registry of the wallet + const wallet = await Wallets.newFileSystemWallet(walletPath); + const hsmProvider = new HsmX509Provider(softHSMOptions); + wallet.getProviderRegistry().addProvider(hsmProvider); + + // build an instance of the fabric ca services client based on + // the information in the network configuration + const caInfo = ccp.certificateAuthorities['ca.org1.example.com']; // lookup CA details from config + const caTLSCACerts = caInfo.tlsCACerts.pem; + + // Here we make sure we pass in an HSM enabled cryptosuite to this CA instance + const hsmCAClient = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName, hsmProvider.getCryptoSuite()); + console.log(`Built a CA Client named ${caInfo.caName}`); + + // enroll the CA admin into the wallet if it doesn't already exist in the wallet + if (!await wallet.get(org1CAAdminUserId)) { + await enrollUserToWallet({ + caClient: hsmCAClient, + wallet, + orgMspId: mspOrg1, + userId: org1CAAdminUserId, + userIdSecret: org1CAAdminSecret, + } as UserToEnroll); + } + + // if we don't have the required user in the wallet then + // register this user to the CA (if it's not already registered) + // then enroll that user into the wallet + if (!await wallet.get(org1UserId)) { + await registerUser({ + caClient: hsmCAClient, + adminId: org1CAAdminUserId, + wallet, + orgMspId: mspOrg1, + userId: org1UserId, + userIdSecret: org1Secret, + affiliation: org1Affiliation, + } as UserToRegister); + + // By default you can only enroll a user once, after that you would have to re-enroll using the current + // certificate rather than using the secret. + await enrollUserToWallet({ + caClient: hsmCAClient, + wallet, + orgMspId: mspOrg1, + userId: org1UserId, + userIdSecret: org1Secret, + } as UserToEnroll); + } + + // Create a new gateway instance for interacting with the fabric network bound to our user + // This sample expects a locally deployed fabric network (ie running on the same machine in docker containers) + // therefore we set asLocalhost to true + const gateway = new Gateway(); + + const gatewayOpts: GatewayOptions = { + wallet, + identity: org1UserId, + discovery: { enabled: true, asLocalhost: true }, // using asLocalhost as this gateway is using a fabric network deployed locally + }; + + try { + // setup the gateway instance + // The user will now be able to create connections to the fabric network and be able to + // submit transactions and query. All transactions submitted by this gateway will be + // signed by this user using the credentials stored in the wallet. + await gateway.connect(ccp, gatewayOpts); + + // Build a network instance based on the channel where the smart contract is deployed + const network = await gateway.getNetwork(channelName); + + // Get the contract from the network. + const contract = network.getContract(chaincodeId); + + // loop around all transactions to send, each one will be sent sequentially + // through the same gateway/network/contract as subsequent transations expect the + // previous submits to have been committed + // Note however that a gateway/network/contract can support concurrent submitted + // transactions. + for (const transactionToSend of transactionsToSend) { + await interactWithFabric(contract, transactionToSend); + } + + console.log('*** The sample ran successfully ***'); + } finally { + + // Disconnect from the gateway when the application is closing + // This will close all connections to the network + gateway.disconnect(); + } + } catch (error) { + console.error(`******** FAILED to run the application: ${error}`); + } +} + +/** + * Determine the location of the SoftHSM PKCS11 Library + * @returns the location of the SoftHSM PKCS11 Library or 'NOT FOUND' if not found + */ +async function findSoftHSMPKCS11Lib() { + const commonSoftHSMPathNames = [ + '/usr/lib/softhsm/libsofthsm2.so', + '/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so', + '/usr/local/lib/softhsm/libsofthsm2.so', + '/usr/lib/libacsp-pkcs11.so', + ]; + + let pkcsLibPath = 'NOT FOUND'; + if (typeof process.env.PKCS11_LIB === 'string' && process.env.PKCS11_LIB !== '') { + pkcsLibPath = process.env.PKCS11_LIB; + } else { + // + // Check common locations for PKCS library + // + for (const pathnameToTry of commonSoftHSMPathNames) { + if (fs.existsSync(pathnameToTry)) { + pkcsLibPath = pathnameToTry; + break; + } + } + } + + return pkcsLibPath; +} + +/** + * Interact with the Fabric Network via the Contact with the required transaction to perform. + * @param contract The contract to send the transaction to + * @param transactionToPerform the transaction to perform + */ +async function interactWithFabric(contract: Contract, transactionToPerform: TransactionToSendFormat): Promise { + console.log(transactionToPerform.description); + try { + switch (transactionToPerform.request) { + case 'submit': { + await contract.submitTransaction(transactionToPerform.txName, ...transactionToPerform.txArgs); + console.log('*** Result: committed'); + break; + } + + case 'evaluate': { + const result = await contract.evaluateTransaction(transactionToPerform.txName, ...transactionToPerform.txArgs); + console.log(`*** Result: ${prettyJSONString(result.toString())}`); + } + } + } catch (error) { + console.log(`*** Successfully caught the error: \n ${error}`); + // In reality applications should check the returned error to decide whether the transaction needs to be retried (ie go through + // endorsement again) for example an MVCC_READ_CONFLICT error, resubmitted to the orderer for example a timeout because it was + // never committed (for example due to networking issues the transaction never gets included in a block), or whether it should + // be reported back, for example they tried to perform an invalid application action. + } +} + +// execute the main function +main(); diff --git a/asset-transfer-basic/application-typescript-hsm/src/utils/AppUtil.ts b/asset-transfer-basic/application-typescript-hsm/src/utils/AppUtil.ts new file mode 100644 index 00000000..685acce5 --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/src/utils/AppUtil.ts @@ -0,0 +1,56 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +const buildCCPOrg1 = (): Record => { + // load the common connection configuration file + const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network', + 'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json'); + const fileExists = fs.existsSync(ccpPath); + if (!fileExists) { + throw new Error(`no such file or directory: ${ccpPath}`); + } + const contents = fs.readFileSync(ccpPath, 'utf8'); + + // build a JSON object from the file contents + const ccp = JSON.parse(contents); + + console.log(`Loaded the network configuration located at ${ccpPath}`); + return ccp; +}; + +const buildCCPOrg2 = (): Record => { + // load the common connection configuration file + const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network', + 'organizations', 'peerOrganizations', 'org2.example.com', 'connection-org2.json'); + const fileExists = fs.existsSync(ccpPath); + if (!fileExists) { + throw new Error(`no such file or directory: ${ccpPath}`); + } + const contents = fs.readFileSync(ccpPath, 'utf8'); + + // build a JSON object from the file contents + const ccp = JSON.parse(contents); + + console.log(`Loaded the network configuration located at ${ccpPath}`); + return ccp; +}; + +const prettyJSONString = (inputString: string): string => { + if (inputString) { + return JSON.stringify(JSON.parse(inputString), null, 2); + } else { + return inputString; + } +}; + +export { + buildCCPOrg1, + buildCCPOrg2, + prettyJSONString, +}; diff --git a/asset-transfer-basic/application-typescript-hsm/src/utils/CAUtil.ts b/asset-transfer-basic/application-typescript-hsm/src/utils/CAUtil.ts new file mode 100644 index 00000000..00944d22 --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/src/utils/CAUtil.ts @@ -0,0 +1,94 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as FabricCAServices from 'fabric-ca-client'; +import { Wallet } from 'fabric-network'; + +export interface UserToEnroll { + caClient: FabricCAServices; + wallet: Wallet; + orgMspId: string; + userId: string; + userIdSecret: string; +} + +/** + * enroll a registered CA user and store the credentials in the wallet + * @param userToEnroll details about the user and the wallet to use + */ +export const enrollUserToWallet = async (userToEnroll: UserToEnroll): Promise => { + try { + + // check that the identity isn't already in the wallet + const identity = await userToEnroll.wallet.get(userToEnroll.userId); + if (identity) { + console.log(`Identity ${userToEnroll.userId} already exists in the wallet`); + return; + } + + // Enroll the user + const enrollment = await userToEnroll.caClient.enroll({ enrollmentID: userToEnroll.userId, enrollmentSecret: userToEnroll.userIdSecret }); + + // store the user + const hsmIdentity = { + credentials: { + certificate: enrollment.certificate, + }, + mspId: userToEnroll.orgMspId, + type: 'HSM-X.509', + }; + await userToEnroll.wallet.put(userToEnroll.userId, hsmIdentity); + console.log(`Successfully enrolled user ${userToEnroll.userId} and imported it into the wallet`); + } catch (error) { + console.error(`Failed to enroll user ${userToEnroll.userId}: ${error}`); + } +}; + +export interface UserToRegister { + caClient: FabricCAServices; + wallet: Wallet; + orgMspId: string; + adminId: string; + userId: string; + userIdSecret: string; + affiliation: string; +} + +export const registerUser = async (userToRegister: UserToRegister): Promise => { + try { + + // Must use a CA admin (registrar) to register a new user + const adminIdentity = await userToRegister.wallet.get(userToRegister.adminId); + if (!adminIdentity) { + console.log('An identity for the admin user does not exist in the wallet'); + console.log('Enroll the admin user before retrying'); + return; + } + + // build a user object for authenticating with the CA + const provider = userToRegister.wallet.getProviderRegistry().getProvider(adminIdentity.type); + const adminUser = await provider.getUserContext(adminIdentity, userToRegister.adminId); + + // Register the user + // if affiliation is specified by client, the affiliation value must be configured in CA + await userToRegister.caClient.register({ + affiliation: userToRegister.affiliation, + enrollmentID: userToRegister.userId, + enrollmentSecret: userToRegister.userIdSecret, + role: 'client', + }, adminUser); + console.log(`Successfully registered ${userToRegister.userId}.`); + return; + + } catch (error) { + // check to see if it's an already registered error, if it is, then we can ignore it + // otherwise we rethrow the error + if (error.errors[0].code !== 74) { + console.error(`Failed to register user : ${error}`); + throw error; + } + } +}; diff --git a/asset-transfer-basic/application-typescript-hsm/tsconfig.json b/asset-transfer-basic/application-typescript-hsm/tsconfig.json new file mode 100644 index 00000000..590edcb3 --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "dist", + "target": "es2017", + "moduleResolution": "node", + "module": "commonjs", + "declaration": true, + "sourceMap": true, + "noImplicitAny": true, + "strict": true + }, + "include": [ + "./src/**/*" + ], + "exclude": [ + "./src/**/*.spec.ts" + ] +} diff --git a/asset-transfer-basic/application-typescript-hsm/tslint.json b/asset-transfer-basic/application-typescript-hsm/tslint.json new file mode 100644 index 00000000..3612c23a --- /dev/null +++ b/asset-transfer-basic/application-typescript-hsm/tslint.json @@ -0,0 +1,24 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "indent": [true, "spaces", 4], + "linebreak-style": [true, "LF"], + "quotemark": [true, "single"], + "semicolon": [true, "always"], + "no-console": false, + "curly": true, + "triple-equals": true, + "no-string-throw": true, + "no-var-keyword": true, + "no-trailing-whitespace": true, + "object-literal-key-quotes": [true, "as-needed"], + "object-literal-sort-keys": false, + "max-line-length": false, + "interface-name":false + }, + "rulesDirectory": [] +} diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 9e78c63f..ea01dbe7 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -109,6 +109,8 @@ jobs: CHAINCODE_LANGUAGE: typescript steps: - template: templates/install-deps.yml + - script: sudo apt-get install softhsm2 + displayName: Install SoftHSM - script: ../ci/scripts/run-test-network-basic.sh workingDirectory: test-network displayName: Run Test Network Basic Chaincode diff --git a/ci/scripts/run-test-network-basic.sh b/ci/scripts/run-test-network-basic.sh index 5350e1d8..22f5b8c6 100755 --- a/ci/scripts/run-test-network-basic.sh +++ b/ci/scripts/run-test-network-basic.sh @@ -62,3 +62,19 @@ print "Running the output app" node dist/app.js popd stopNetwork + +# Run typescript HSM application +createNetwork +print "Initializing Typescript HSM application" +pushd ../asset-transfer-basic/application-typescript-hsm +print "Setup SoftHSM" +export SOFTHSM2_CONF=$PWD/softhsm2.conf +softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234 +print "install dependencies" +npm install +print "Building app.ts" +npm run build +print "Running the output app" +node dist/app.js +popd +stopNetwork From d5d8ff1539a6427ba347b442af658d7c5f46fb1b Mon Sep 17 00:00:00 2001 From: Yuki Kondo <57741286+yukiknd@users.noreply.github.com> Date: Thu, 8 Jul 2021 05:40:46 +0900 Subject: [PATCH 04/15] Fix off_chain_data to put data to off-chain database (#457) The off_chain_data sample fails to put data to the off-chain database. The application does not read fetched blocks because it uses the old interface of `addBlockListener()` to handle block events. This PR fixes the application to use the latest block listener and build the off-chain database. Signed-off-by: Yuki Kondo --- off_chain_data/blockEventListener.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/off_chain_data/blockEventListener.js b/off_chain_data/blockEventListener.js index 697cf8ef..43749c3b 100644 --- a/off_chain_data/blockEventListener.js +++ b/off_chain_data/blockEventListener.js @@ -116,20 +116,13 @@ async function main() { // Get the network (channel) our contract is deployed to. const network = await gateway.getNetwork('mychannel'); - const listener = await network.addBlockListener( - async (err, blockNum, block) => { - if (err) { - console.error(err); - return; - } - // Add the block to the processing map by block number - await ProcessingMap.set(block.header.number, block); - - console.log(`Added block ${blockNum} to ProcessingMap`) - }, - // set the starting block for the listener - { filtered: false, startBlock: parseInt(nextBlock, 10) } - ); + const listener = async (event) => { + // Add the block to the processing map by block number + await ProcessingMap.set(event.blockNumber, event.blockData); + console.log(`Added block ${event.blockNumber} to ProcessingMap`); + }; + const options = { filtered: false, startBlock: parseInt(nextBlock, 10) }; + await network.addBlockListener(listener, options); console.log(`Listening for block events, nextblock: ${nextBlock}`); From f38845ecab47c219d409e6edccb3d9f942b58368 Mon Sep 17 00:00:00 2001 From: ankitm123 Date: Mon, 12 Jul 2021 11:42:23 -0400 Subject: [PATCH 05/15] chore: minor simplification of configtx yaml (#459) We dont need << as we are not adding or overwriting any values. Signed-off-by: ankitm123 --- test-network/configtx/configtx.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test-network/configtx/configtx.yaml b/test-network/configtx/configtx.yaml index 8d7e48e6..48bd1d75 100644 --- a/test-network/configtx/configtx.yaml +++ b/test-network/configtx/configtx.yaml @@ -202,7 +202,6 @@ Orderer: &OrdererDefaults # Orderer Type: The orderer implementation to start OrdererType: etcdraft - # Addresses used to be the list of orderer addresses that clients and peers # could connect to. However, this does not allow clients to associate orderer # addresses and orderer organizations which can be useful for things such @@ -307,12 +306,10 @@ Profiles: <<: *OrdererDefaults Organizations: - *OrdererOrg - Capabilities: - <<: *OrdererCapabilities + Capabilities: *OrdererCapabilities Application: <<: *ApplicationDefaults Organizations: - *Org1 - *Org2 - Capabilities: - <<: *ApplicationCapabilities \ No newline at end of file + Capabilities: *ApplicationCapabilities From f84754ea3b8ea9838fb48936e9394bd54d31e54a Mon Sep 17 00:00:00 2001 From: James Taylor Date: Fri, 16 Jul 2021 19:10:12 +0100 Subject: [PATCH 06/15] Update CreateAsset txn in basic js and ts sample (#460) The Golang and Java sample chaincode returned an error when trying to create an asset which already exists JavaScript and TypeScript samples should now do the same Signed-off-by: James Taylor --- .../chaincode-javascript/lib/assetTransfer.js | 5 +++++ .../chaincode-typescript/src/assetTransfer.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js index 821e413a..a1a27aa4 100644 --- a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js +++ b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js @@ -65,6 +65,11 @@ class AssetTransfer extends Contract { // CreateAsset issues a new asset to the world state with given details. async CreateAsset(ctx, id, color, size, owner, appraisedValue) { + const exists = await this.AssetExists(ctx, id); + if (exists) { + throw new Error(`The asset ${id} already exists`); + } + const asset = { ID: id, Color: color, diff --git a/asset-transfer-basic/chaincode-typescript/src/assetTransfer.ts b/asset-transfer-basic/chaincode-typescript/src/assetTransfer.ts index b21f12ec..55b19b77 100644 --- a/asset-transfer-basic/chaincode-typescript/src/assetTransfer.ts +++ b/asset-transfer-basic/chaincode-typescript/src/assetTransfer.ts @@ -65,6 +65,11 @@ export class AssetTransferContract extends Contract { // CreateAsset issues a new asset to the world state with given details. @Transaction() public async CreateAsset(ctx: Context, id: string, color: string, size: number, owner: string, appraisedValue: number): Promise { + const exists = await this.AssetExists(ctx, id); + if (exists) { + throw new Error(`The asset ${id} already exists`); + } + const asset = { ID: id, Color: color, From 31ef28e64750af382345156cec7b512f78f27790 Mon Sep 17 00:00:00 2001 From: nao Date: Mon, 26 Jul 2021 23:11:14 +0900 Subject: [PATCH 07/15] Support docker.sock in rootless mode (#447) Docker version 20.10 support rootless mode. That features changes a mount path of docker.sock. This PR loads a mount path from DOCKER_HOST environment, and if not set, a mount path will be /var/run/docker.sock FAB-18481 Signed-off-by: Nao Nishijima --- test-network/addOrg3/addOrg3.sh | 8 ++++++-- test-network/addOrg3/docker/docker-compose-org3.yaml | 2 +- test-network/docker/docker-compose-test-net.yaml | 4 ++-- test-network/network.sh | 8 ++++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/test-network/addOrg3/addOrg3.sh b/test-network/addOrg3/addOrg3.sh index 1f5bf97b..3a1a7b66 100755 --- a/test-network/addOrg3/addOrg3.sh +++ b/test-network/addOrg3/addOrg3.sh @@ -119,9 +119,9 @@ function generateOrg3Definition() { function Org3Up () { # start org3 nodes if [ "${DATABASE}" == "couchdb" ]; then - docker-compose -f $COMPOSE_FILE_ORG3 -f $COMPOSE_FILE_COUCH_ORG3 up -d 2>&1 + DOCKER_SOCK=${DOCKER_SOCK} docker-compose -f $COMPOSE_FILE_ORG3 -f $COMPOSE_FILE_COUCH_ORG3 up -d 2>&1 else - docker-compose -f $COMPOSE_FILE_ORG3 up -d 2>&1 + DOCKER_SOCK=${DOCKER_SOCK} docker-compose -f $COMPOSE_FILE_ORG3 up -d 2>&1 fi if [ $? -ne 0 ]; then fatalln "ERROR !!!! Unable to start Org3 network" @@ -183,6 +183,10 @@ COMPOSE_FILE_CA_ORG3=docker/docker-compose-ca-org3.yaml # database DATABASE="leveldb" +# Get docker sock path from environment variable +SOCK="${DOCKER_HOST:-/var/run/docker.sock}" +DOCKER_SOCK="${SOCK##unix://}" + # Parse commandline args ## Parse mode diff --git a/test-network/addOrg3/docker/docker-compose-org3.yaml b/test-network/addOrg3/docker/docker-compose-org3.yaml index b5bc8cc9..0d3a85a6 100644 --- a/test-network/addOrg3/docker/docker-compose-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-org3.yaml @@ -40,7 +40,7 @@ services: - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org3.example.com:11051 - CORE_PEER_LOCALMSPID=Org3MSP volumes: - - /var/run/docker.sock:/host/var/run/docker.sock + - ${DOCKER_SOCK}:/host/var/run/docker.sock - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp:/etc/hyperledger/fabric/msp - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls:/etc/hyperledger/fabric/tls - peer0.org3.example.com:/var/hyperledger/production diff --git a/test-network/docker/docker-compose-test-net.yaml b/test-network/docker/docker-compose-test-net.yaml index 35d29eef..b4b54a45 100644 --- a/test-network/docker/docker-compose-test-net.yaml +++ b/test-network/docker/docker-compose-test-net.yaml @@ -87,7 +87,7 @@ services: - CORE_PEER_LOCALMSPID=Org1MSP - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:17051 volumes: - - /var/run/docker.sock:/host/var/run/docker.sock + - ${DOCKER_SOCK}:/host/var/run/docker.sock - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls:/etc/hyperledger/fabric/tls - peer0.org1.example.com:/var/hyperledger/production @@ -126,7 +126,7 @@ services: - CORE_PEER_LOCALMSPID=Org2MSP - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:19051 volumes: - - /var/run/docker.sock:/host/var/run/docker.sock + - ${DOCKER_SOCK}:/host/var/run/docker.sock - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp:/etc/hyperledger/fabric/msp - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls:/etc/hyperledger/fabric/tls - peer0.org2.example.com:/var/hyperledger/production diff --git a/test-network/network.sh b/test-network/network.sh index 24b9481e..2b09f79e 100755 --- a/test-network/network.sh +++ b/test-network/network.sh @@ -241,7 +241,7 @@ function networkUp() { COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_COUCH}" fi - docker-compose ${COMPOSE_FILES} up -d 2>&1 + DOCKER_SOCK="${DOCKER_SOCK}" docker-compose ${COMPOSE_FILES} up -d 2>&1 docker ps -a if [ $? -ne 0 ]; then @@ -278,7 +278,7 @@ function deployCC() { # Tear down running network function networkDown() { # stop org3 containers also in addition to org1 and org2, in case we were running sample to add org3 - docker-compose -f $COMPOSE_FILE_BASE -f $COMPOSE_FILE_COUCH -f $COMPOSE_FILE_CA down --volumes --remove-orphans + DOCKER_SOCK=$DOCKER_SOCK docker-compose -f $COMPOSE_FILE_BASE -f $COMPOSE_FILE_COUCH -f $COMPOSE_FILE_CA down --volumes --remove-orphans docker-compose -f $COMPOSE_FILE_COUCH_ORG3 -f $COMPOSE_FILE_ORG3 down --volumes --remove-orphans # Don't remove the generated artifacts -- note, the ledgers are always removed if [ "$MODE" != "restart" ]; then @@ -338,6 +338,10 @@ CC_SEQUENCE=1 # default database DATABASE="leveldb" +# Get docker sock path from environment variable +SOCK="${DOCKER_HOST:-/var/run/docker.sock}" +DOCKER_SOCK="${SOCK##unix://}" + # Parse commandline args ## Parse mode From fb648c519dea4218349b5ac4f19e3e8dd30c2b6b Mon Sep 17 00:00:00 2001 From: Saliou <47605697+s4l10u@users.noreply.github.com> Date: Mon, 2 Aug 2021 09:50:19 +0000 Subject: [PATCH 08/15] change docker-compose version from 2 to 2.4 on folder addOrg3/docker (#448) * change docker-compose version from 2 to 2.4 on folder addOrg3/docker Signed-off-by: Saliou * changing all docker-compose version from 2 to 2.4 under addOrg3/docker Signed-off-by: Saliou Co-authored-by: Saliou --- test-network/addOrg3/docker/docker-compose-ca-org3.yaml | 2 +- test-network/addOrg3/docker/docker-compose-couch-org3.yaml | 2 +- test-network/addOrg3/docker/docker-compose-org3.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-network/addOrg3/docker/docker-compose-ca-org3.yaml b/test-network/addOrg3/docker/docker-compose-ca-org3.yaml index fda2d83c..7c447fd5 100644 --- a/test-network/addOrg3/docker/docker-compose-ca-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-ca-org3.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2' +version: '2.4' networks: test: diff --git a/test-network/addOrg3/docker/docker-compose-couch-org3.yaml b/test-network/addOrg3/docker/docker-compose-couch-org3.yaml index a2166f83..e280ff0f 100644 --- a/test-network/addOrg3/docker/docker-compose-couch-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-couch-org3.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2' +version: '2.4' networks: test: diff --git a/test-network/addOrg3/docker/docker-compose-org3.yaml b/test-network/addOrg3/docker/docker-compose-org3.yaml index 0d3a85a6..3b1d5926 100644 --- a/test-network/addOrg3/docker/docker-compose-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-org3.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2' +version: '2.4' volumes: peer0.org3.example.com: From 8890d49d19fee8b4683f0d9a4b597a2a4b04cde0 Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Tue, 3 Aug 2021 02:33:25 +0900 Subject: [PATCH 09/15] Fix typo (#466) Signed-off-by: Tatsuya Sato --- test-network/addOrg3/docker/docker-compose-org3.yaml | 2 +- test-network/docker/docker-compose-test-net.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-network/addOrg3/docker/docker-compose-org3.yaml b/test-network/addOrg3/docker/docker-compose-org3.yaml index 3b1d5926..5c394d38 100644 --- a/test-network/addOrg3/docker/docker-compose-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-org3.yaml @@ -30,7 +30,7 @@ services: - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt - # Peer specific variabes + # Peer specific variables - CORE_PEER_ID=peer0.org3.example.com - CORE_PEER_ADDRESS=peer0.org3.example.com:11051 - CORE_PEER_LISTENADDRESS=0.0.0.0:11051 diff --git a/test-network/docker/docker-compose-test-net.yaml b/test-network/docker/docker-compose-test-net.yaml index b4b54a45..f156f030 100644 --- a/test-network/docker/docker-compose-test-net.yaml +++ b/test-network/docker/docker-compose-test-net.yaml @@ -76,7 +76,7 @@ services: - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt - # Peer specific variabes + # Peer specific variables - CORE_PEER_ID=peer0.org1.example.com - CORE_PEER_ADDRESS=peer0.org1.example.com:7051 - CORE_PEER_LISTENADDRESS=0.0.0.0:7051 @@ -115,7 +115,7 @@ services: - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt - # Peer specific variabes + # Peer specific variables - CORE_PEER_ID=peer0.org2.example.com - CORE_PEER_ADDRESS=peer0.org2.example.com:9051 - CORE_PEER_LISTENADDRESS=0.0.0.0:9051 From 2663a07765f0c7651329d8a2947b72c25f46d3a2 Mon Sep 17 00:00:00 2001 From: denyeart Date: Wed, 11 Aug 2021 08:53:47 -0400 Subject: [PATCH 10/15] Test network nano bash - initial commit (#450) Test network Nano bash provides a set of minimal bash scripts to run a Fabric network on your local machine. The network is functionally equivalent to the docker-based Test Network, you can therefore run all the tutorials and samples that target the Test Network. The Fabric release binaries are utilized rather than using docker containers to avoid all unnecessary layers. Only the chaincode and chaincode builder runs in a docker container behind the scenes. Using the Fabric binaries also makes it simple for Fabric developers to iteratively and quickly modify Fabric code and test a Fabric network as a user. Signed-off-by: David Enyeart --- test-network-nano-bash/.gitignore | 4 + test-network-nano-bash/README.md | 85 ++++ test-network-nano-bash/configtx.yaml | 389 +++++++++++++++++++ test-network-nano-bash/crypto-config.yaml | 54 +++ test-network-nano-bash/generate_artifacts.sh | 28 ++ test-network-nano-bash/orderer1.sh | 26 ++ test-network-nano-bash/orderer2.sh | 26 ++ test-network-nano-bash/orderer3.sh | 26 ++ test-network-nano-bash/peer1.sh | 35 ++ test-network-nano-bash/peer1admin.sh | 19 + test-network-nano-bash/peer2.sh | 35 ++ test-network-nano-bash/peer2admin.sh | 15 + test-network-nano-bash/peer3.sh | 35 ++ test-network-nano-bash/peer3admin.sh | 15 + test-network-nano-bash/peer4.sh | 35 ++ test-network-nano-bash/peer4admin.sh | 15 + test-network-nano-bash/terminal_setup.png | Bin 0 -> 167149 bytes 17 files changed, 842 insertions(+) create mode 100644 test-network-nano-bash/.gitignore create mode 100644 test-network-nano-bash/README.md create mode 100644 test-network-nano-bash/configtx.yaml create mode 100644 test-network-nano-bash/crypto-config.yaml create mode 100755 test-network-nano-bash/generate_artifacts.sh create mode 100755 test-network-nano-bash/orderer1.sh create mode 100755 test-network-nano-bash/orderer2.sh create mode 100755 test-network-nano-bash/orderer3.sh create mode 100755 test-network-nano-bash/peer1.sh create mode 100755 test-network-nano-bash/peer1admin.sh create mode 100755 test-network-nano-bash/peer2.sh create mode 100755 test-network-nano-bash/peer2admin.sh create mode 100755 test-network-nano-bash/peer3.sh create mode 100755 test-network-nano-bash/peer3admin.sh create mode 100755 test-network-nano-bash/peer4.sh create mode 100755 test-network-nano-bash/peer4admin.sh create mode 100644 test-network-nano-bash/terminal_setup.png diff --git a/test-network-nano-bash/.gitignore b/test-network-nano-bash/.gitignore new file mode 100644 index 00000000..4d1ad604 --- /dev/null +++ b/test-network-nano-bash/.gitignore @@ -0,0 +1,4 @@ +channel-artifacts/ +crypto-config/ +data/ +*.gz diff --git a/test-network-nano-bash/README.md b/test-network-nano-bash/README.md new file mode 100644 index 00000000..4e077de4 --- /dev/null +++ b/test-network-nano-bash/README.md @@ -0,0 +1,85 @@ +# Test network - Nano bash + +Test network Nano bash provides a set of minimal bash scripts to run a Fabric network on your local machine. +The network is functionally equivalent to the docker-based Test Network, you can therefore run all the tutorials and samples that target the Test Network with minimal changes. +The Fabric release binaries are utilized rather than using docker containers to avoid all unnecessary layers. Only the chaincode and chaincode builder runs in a docker container behind the scenes. +Using the Fabric binaries also makes it simple for Fabric developers to iteratively and quickly modify Fabric code and test a Fabric network as a user. + +As the name `nano` implies, the scripts provide the smallest minimal setup possible for a Fabric network while still offering a multi-node TLS-enabled network: +- Minimal set of dependencies +- Minimal requirements on Fabric version (any v2.x orderer and peer nodes should work) +- Minimal set of environment variable overrides of the default orderer orderer.yaml and peer core.yaml configurations +- Minimal scripting with minimal set of reference commands to get a Fabric network up and running +- Minimal channel configuration for an orderer organization (3 ordering nodes) and two peer organizations (with two peers each) +- Minimal endorsement policy to allow a single organization to approve and commit a chaincode (unlike Test Network which requires both organizations to endorse) + +# Prereqs + +- Follow the Fabric documentation for the [Prereqs](https://hyperledger-fabric.readthedocs.io/en/latest/prereqs.html) +- Follow the Fabric documentation for [downloading the Fabric samples and binaries](https://hyperledger-fabric.readthedocs.io/en/latest/install.html). You can skip the docker image downloads by using `curl -sSL https://bit.ly/2ysbOFE | bash -s -- -d` + +# Instructions for starting network + +Open terminal windows for 3 ordering nodes, 4 peer nodes, and 4 peer admins as seen in the following terminal setup. The first two peers and peer admins belong to Org1, the latter two peer and peer admins belong to Org2. +Note, you can start with two ordering nodes and a single Org1 peer node and single Org1 peer admin terminal if you would like to keep things even more minimal (two ordering nodes are required to achieve consensus (2 of 3), while a single peer from Org1 can be utilized since the endorsement policy is set as any single organization). +![Terminal setup](terminal_setup.png) + +The following instructions will have you run simple bash scripts that set environment variable overrides for a component and then runs the component. +The scripts contain only simple single-line commands so that they are easy to read and understand. +If you have trouble running bash scripts in your environment, you can just as easily copy and paste the individual commands from the script files instead of running the script files. + +- cd to the `test-network-nano-bash` directory in each terminal window +- In the first orderer terminal, run `./generate_artifacts.sh` to generate crypto material (calls cryptogen) and system and application channel genesis block and configuration transactions (calls configtxgen). The artifacts will be created in the `crypto-config` and `channel-artifacts` directories. +- In the three orderer terminals, run `./orderer1.sh`, `./orderer2.sh`, `./orderer3.sh` respectively +- In the four peer terminals, run `./peer1.sh`, `./peer2.sh`, `./peer3.sh`, `./peer4.sh` respectively +- Note that each orderer and peer write their data (including their ledgers) to their own subdirectory under the `data` directory +- In the four peer admin terminals, run `source peer1admin.sh`, `source peer2admin.sh`, `source peer3admin.sh`, `source peer4admin.sh` respectively + +Note the syntax of running the scripts. The peer admin scripts run with the `source` command in order to source the script files in the respective shells. This is important so that the exported environment variables can be utilized by any subsequent user commands. + +The `peer1admin.sh` script sets the peer1 admin environment variables, creates the application channel `mychannel`, updates the channel configuration for the org1 gossip anchor peer, and joins peer1 to `mychannel`. +The remaining peer admin scripts join their respective peers to `mychannel`. + +# Instructions for deploying and running the basic asset transfer sample chaincode + +To deploy and invoke the chaincode, utilize the peer1 admin terminal that you have created in the prior steps. + +Package and install the chaincode on peer1: + +``` +peer lifecycle chaincode package basic.tar.gz --path ../asset-transfer-basic/chaincode-go --lang golang --label basic_1 + +peer lifecycle chaincode install basic.tar.gz +``` + +The chaincode install may take a minute since the `fabric-ccenv` chaincode builder docker image will be downloaded if not already available on your machine. +Copy the returned chaincode package ID into an environment variable for use in subsequent commands (your ID may be different): + +``` +export CC_PACKAGE_ID=basic_1:faaa38f2fc913c8344986a7d1617d21f6c97bc8d85ee0a489c90020cd57af4a5 +``` + +Approve and commit the chaincode (only a single approver is required based on the lifecycle endorsement policy of any organization): + +``` +peer lifecycle chaincode approveformyorg -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile ${PWD}/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt + +peer lifecycle chaincode commit -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --sequence 1 --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt +``` + +Invoke the chaincode to create an asset (only a single endorser is required based on the default endorsement policy of any organization). +Then query the asset, update it, and query again to see the resulting asset changes on the ledger. + +``` +peer chaincode invoke -o 127.0.0.1:6050 -C mychannel -n basic -c '{"Args":["CreateAsset","1","blue","35","tom","1000"]}' --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt + +peer chaincode query -C mychannel -n basic -c '{"Args":["ReadAsset","1"]}' + +peer chaincode invoke -o 127.0.0.1:6050 -C mychannel -n basic -c '{"Args":["UpdateAsset","1","blue","35","jerry","1000"]}' --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt + +peer chaincode query -C mychannel -n basic -c '{"Args":["ReadAsset","1"]}' +``` + +Congratulations, you have deployed a minimal Fabric network! Inspect the scripts if you would like to see the minimal set of commands that were required to deploy the network. + +Utilize `Ctrl-C` in the orderer and peer terminal windows to kill the orderer and peer processes. You can run the scripts again to restart the components with their existing data, or run `./generate_artifacts` again to clean up the existing artifacts and data if you would like to restart with a clean environment. diff --git a/test-network-nano-bash/configtx.yaml b/test-network-nano-bash/configtx.yaml new file mode 100644 index 00000000..e1634014 --- /dev/null +++ b/test-network-nano-bash/configtx.yaml @@ -0,0 +1,389 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +--- +################################################################################ +# +# Section: Organizations +# +# - This section defines the different organizational identities which will +# be referenced later in the configuration. +# +################################################################################ +Organizations: + + # SampleOrg defines an MSP using the sampleconfig. It should never be used + # in production but may be used as a template for other definitions + - &OrdererOrg + # DefaultOrg defines the organization which is used in the sampleconfig + # of the fabric.git development environment + Name: OrdererOrg + + # ID to load the MSP definition as + ID: OrdererMSP + + # MSPDir is the filesystem path which contains the MSP configuration + MSPDir: crypto-config/ordererOrganizations/example.com/msp + + # Policies defines the set of policies at this level of the config tree + # For organization policies, their canonical path is usually + # /Channel/// + Policies: + Readers: + Type: Signature + Rule: "OR('OrdererMSP.member')" + Writers: + Type: Signature + Rule: "OR('OrdererMSP.member')" + Admins: + Type: Signature + Rule: "OR('OrdererMSP.admin')" + + OrdererEndpoints: + - 127.0.0.1:6050 + - 127.0.0.1:6051 + - 127.0.0.1:6052 + + - &Org1 + # DefaultOrg defines the organization which is used in the sampleconfig + # of the fabric.git development environment + Name: Org1MSP + + # ID to load the MSP definition as + ID: Org1MSP + + MSPDir: crypto-config/peerOrganizations/org1.example.com/msp + + # Policies defines the set of policies at this level of the config tree + # For organization policies, their canonical path is usually + # /Channel/// + Policies: + Readers: + Type: Signature + Rule: "OR('Org1MSP.admin', 'Org1MSP.peer', 'Org1MSP.client')" + Writers: + Type: Signature + Rule: "OR('Org1MSP.admin', 'Org1MSP.client')" + Admins: + Type: Signature + Rule: "OR('Org1MSP.admin')" + Endorsement: + Type: Signature + Rule: "OR('Org1MSP.peer')" + + # leave this flag set to true. + AnchorPeers: + # AnchorPeers defines the location of peers which can be used + # for cross org gossip communication. Note, this value is only + # encoded in the genesis block in the Application section context + - Host: 127.0.0.1 + Port: 7051 + + - &Org2 + # DefaultOrg defines the organization which is used in the sampleconfig + # of the fabric.git development environment + Name: Org2MSP + + # ID to load the MSP definition as + ID: Org2MSP + + MSPDir: crypto-config/peerOrganizations/org2.example.com/msp + + # Policies defines the set of policies at this level of the config tree + # For organization policies, their canonical path is usually + # /Channel/// + Policies: + Readers: + Type: Signature + Rule: "OR('Org2MSP.admin', 'Org2MSP.peer', 'Org2MSP.client')" + Writers: + Type: Signature + Rule: "OR('Org2MSP.admin', 'Org2MSP.client')" + Admins: + Type: Signature + Rule: "OR('Org2MSP.admin')" + Endorsement: + Type: Signature + Rule: "OR('Org2MSP.peer')" + + AnchorPeers: + # AnchorPeers defines the location of peers which can be used + # for cross org gossip communication. Note, this value is only + # encoded in the genesis block in the Application section context + - Host: 127.0.0.1 + Port: 7055 + +################################################################################ +# +# SECTION: Capabilities +# +# - This section defines the capabilities of fabric network. This is a new +# concept as of v1.1.0 and should not be utilized in mixed networks with +# v1.0.x peers and orderers. Capabilities define features which must be +# present in a fabric binary for that binary to safely participate in the +# fabric network. For instance, if a new MSP type is added, newer binaries +# might recognize and validate the signatures from this type, while older +# binaries without this support would be unable to validate those +# transactions. This could lead to different versions of the fabric binaries +# having different world states. Instead, defining a capability for a channel +# informs those binaries without this capability that they must cease +# processing transactions until they have been upgraded. For v1.0.x if any +# capabilities are defined (including a map with all capabilities turned off) +# then the v1.0.x peer will deliberately crash. +# +################################################################################ +Capabilities: + # Channel capabilities apply to both the orderers and the peers and must be + # supported by both. + # Set the value of the capability to true to require it. + Channel: &ChannelCapabilities + # V2_0 capability ensures that orderers and peers behave according + # to v2.0 channel capabilities. Orderers and peers from + # prior releases would behave in an incompatible way, and are therefore + # not able to participate in channels at v2.0 capability. + # Prior to enabling V2.0 channel capabilities, ensure that all + # orderers and peers on a channel are at v2.0.0 or later. + V2_0: true + + # Orderer capabilities apply only to the orderers, and may be safely + # used with prior release peers. + # Set the value of the capability to true to require it. + Orderer: &OrdererCapabilities + # V2_0 orderer capability ensures that orderers behave according + # to v2.0 orderer capabilities. Orderers from + # prior releases would behave in an incompatible way, and are therefore + # not able to participate in channels at v2.0 orderer capability. + # Prior to enabling V2.0 orderer capabilities, ensure that all + # orderers on channel are at v2.0.0 or later. + V2_0: true + + # Application capabilities apply only to the peer network, and may be safely + # used with prior release orderers. + # Set the value of the capability to true to require it. + Application: &ApplicationCapabilities + # V2_0 application capability ensures that peers behave according + # to v2.0 application capabilities. Peers from + # prior releases would behave in an incompatible way, and are therefore + # not able to participate in channels at v2.0 application capability. + # Prior to enabling V2.0 application capabilities, ensure that all + # peers on channel are at v2.0.0 or later. + V2_0: true + +################################################################################ +# +# SECTION: Application +# +# - This section defines the values to encode into a config transaction or +# genesis block for application related parameters +# +################################################################################ +Application: &ApplicationDefaults + + # Organizations is the list of orgs which are defined as participants on + # the application side of the network + Organizations: + + # Policies defines the set of policies at this level of the config tree + # For Application policies, their canonical path is + # /Channel/Application/ + Policies: + Readers: + Type: ImplicitMeta + Rule: "ANY Readers" + Writers: + Type: ImplicitMeta + Rule: "ANY Writers" + Admins: + Type: ImplicitMeta + Rule: "MAJORITY Admins" + LifecycleEndorsement: + Type: Signature + Rule: "OR('Org1MSP.peer','Org2MSP.peer')" + Endorsement: + Type: Signature + Rule: "OR('Org1MSP.peer','Org2MSP.peer')" + + Capabilities: + <<: *ApplicationCapabilities +################################################################################ +# +# SECTION: Orderer +# +# - This section defines the values to encode into a config transaction or +# genesis block for orderer related parameters +# +################################################################################ +Orderer: &OrdererDefaults + + # Orderer Type: The orderer implementation to start + OrdererType: etcdraft + + EtcdRaft: + Consenters: + - Host: 127.0.0.1 + Port: 6050 + ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt + ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt + - Host: 127.0.0.1 + Port: 6051 + ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/server.crt + ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/server.crt + - Host: 127.0.0.1 + Port: 6052 + ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/server.crt + ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/server.crt + + # Options to be specified for all the etcd/raft nodes. The values here + # are the defaults for all new channels and can be modified on a + # per-channel basis via configuration updates. + Options: + # TickInterval is the time interval between two Node.Tick invocations. + #TickInterval: 500ms default + TickInterval: 2500ms + + # ElectionTick is the number of Node.Tick invocations that must pass + # between elections. That is, if a follower does not receive any + # message from the leader of current term before ElectionTick has + # elapsed, it will become candidate and start an election. + # ElectionTick must be greater than HeartbeatTick. + # ElectionTick: 10 default + ElectionTick: 5 + + # HeartbeatTick is the number of Node.Tick invocations that must + # pass between heartbeats. That is, a leader sends heartbeat + # messages to maintain its leadership every HeartbeatTick ticks. + HeartbeatTick: 1 + + # MaxInflightBlocks limits the max number of in-flight append messages + # during optimistic replication phase. + MaxInflightBlocks: 5 + + # SnapshotIntervalSize defines number of bytes per which a snapshot is taken + SnapshotIntervalSize: 16 MB + + # Batch Timeout: The amount of time to wait before creating a batch + BatchTimeout: 2s + + # Batch Size: Controls the number of messages batched into a block + BatchSize: + + # Max Message Count: The maximum number of messages to permit in a batch + MaxMessageCount: 10 + + # Absolute Max Bytes: The absolute maximum number of bytes allowed for + # the serialized messages in a batch. + AbsoluteMaxBytes: 99 MB + + # Preferred Max Bytes: The preferred maximum number of bytes allowed for + # the serialized messages in a batch. A message larger than the preferred + # max bytes will result in a batch larger than preferred max bytes. + PreferredMaxBytes: 512 KB + + # Organizations is the list of orgs which are defined as participants on + # the orderer side of the network + Organizations: + + # Policies defines the set of policies at this level of the config tree + # For Orderer policies, their canonical path is + # /Channel/Orderer/ + Policies: + Readers: + Type: ImplicitMeta + Rule: "ANY Readers" + Writers: + Type: ImplicitMeta + Rule: "ANY Writers" + Admins: + Type: ImplicitMeta + Rule: "MAJORITY Admins" + # BlockValidation specifies what signatures must be included in the block + # from the orderer for the peer to validate it. + BlockValidation: + Type: ImplicitMeta + Rule: "ANY Writers" + +################################################################################ +# +# CHANNEL +# +# This section defines the values to encode into a config transaction or +# genesis block for channel related parameters. +# +################################################################################ +Channel: &ChannelDefaults + # Policies defines the set of policies at this level of the config tree + # For Channel policies, their canonical path is + # /Channel/ + Policies: + # Who may invoke the 'Deliver' API + Readers: + Type: ImplicitMeta + Rule: "ANY Readers" + # Who may invoke the 'Broadcast' API + Writers: + Type: ImplicitMeta + Rule: "ANY Writers" + # By default, who may modify elements at this config level + Admins: + Type: ImplicitMeta + Rule: "MAJORITY Admins" + + # Capabilities describes the channel level capabilities, see the + # dedicated Capabilities section elsewhere in this file for a full + # description + Capabilities: + <<: *ChannelCapabilities + +################################################################################ +# +# Profile +# +# - Different configuration profiles may be encoded here to be specified +# as parameters to the configtxgen tool +# +################################################################################ +Profiles: + + TwoOrgsOrdererGenesis: + <<: *ChannelDefaults + Orderer: + <<: *OrdererDefaults + Organizations: + - *OrdererOrg + Capabilities: + <<: *OrdererCapabilities + Consortiums: + SampleConsortium: + Organizations: + - *Org1 + - *Org2 + TwoOrgsChannel: + Consortium: SampleConsortium + <<: *ChannelDefaults + Application: + <<: *ApplicationDefaults + Organizations: + - *Org1 + - *Org2 + Capabilities: + <<: *ApplicationCapabilities + Org1Channel: + Consortium: SampleConsortium + <<: *ChannelDefaults + Application: + <<: *ApplicationDefaults + Organizations: + - *Org1 + Capabilities: + <<: *ApplicationCapabilities + Org2Channel: + Consortium: SampleConsortium + <<: *ChannelDefaults + Application: + <<: *ApplicationDefaults + Organizations: + - *Org2 + Capabilities: + <<: *ApplicationCapabilities diff --git a/test-network-nano-bash/crypto-config.yaml b/test-network-nano-bash/crypto-config.yaml new file mode 100644 index 00000000..60e0869e --- /dev/null +++ b/test-network-nano-bash/crypto-config.yaml @@ -0,0 +1,54 @@ +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# --------------------------------------------------------------------------- +# "OrdererOrgs" - Definition of organizations managing orderer nodes +# --------------------------------------------------------------------------- +OrdererOrgs: + - Name: Orderer + Domain: example.com + EnableNodeOUs: true + + Specs: + - Hostname: orderer + SANS: + - 127.0.0.1 + - Hostname: orderer2 + SANS: + - 127.0.0.1 + - Hostname: orderer3 + SANS: + - 127.0.0.1 + - Hostname: orderer4 + SANS: + - 127.0.0.1 + - Hostname: orderer5 + SANS: + - 127.0.0.1 + +# --------------------------------------------------------------------------- +# "PeerOrgs" - Definition of organizations managing peer nodes +# --------------------------------------------------------------------------- +PeerOrgs: + - Name: Org1 + Domain: org1.example.com + EnableNodeOUs: true + + Template: + Count: 2 + SANS: + - 127.0.0.1 + Users: + Count: 1 + + - Name: Org2 + Domain: org2.example.com + EnableNodeOUs: true + Template: + Count: 2 + SANS: + - 127.0.0.1 + Users: + Count: 1 diff --git a/test-network-nano-bash/generate_artifacts.sh b/test-network-nano-bash/generate_artifacts.sh new file mode 100755 index 00000000..bf832367 --- /dev/null +++ b/test-network-nano-bash/generate_artifacts.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +# remove existing artifacts, or proceed on if the directories don't exist +rm -r "${PWD}"/channel-artifacts || true +rm -r "${PWD}"/crypto-config || true +rm -r "${PWD}"/data || true + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" + +echo "Generating MSP certificates using cryptogen tool" +cryptogen generate --config="${PWD}"/crypto-config.yaml + +# set FABRIC_CFG_PATH to configtx.yaml directory that contains the profiles +export FABRIC_CFG_PATH="${PWD}" + +echo "Generating orderer genesis block" +configtxgen -profile TwoOrgsOrdererGenesis -channelID test-system-channel-name -outputBlock channel-artifacts/genesis.block + +echo "Generating channel create config transaction" +configtxgen -channelID mychannel -outputCreateChannelTx channel-artifacts/mychannel.tx -profile TwoOrgsChannel + +echo "Generating anchor peer update transaction for Org1" +configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP + +echo "Generating anchor peer update transaction for Org2" +configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP diff --git a/test-network-nano-bash/orderer1.sh b/test-network-nano-bash/orderer1.sh new file mode 100755 index 00000000..c06612e0 --- /dev/null +++ b/test-network-nano-bash/orderer1.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,common.configtx,common.channelconfig=info +export ORDERER_GENERAL_LISTENPORT=6050 +export ORDERER_GENERAL_LOCALMSPID=OrdererMSP +export ORDERER_GENERAL_LOCALMSPDIR="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp +export ORDERER_GENERAL_TLS_ENABLED=true +export ORDERER_GENERAL_TLS_PRIVATEKEY="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key +export ORDERER_GENERAL_TLS_CERTIFICATE="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt +# following setting is not really needed at runtime since channel config has ca root certs, but we need to override the default in orderer.yaml +export ORDERER_GENERAL_TLS_ROOTCAS="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt +export ORDERER_GENERAL_BOOTSTRAPMETHOD=file +export ORDERER_GENERAL_BOOTSTRAPFILE="${PWD}"/channel-artifacts/genesis.block +export ORDERER_FILELEDGER_LOCATION="${PWD}"/data/orderer +export ORDERER_CONSENSUS_WALDIR="${PWD}"/data/orderer/etcdraft/wal +export ORDERER_CONSENSUS_SNAPDIR="${PWD}"/data/orderer/etcdraft/wal +export ORDERER_OPERATIONS_LISTENADDRESS=127.0.0.1:8443 +export ORDERER_ADMIN_LISTENADDRESS=127.0.0.1:9443 + +# start orderer +orderer diff --git a/test-network-nano-bash/orderer2.sh b/test-network-nano-bash/orderer2.sh new file mode 100755 index 00000000..65a135e0 --- /dev/null +++ b/test-network-nano-bash/orderer2.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,common.configtx,common.channelconfig=info +export ORDERER_GENERAL_LISTENPORT=6051 +export ORDERER_GENERAL_LOCALMSPID=OrdererMSP +export ORDERER_GENERAL_LOCALMSPDIR="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/msp +export ORDERER_GENERAL_TLS_ENABLED=true +export ORDERER_GENERAL_TLS_PRIVATEKEY="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/server.key +export ORDERER_GENERAL_TLS_CERTIFICATE="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/server.crt +# following setting is not really needed at runtime since channel config has ca root certs, but we need to override the default in orderer.yaml +export ORDERER_GENERAL_TLS_ROOTCAS="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/ca.crt +export ORDERER_GENERAL_BOOTSTRAPMETHOD=file +export ORDERER_GENERAL_BOOTSTRAPFILE="${PWD}"/channel-artifacts/genesis.block +export ORDERER_FILELEDGER_LOCATION="${PWD}"/data/orderer2 +export ORDERER_CONSENSUS_WALDIR="${PWD}"/data/orderer2/etcdraft/wal +export ORDERER_CONSENSUS_SNAPDIR="${PWD}"/data/orderer2/etcdraft/wal +export ORDERER_OPERATIONS_LISTENADDRESS=127.0.0.1:8444 +export ORDERER_ADMIN_LISTENADDRESS=127.0.0.1:9444 + +# start orderer +orderer diff --git a/test-network-nano-bash/orderer3.sh b/test-network-nano-bash/orderer3.sh new file mode 100755 index 00000000..6f2bb4c6 --- /dev/null +++ b/test-network-nano-bash/orderer3.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,common.configtx,common.channelconfig=info +export ORDERER_GENERAL_LISTENPORT=6052 +export ORDERER_GENERAL_LOCALMSPID=OrdererMSP +export ORDERER_GENERAL_LOCALMSPDIR="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/msp +export ORDERER_GENERAL_TLS_ENABLED=true +export ORDERER_GENERAL_TLS_PRIVATEKEY="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/server.key +export ORDERER_GENERAL_TLS_CERTIFICATE="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/server.crt +# following setting is not really needed at runtime since channel config has ca root certs, but we need to override the default in orderer.yaml +export ORDERER_GENERAL_TLS_ROOTCAS="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/ca.crt +export ORDERER_GENERAL_BOOTSTRAPMETHOD=file +export ORDERER_GENERAL_BOOTSTRAPFILE="${PWD}"/channel-artifacts/genesis.block +export ORDERER_FILELEDGER_LOCATION="${PWD}"/data/orderer3 +export ORDERER_CONSENSUS_WALDIR="${PWD}"/data/orderer3/etcdraft/wal +export ORDERER_CONSENSUS_SNAPDIR="${PWD}"/data/orderer3/etcdraft/wal +export ORDERER_OPERATIONS_LISTENADDRESS=127.0.0.1:8445 +export ORDERER_ADMIN_LISTENADDRESS=127.0.0.1:9445 + +# start orderer +orderer diff --git a/test-network-nano-bash/peer1.sh b/test-network-nano-bash/peer1.sh new file mode 100755 index 00000000..6655936b --- /dev/null +++ b/test-network-nano-bash/peer1.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_TLS_CERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt +export CORE_PEER_TLS_KEY_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key +export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt +export CORE_PEER_ID=peer0.org1.example.com +export CORE_PEER_ADDRESS=127.0.0.1:7051 +export CORE_PEER_LISTENADDRESS=127.0.0.1:7051 +export CORE_PEER_CHAINCODEADDRESS=host.docker.internal:7052 +export CORE_PEER_CHAINCODELISTENADDRESS=127.0.0.1:7052 +# bootstrap peer is the other peer in the same org +export CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7053 +export CORE_PEER_GOSSIP_EXTERNALENDPOINT=127.0.0.1:7051 +export CORE_PEER_LOCALMSPID=Org1MSP +export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp +export CORE_OPERATIONS_LISTENADDRESS=127.0.0.1:8446 +export CORE_PEER_FILESYSTEMPATH="${PWD}"/data/peer0.org1.example.com +export CORE_LEDGER_SNAPSHOTS_ROOTDIR="${PWD}"/data/peer0.org1.example.com/snapshots + +# uncomment the lines below to utilize couchdb state database, when done with the environment you can stop the couchdb container with "docker rm -f couchdb1" +# export CORE_LEDGER_STATE_STATEDATABASE=CouchDB +# export CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=127.0.0.1:5984 +# export CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin +# export CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=password +# docker run --publish 5984:5984 --detach -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --name couchdb1 couchdb:3.1.1 + +# start peer +peer node start diff --git a/test-network-nano-bash/peer1admin.sh b/test-network-nano-bash/peer1admin.sh new file mode 100755 index 00000000..17e4d619 --- /dev/null +++ b/test-network-nano-bash/peer1admin.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=INFO +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt +export CORE_PEER_ADDRESS=127.0.0.1:7051 +export CORE_PEER_LOCALMSPID=Org1MSP +export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp + +# peer1 admin will be responsible for creating channel and adding anchor peer +peer channel create -c mychannel -o 127.0.0.1:6050 -f "${PWD}"/channel-artifacts/mychannel.tx --outputBlock "${PWD}"/channel-artifacts/mychannel.block --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt +peer channel update -o 127.0.0.1:6050 -c mychannel -f "${PWD}"/channel-artifacts/Org1MSPanchors.tx --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt + +# join peer to channel +peer channel join -b "${PWD}"/channel-artifacts/mychannel.block diff --git a/test-network-nano-bash/peer2.sh b/test-network-nano-bash/peer2.sh new file mode 100755 index 00000000..39f66312 --- /dev/null +++ b/test-network-nano-bash/peer2.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_TLS_CERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.crt +export CORE_PEER_TLS_KEY_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.key +export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt +export CORE_PEER_ID=peer1.org1.example.com +export CORE_PEER_ADDRESS=127.0.0.1:7053 +export CORE_PEER_LISTENADDRESS=127.0.0.1:7053 +export CORE_PEER_CHAINCODEADDRESS=host.docker.internal:7054 +export CORE_PEER_CHAINCODELISTENADDRESS=127.0.0.1:7054 +# bootstrap peer is the other peer in the same org +export CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7051 +export CORE_PEER_GOSSIP_EXTERNALENDPOINT=127.0.0.1:7053 +export CORE_PEER_LOCALMSPID=Org1MSP +export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/msp +export CORE_OPERATIONS_LISTENADDRESS=127.0.0.1:8447 +export CORE_PEER_FILESYSTEMPATH="${PWD}"/data/peer1.org1.example.com +export CORE_LEDGER_SNAPSHOTS_ROOTDIR="${PWD}"/data/peer1.org1.example.com/snapshots + +# uncomment the lines below to utilize couchdb state database, when done with the environment you can stop the couchdb container with "docker rm -f couchdb2" +# export CORE_LEDGER_STATE_STATEDATABASE=CouchDB +# export CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=127.0.0.1:5985 +# export CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin +# export CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=password +# docker run --publish 5985:5984 --detach -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --name couchdb2 couchdb:3.1.1 + +# start peer +peer node start diff --git a/test-network-nano-bash/peer2admin.sh b/test-network-nano-bash/peer2admin.sh new file mode 100755 index 00000000..efd5a0c1 --- /dev/null +++ b/test-network-nano-bash/peer2admin.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=INFO +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt +export CORE_PEER_ADDRESS=127.0.0.1:7053 +export CORE_PEER_LOCALMSPID=Org1MSP +export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp + +# join peer to channel +peer channel join -b "${PWD}"/channel-artifacts/mychannel.block diff --git a/test-network-nano-bash/peer3.sh b/test-network-nano-bash/peer3.sh new file mode 100755 index 00000000..58abfdf0 --- /dev/null +++ b/test-network-nano-bash/peer3.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_TLS_CERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.crt +export CORE_PEER_TLS_KEY_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.key +export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt +export CORE_PEER_ID=peer0.org2.example.com +export CORE_PEER_ADDRESS=127.0.0.1:7055 +export CORE_PEER_LISTENADDRESS=127.0.0.1:7055 +export CORE_PEER_CHAINCODEADDRESS=host.docker.internal:7056 +export CORE_PEER_CHAINCODELISTENADDRESS=127.0.0.1:7056 +# bootstrap peer is the other peer in the same org +export CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7057 +export CORE_PEER_GOSSIP_EXTERNALENDPOINT=127.0.0.1:7055 +export CORE_PEER_LOCALMSPID=Org2MSP +export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp +export CORE_OPERATIONS_LISTENADDRESS=127.0.0.1:8448 +export CORE_PEER_FILESYSTEMPATH="${PWD}"/data/peer0.org2.example.com +export CORE_LEDGER_SNAPSHOTS_ROOTDIR="${PWD}"/data/peer0.org2.example.com/snapshots + +# uncomment the lines below to utilize couchdb state database, when done with the environment you can stop the couchdb container with "docker rm -f couchdb3" +# export CORE_LEDGER_STATE_STATEDATABASE=CouchDB +# export CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=127.0.0.1:5986 +# export CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin +# export CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=password +# docker run --publish 5986:5984 --detach -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --name couchdb3 couchdb:3.1.1 + +# start peer +peer node start diff --git a/test-network-nano-bash/peer3admin.sh b/test-network-nano-bash/peer3admin.sh new file mode 100755 index 00000000..c9625163 --- /dev/null +++ b/test-network-nano-bash/peer3admin.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=INFO +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt +export CORE_PEER_ADDRESS=127.0.0.1:7055 +export CORE_PEER_LOCALMSPID=Org2MSP +export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp + +# join peer to channel +peer channel join -b "${PWD}"/channel-artifacts/mychannel.block diff --git a/test-network-nano-bash/peer4.sh b/test-network-nano-bash/peer4.sh new file mode 100755 index 00000000..2923f874 --- /dev/null +++ b/test-network-nano-bash/peer4.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_TLS_CERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/server.crt +export CORE_PEER_TLS_KEY_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/server.key +export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt +export CORE_PEER_ID=peer0.org2.example.com +export CORE_PEER_ADDRESS=127.0.0.1:7057 +export CORE_PEER_LISTENADDRESS=127.0.0.1:7057 +export CORE_PEER_CHAINCODEADDRESS=host.docker.internal:7058 +export CORE_PEER_CHAINCODELISTENADDRESS=127.0.0.1:7058 +# bootstrap peer is the other peer in the same org +export CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7055 +export CORE_PEER_GOSSIP_EXTERNALENDPOINT=127.0.0.1:7057 +export CORE_PEER_LOCALMSPID=Org2MSP +export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/msp +export CORE_OPERATIONS_LISTENADDRESS=127.0.0.1:8449 +export CORE_PEER_FILESYSTEMPATH="${PWD}"/data/peer1.org2.example.com +export CORE_LEDGER_SNAPSHOTS_ROOTDIR="${PWD}"/data/peer1.org2.example.com/snapshots + +# uncomment the lines below to utilize couchdb state database, when done with the environment you can stop the couchdb container with "docker rm -f couchdb4" +# export CORE_LEDGER_STATE_STATEDATABASE=CouchDB +# export CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=127.0.0.1:5987 +# export CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin +# export CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=password +# docker run --publish 5987:5984 --detach -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --name couchdb4 couchdb:3.1.1 + +# start peer +peer node start diff --git a/test-network-nano-bash/peer4admin.sh b/test-network-nano-bash/peer4admin.sh new file mode 100755 index 00000000..07a2373d --- /dev/null +++ b/test-network-nano-bash/peer4admin.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory +export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH" +export FABRIC_CFG_PATH="${PWD}"/../config + +export FABRIC_LOGGING_SPEC=INFO +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt +export CORE_PEER_ADDRESS=127.0.0.1:7057 +export CORE_PEER_LOCALMSPID=Org2MSP +export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp + +# join peer to channel +peer channel join -b "${PWD}"/channel-artifacts/mychannel.block diff --git a/test-network-nano-bash/terminal_setup.png b/test-network-nano-bash/terminal_setup.png new file mode 100644 index 0000000000000000000000000000000000000000..160ccb36830eae7fef7453d942f1293682b71816 GIT binary patch literal 167149 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 Date: Mon, 30 Aug 2021 23:03:08 -0400 Subject: [PATCH 11/15] Retire Jason Yellick as maintainer. It's been some time since I've had the cycles available to review anything here, so removing myself. Signed-off-by: Jason Yellick --- MAINTAINERS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 5c856227..2a4dafb1 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -10,7 +10,6 @@ fabric-samples uses a non-author code review policy, requiring a single approval | Chris Ferris | christo4ferris | cbf | chris.ferris@gmail.com | | Dave Enyeart | denyeart | dave.enyeart | enyeart@us.ibm.com | | Gari Singh | mastersingh24 | mastersingh24 | gari.r.singh@gmail.com | -| Jason Yellick | jyellick | jyellick | jyellick@us.ibm.com | | Matthew B White | mbwhite | mbwhite | whitemat@uk.ibm.com | | Nikhil Gupta | nikhil550 | negupta | nikhilg550@gmail.com | From da65e20f7d3c0b49f001ba8ae0cba4a36abdcf9b Mon Sep 17 00:00:00 2001 From: fraVlaca <86831094+fraVlaca@users.noreply.github.com> Date: Wed, 1 Sep 2021 19:19:15 +0100 Subject: [PATCH 12/15] Updated dependencies for Json and fabric-chaincode-shim:2.+ in java samples (#481) * fixed json dependencies for java Signed-off-by: fraVlaca * updated dependency for asset-tranfer-sbe: now declaing also org.hyperledger.fabric.protos.common Signed-off-by: fraVlaca * "corrected typo of last commit and added testImplementation 'org.hyperledger.fabric.protos.common'" Signed-off-by: fraVlaca * included correct dependecy for fabric-protos and added com.google.protobuf as well Signed-off-by: fraVlaca --- asset-transfer-basic/chaincode-java/build.gradle | 3 ++- asset-transfer-events/chaincode-java/build.gradle | 3 ++- asset-transfer-private-data/chaincode-java/build.gradle | 3 ++- asset-transfer-sbe/chaincode-java/build.gradle | 6 +++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/asset-transfer-basic/chaincode-java/build.gradle b/asset-transfer-basic/chaincode-java/build.gradle index 1b5a2070..41183acd 100644 --- a/asset-transfer-basic/chaincode-java/build.gradle +++ b/asset-transfer-basic/chaincode-java/build.gradle @@ -14,7 +14,8 @@ version '1.0-SNAPSHOT' dependencies { - compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' + implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' + implementation 'org.json:json:+' implementation 'com.owlike:genson:1.5' testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' diff --git a/asset-transfer-events/chaincode-java/build.gradle b/asset-transfer-events/chaincode-java/build.gradle index 17599503..f59f27c7 100644 --- a/asset-transfer-events/chaincode-java/build.gradle +++ b/asset-transfer-events/chaincode-java/build.gradle @@ -13,7 +13,8 @@ group 'org.hyperledger.fabric.samples' version '1.0-SNAPSHOT' dependencies { - compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' + implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' + implementation 'org.json:json:+' testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' } diff --git a/asset-transfer-private-data/chaincode-java/build.gradle b/asset-transfer-private-data/chaincode-java/build.gradle index 1aac92b5..6054373e 100644 --- a/asset-transfer-private-data/chaincode-java/build.gradle +++ b/asset-transfer-private-data/chaincode-java/build.gradle @@ -14,7 +14,8 @@ version '1.0-SNAPSHOT' dependencies { - compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' + implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' + implementation 'org.json:json:+' testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' diff --git a/asset-transfer-sbe/chaincode-java/build.gradle b/asset-transfer-sbe/chaincode-java/build.gradle index 1b5a2070..e220c114 100644 --- a/asset-transfer-sbe/chaincode-java/build.gradle +++ b/asset-transfer-sbe/chaincode-java/build.gradle @@ -14,8 +14,12 @@ version '1.0-SNAPSHOT' dependencies { - compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' + implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' + implementation 'org.json:json:+' + implementation 'com.google.protobuf:protobuf-java:3.+' + implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-protos:2.+' implementation 'com.owlike:genson:1.5' + testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+' testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' testImplementation 'org.assertj:assertj-core:3.11.1' From 36fb3a9273be03b67a77d39272cd7d90b207c341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baran=20K=C4=B1l=C4=B1=C3=A7?= <36154262+kilicbaran@users.noreply.github.com> Date: Thu, 2 Sep 2021 00:05:57 +0300 Subject: [PATCH 13/15] Add ERC1155 chaincode in Go (#463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Baran Kılıç --- token-erc-1155/README.md | 265 ++++++ .../chaincode-go/chaincode/contract.go | 808 ++++++++++++++++++ token-erc-1155/chaincode-go/erc1155.go | 27 + token-erc-1155/chaincode-go/go.mod | 5 + token-erc-1155/chaincode-go/go.sum | 145 ++++ 5 files changed, 1250 insertions(+) create mode 100644 token-erc-1155/README.md create mode 100644 token-erc-1155/chaincode-go/chaincode/contract.go create mode 100644 token-erc-1155/chaincode-go/erc1155.go create mode 100644 token-erc-1155/chaincode-go/go.mod create mode 100644 token-erc-1155/chaincode-go/go.sum diff --git a/token-erc-1155/README.md b/token-erc-1155/README.md new file mode 100644 index 00000000..3298cc9e --- /dev/null +++ b/token-erc-1155/README.md @@ -0,0 +1,265 @@ +# ERC-1155 Chaincode + +This is a sample ERC-1155 chaincode written in Go. + +ERC-20 is the standard for fungible tokens. ERC-721 is the standard for non-fungible tokens. ERC-1155 is the standard for multiple tokens (both fungible and non-fungible). [More information about the ERC-1155 standard can be found here.](https://eips.ethereum.org/EIPS/eip-1155) + +## Architecture +This implementation aims for high throughput by minimizing key collisions. The balance of accounts is distributed over multiple keys. The token transfers can be batched using the batched versions of functions (e.g. BatchTransferFrom, BalanceOfBatch). Since ERC-1155 is account-based, the interface of the chaincode is account-based. However, since the balances are distributed over multiple keys, the chaincode has a model similar to a UTXO-based chaincode internally. + +In this chaincode, one organization has a minter/burner role just like in the [ERC-20 example in this repository](https://github.com/hyperledger/fabric-samples/tree/main/token-erc-20). + + +## Functions Implemented +The following required functions of ERC-1155 are implemented: +- safeTransferFrom +- safeBatchTransferFrom +- balanceOf +- balanceOfBatch +- setApprovalForAll +- isApprovedForAll + +Note: The "safe" prefix is omitted from "TransferFrom" in the implementation because the prefix is related to some issue about backwards compatibility with older smart contracts in Ethereum. + +Note: TransferFrom is used to send a single token type between two users. BatchTransferFrom is used to send multiple tokens between two users. So, BatchTransferFrom is a more general form of TransferFrom. Ethereum defined two functions instead of one because this reduces gas costs. Since there is no gas cost in Fabric, implementing these two functions is unnecessary. Nevertheless, both of them are implemented to to conform the ERC-1155 standard. + +## Additional Functions Implemented + +The following additional functions are also implemented. The following paragraphs give the reasoning behind adding these functions: +- Optional Metadata URI extension: +Defined in ERC-1155 but not required. Allows one to set a URI for tokens and get the URI. + - SetURI + - URI +- Mint/Burn extension: +Although Mint / Burn are not required, they are necessary to change the supply of tokens, create new fungible or non-fungible tokens. In a real implementation, they will be implemented unless the supply of the tokens is fixed beforehand. MintBatch / BurnBatch is only implemented to complement the TransferFrom/BatchTransferFrom. Actually, using only MintBatch and BurnBatch would be enough. + - Mint + - MintBatch + - Burn + - BurnBatch +- Extra/utility functions + - BatchTransferFromMultiRecipient: This is not defined in the standard. We created this function to solve an issue we encountered. It is only required if a person wants to send tokens to multiple persons in a blockchain block. If a person doesn't use this function and create two transactions in a single block, there will be key conflicts because the chaincode will try to decrement the balance of the sender twice in a block and this causes a key conflict in Fabric [just like explained in here](https://github.com/hyperledger/fabric-samples/tree/main/high-throughput). This problem does not exist in Ethereum because, in Ethereum, the transactions are ordered before they are executed. + - BroadcastTokenExistence: Explained in ERC-1155 but it is not required. It is only used if a token minter wants to announce the existence of a token without minting it. + - ClientAccountID: This function is special for Fabric because we do not have wallet addresses in Fabric and users need to know their account ID to transfer tokens. + - ClientAccountBalance: A shorthand for BalanceOf function. + +## Example Usage + +### Launch test network + +Open a command terminal and navigate to the test network directory. + +```bash +cd fabric-samples/test-network +``` + +Clean up the existing network if you have any. +```bash +./network.sh down +``` + +Start test network +```bash +./network.sh up createChannel -ca +``` + +### Deploy chaincode + +Deploy ERC-1155 chaincode. + +```bash +./network.sh deployCC -ccn erc1155 -ccp ../token-erc-1155/chaincode-go/ -ccl go +``` + +### Register identities + +In this example, there are two organizations (org). We will register new identities using the Org1 and Org2 Certificate Authorities (CA's), and then use the CA's to generate each identity's certificate and private key. + +We need to set the following environment variables to use the Fabric CA client (and subsequent commands). The first command sets Fabric config path. The second command adds Fabric CLI utilities to path. + +```bash +export FABRIC_CFG_PATH=${PWD}/../config/ +export PATH=${PWD}/../bin:$PATH +``` + +The terminal we have been using will represent Org1. We will use the Org1 CA to create a new identity. Set the Fabric CA client home to the MSP of the Org1 CA admin (this identity was generated by the test network script). + +```bash +export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/ +``` + +Register `Person1` identity to Org1. +```bash +fabric-ca-client register --caname ca-org1 --id.name person1 --id.secret person1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" +``` + +Generate the identity certificates and MSP folder. + +```bash +fabric-ca-client enroll -u https://person1:person1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem" +``` + +Copy the Node OU configuration file into the identity MSP folder. + +```bash +cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp/config.yaml" +``` + +Open a new terminal to represent Org2 and navigate to fabric-samples/test-network. We'll use the Org2 CA to create the Org2 identities. Set the Fabric CA client home to the MSP of the Org2 CA admin. + +```bash +cd fabric-samples/test-network +export FABRIC_CFG_PATH=${PWD}/../config/ +export PATH=${PWD}/../bin:$PATH +export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/ +``` + +Register `Person2`, `Person3`, `Person4`, `Person5` to Org2. + +```bash +fabric-ca-client register --caname ca-org2 --id.name person2 --id.secret person2pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" +fabric-ca-client enroll -u https://person2:person2pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" +cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp/config.yaml" + +fabric-ca-client register --caname ca-org2 --id.name person3 --id.secret person3pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" +fabric-ca-client enroll -u https://person3:person3pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person3@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" +cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person3@org2.example.com/msp/config.yaml" + +fabric-ca-client register --caname ca-org2 --id.name person4 --id.secret person4pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" +fabric-ca-client enroll -u https://person4:person4pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person4@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" +cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person4@org2.example.com/msp/config.yaml" + +fabric-ca-client register --caname ca-org2 --id.name person5 --id.secret person5pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" +fabric-ca-client enroll -u https://person5:person5pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person5@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem" +cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person5@org2.example.com/msp/config.yaml" +``` + +### Get account ID + +Go back to the Org1 terminal, we'll set the following environment variables to operate the peer CLI as the minter identity from Org1. + +```bash +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_LOCALMSPID="Org1MSP" +export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp +export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt +export CORE_PEER_ADDRESS=localhost:7051 +export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt") +``` + +Get client account ID for Person1. +```bash +peer chaincode query -C mychannel -n erc1155 -c '{"function":"ClientAccountID","Args":[]}' +``` + +``` +eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT +``` + +Client Account ID is a base64-encoded concatenation of the issuer and subject from the client identity's enrolment certificate. +You can decode it with the following command: + +```bash +echo "eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT" | base64 --decode +``` + +``` +x509::CN=person1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US +``` + +Set the client account IDs as environment variables on both of the terminals to make the subsequent commands more readable. +```bash +export P1="eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT" +export P2="eDUwOTo6Q049cGVyc29uMixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs=" +export P3="eDUwOTo6Q049cGVyc29uMyxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs=" +export P4="eDUwOTo6Q049cGVyc29uNCxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs=" +export P5="eDUwOTo6Q049cGVyc29uNSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs=" +``` + +### Mint tokens + +Mint tokens by calling the MintBatch function in order to create 100 token1s, 200 token2s, 300 token3s, 150 token4s, 100 token5s, 100 token6s as Person P1 from organization 1. + +```bash +peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"MintBatch\",\"Args\":[\"$P1\",\"[1,2,3,4,5,6]\",\"[100,200,300,150,100,100]\"]}" --waitForEvent +``` + +Query the tokens of Person1. + +```bash +peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOfBatch\",\"Args\":[\"[\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\"]\",\"[1,2,3,4,5,6]\"]}" +``` + +``` +[100,200,300,150,100,100] +``` + +Side note: There may seem too many slashes in the previous command. It double escapes the quotes. One escape is to be able to use quotes in the `-c` argument of the command. The second escape is necessary to pass the account IDs as an array. Quote is needed since the elements of the array are strings. + +### Transfer tokens + +#### TransferFrom + +Send Person P2 six token3s by calling TransferFrom as Person P1. +```bash +peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"TransferFrom\",\"Args\":[\"$P1\",\"$P2\",\"3\",\"6\"]}" --waitForEvent +``` + +Get the new Balance of Person1 for token3. +```bash +peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOf\",\"Args\":[\"$P1\",\"3\"]}" +``` + +``` +294 +``` + +Switch to the Org2 terminal and set the following environment variables. +```bash +export FABRIC_CFG_PATH=$PWD/../config/ +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_LOCALMSPID="Org2MSP" +export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp +export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt +export CORE_PEER_ADDRESS=localhost:9051 +export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt") +``` + +Get the new balance of Person2 for token3. +```bash +peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOf\",\"Args\":[\"$P2\",\"3\"]}" +``` + +``` +6 +``` + +#### BatchTransferFrom + +Switch to the Org1 terminal. + +Send Person P2 six token3s, three token4s, and one token2s by calling BatchTransferFrom as Person P1. + +```bash +peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"BatchTransferFrom\",\"Args\":[\"$P1\",\"$P2\",\"[3,4,2]\",\"[6,3,1]\"]}" --waitForEvent +``` + +#### BatchTransferFromMultiReceipent + +Call BatchTransferFromMultiReceipent as Person1 in order to send: +- six token5s to person P3, +- six token3s to person P4, +- three token4s to person P2, +- two token2s to person P5, +- and three token6s to person P2. + +```bash +peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"BatchTransferFromMultiRecipient\",\"Args\":[\"$P1\",\"[\\\"$P3\\\",\\\"$P4\\\",\\\"$P2\\\",\\\"$P5\\\",\\\"$P2\\\"]\",\"[5,3,4,2,6]\",\"[6,6,3,2,3]\"]}" --waitForEvent +``` + +### Clean up + +When you are finished, you can bring down the test network. This command will bring down the CAs, peers, and ordering node of the network that you created. + +```bash +./network.sh down +``` diff --git a/token-erc-1155/chaincode-go/chaincode/contract.go b/token-erc-1155/chaincode-go/chaincode/contract.go new file mode 100644 index 00000000..dfe1c3cb --- /dev/null +++ b/token-erc-1155/chaincode-go/chaincode/contract.go @@ -0,0 +1,808 @@ +/* + 2021 Baran Kılıç + + SPDX-License-Identifier: Apache-2.0 +*/ + +package chaincode + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/hyperledger/fabric-contract-api-go/contractapi" +) + +const uriKey = "uri" + +const balancePrefix = "account~tokenId~sender" +const approvalPrefix = "account~operator" + +const minterMSPID = "Org1MSP" + +// SmartContract provides functions for transferring tokens between accounts +type SmartContract struct { + contractapi.Contract +} + +// TransferSingle MUST emit when a single token is transferred, including zero +// value transfers as well as minting or burning. +// The operator argument MUST be msg.sender. +// The from argument MUST be the address of the holder whose balance is decreased. +// The to argument MUST be the address of the recipient whose balance is increased. +// The id argument MUST be the token type being transferred. +// The value argument MUST be the number of tokens the holder balance is decreased +// by and match what the recipient balance is increased by. +// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address). +// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address). +type TransferSingle struct { + Operator string `json:"operator"` + From string `json:"from"` + To string `json:"to"` + ID uint64 `json:"id"` + Value uint64 `json:"value"` +} + +// TransferBatch MUST emit when tokens are transferred, including zero value +// transfers as well as minting or burning. +// The operator argument MUST be msg.sender. +// The from argument MUST be the address of the holder whose balance is decreased. +// The to argument MUST be the address of the recipient whose balance is increased. +// The ids argument MUST be the list of tokens being transferred. +// The values argument MUST be the list of number of tokens (matching the list +// and order of tokens specified in _ids) the holder balance is decreased by +// and match what the recipient balance is increased by. +// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address). +// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address). +type TransferBatch struct { + Operator string `json:"operator"` + From string `json:"from"` + To string `json:"to"` + IDs []uint64 `json:"ids"` + Values []uint64 `json:"values"` +} + +// TransferBatchMultiRecipient MUST emit when tokens are transferred, including zero value +// transfers as well as minting or burning. +// The operator argument MUST be msg.sender. +// The from argument MUST be the address of the holder whose balance is decreased. +// The to argument MUST be the list of the addresses of the recipients whose balance is increased. +// The ids argument MUST be the list of tokens being transferred. +// The values argument MUST be the list of number of tokens (matching the list +// and order of tokens specified in _ids) the holder balance is decreased by +// and match what the recipient balance is increased by. +// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address). +// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address). +type TransferBatchMultiRecipient struct { + Operator string `json:"operator"` + From string `json:"from"` + To []string `json:"to"` + IDs []uint64 `json:"ids"` + Values []uint64 `json:"values"` +} + +// ApprovalForAll MUST emit when approval for a second party/operator address +// to manage all tokens for an owner address is enabled or disabled +// (absence of an event assumes disabled). +type ApprovalForAll struct { + Owner string `json:"owner"` + Operator string `json:"operator"` + Approved bool `json:"approved"` +} + +// URI MUST emit when the URI is updated for a token ID. +// Note: This event is not used in this contract implementation because in this implementation, +// only the programmatic way of setting URI is used. The URI should contain {id} as part of it +// and the clients MUST replace this with the actual token ID. +type URI struct { + Value string `json:"value"` + ID uint64 `json:"id"` +} + +type ToID struct { + To string + ID uint64 +} + +// Mint creates amount tokens of token type id and assigns them to account. +// This function emits a TransferSingle event. +func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens + err := authorizationHelper(ctx) + if err != nil { + return err + } + + // Get ID of submitting client identity + operator, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Mint tokens + err = mintHelper(ctx, operator, account, id, amount) + if err != nil { + return err + } + + // Emit TransferSingle event + transferSingleEvent := TransferSingle{operator, "0x0", account, id, amount} + return emitTransferSingle(ctx, transferSingleEvent) +} + +// MintBatch creates amount tokens for each token type id and assigns them to account. +// This function emits a TransferBatch event. +func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error { + + if len(ids) != len(amounts) { + return fmt.Errorf("ids and amounts must have the same length") + } + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens + err := authorizationHelper(ctx) + if err != nil { + return err + } + + // Get ID of submitting client identity + operator, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Group amount by token id because we can only send token to a recipient only one time in a block. This prevents key conflicts + amountToSend := make(map[uint64]uint64) // token id => amount + + for i := 0; i < len(amounts); i++ { + amountToSend[ids[i]] += amounts[i] + } + + // Mint tokens + for id, amount := range amountToSend { + err = mintHelper(ctx, operator, account, id, amount) + if err != nil { + return err + } + } + + // Emit TransferBatch event + transferBatchEvent := TransferBatch{operator, "0x0", account, ids, amounts} + return emitTransferBatch(ctx, transferBatchEvent) +} + +// Burn destroys amount tokens of token type id from account. +// This function triggers a TransferSingle event. +func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error { + + if account == "0x0" { + return fmt.Errorf("burn to the zero address") + } + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens + err := authorizationHelper(ctx) + if err != nil { + return err + } + + // Get ID of submitting client identity + operator, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Burn tokens + err = removeBalance(ctx, account, []uint64{id}, []uint64{amount}) + if err != nil { + return err + } + + transferSingleEvent := TransferSingle{operator, account, "0x0", id, amount} + return emitTransferSingle(ctx, transferSingleEvent) +} + +// BurnBatch destroys amount tokens of for each token type id from account. +// This function emits a TransferBatch event. +func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error { + + if account == "0x0" { + return fmt.Errorf("burn to the zero address") + } + + if len(ids) != len(amounts) { + return fmt.Errorf("ids and amounts must have the same length") + } + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens + err := authorizationHelper(ctx) + if err != nil { + return err + } + + // Get ID of submitting client identity + operator, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + err = removeBalance(ctx, account, ids, amounts) + if err != nil { + return err + } + + transferBatchEvent := TransferBatch{operator, account, "0x0", ids, amounts} + return emitTransferBatch(ctx, transferBatchEvent) +} + +// TransferFrom transfers tokens from sender account to recipient account +// recipient account must be a valid clientID as returned by the ClientID() function +// This function triggers a TransferSingle event +func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error { + if sender == recipient { + return fmt.Errorf("transfer to self") + } + + // Get ID of submitting client identity + operator, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Check whether operator is owner or approved + if operator != sender { + approved, err := _isApprovedForAll(ctx, sender, operator) + if err != nil { + return err + } + if !approved { + return fmt.Errorf("caller is not owner nor is approved") + } + } + + // Withdraw the funds from the sender address + err = removeBalance(ctx, sender, []uint64{id}, []uint64{amount}) + if err != nil { + return err + } + + if recipient == "0x0" { + return fmt.Errorf("transfer to the zero address") + } + + // Deposit the fund to the recipient address + err = addBalance(ctx, sender, recipient, id, amount) + if err != nil { + return err + } + + // Emit TransferSingle event + transferSingleEvent := TransferSingle{operator, sender, recipient, id, amount} + return emitTransferSingle(ctx, transferSingleEvent) +} + +// BatchTransferFrom transfers multiple tokens from sender account to recipient account +// recipient account must be a valid clientID as returned by the ClientID() function +// This function triggers a TransferBatch event +func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, ids []uint64, amounts []uint64) error { + if sender == recipient { + return fmt.Errorf("transfer to self") + } + + if len(ids) != len(amounts) { + return fmt.Errorf("ids and amounts must have the same length") + } + + // Get ID of submitting client identity + operator, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Check whether operator is owner or approved + if operator != sender { + approved, err := _isApprovedForAll(ctx, sender, operator) + if err != nil { + return err + } + if !approved { + return fmt.Errorf("caller is not owner nor is approved") + } + } + + // Withdraw the funds from the sender address + err = removeBalance(ctx, sender, ids, amounts) + if err != nil { + return err + } + + if recipient == "0x0" { + return fmt.Errorf("transfer to the zero address") + } + + // Group amount by token id because we can only send token to a recipient only one time in a block. This prevents key conflicts + amountToSend := make(map[uint64]uint64) // token id => amount + + for i := 0; i < len(amounts); i++ { + amountToSend[ids[i]] += amounts[i] + } + + // Deposit the funds to the recipient address + for id, amount := range amountToSend { + err = addBalance(ctx, sender, recipient, id, amount) + if err != nil { + return err + } + } + + transferBatchEvent := TransferBatch{operator, sender, recipient, ids, amounts} + return emitTransferBatch(ctx, transferBatchEvent) +} + +// BatchTransferFromMultiRecipient transfers multiple tokens from sender account to multiple recipient accounts +// recipient account must be a valid clientID as returned by the ClientID() function +// This function triggers a TransferBatchMultiRecipient event +func (s *SmartContract) BatchTransferFromMultiRecipient(ctx contractapi.TransactionContextInterface, sender string, recipients []string, ids []uint64, amounts []uint64) error { + + if len(recipients) != len(ids) || len(ids) != len(amounts) { + return fmt.Errorf("recipients, ids, and amounts must have the same length") + } + + for _, recipient := range recipients { + if sender == recipient { + return fmt.Errorf("transfer to self") + } + } + + // Get ID of submitting client identity + operator, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Check whether operator is owner or approved + if operator != sender { + approved, err := _isApprovedForAll(ctx, sender, operator) + if err != nil { + return err + } + if !approved { + return fmt.Errorf("caller is not owner nor is approved") + } + } + + // Withdraw the funds from the sender address + err = removeBalance(ctx, sender, ids, amounts) + if err != nil { + return err + } + + // Group amount by (recipient, id ) pair because we can only send token to a recipient only one time in a block. This prevents key conflicts + amountToSend := make(map[ToID]uint64) // (recipient, id ) => amount + + for i := 0; i < len(amounts); i++ { + amountToSend[ToID{recipients[i], ids[i]}] += amounts[i] + } + + // Deposit the funds to the recipient addresses + for key, amount := range amountToSend { + if key.To == "0x0" { + return fmt.Errorf("transfer to the zero address") + } + + err = addBalance(ctx, sender, key.To, key.ID, amount) + if err != nil { + return err + } + } + + // Emit TransferBatchMultiRecipient event + transferBatchMultiRecipientEvent := TransferBatchMultiRecipient{operator, sender, recipients, ids, amounts} + return emitTransferBatchMultiRecipient(ctx, transferBatchMultiRecipientEvent) +} + +// IsApprovedForAll returns true if operator is approved to transfer account's tokens. +func (s *SmartContract) IsApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) { + return _isApprovedForAll(ctx, account, operator) +} + +// _isApprovedForAll returns true if operator is approved to transfer account's tokens. +func _isApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) { + approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator}) + if err != nil { + return false, fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err) + } + + approvalBytes, err := ctx.GetStub().GetState(approvalKey) + if err != nil { + return false, fmt.Errorf("failed to read approval of operator %s for account %s from world state: %v", operator, account, err) + } + + if approvalBytes == nil { + return false, nil + } + + var approved bool + err = json.Unmarshal(approvalBytes, &approved) + if err != nil { + return false, fmt.Errorf("failed to decode approval JSON of operator %s for account %s: %v", operator, account, err) + } + + return approved, nil +} + +// SetApprovalForAll returns true if operator is approved to transfer account's tokens. +func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) error { + // Get ID of submitting client identity + account, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + if account == operator { + return fmt.Errorf("setting approval status for self") + } + + approvalForAllEvent := ApprovalForAll{account, operator, approved} + approvalForAllEventJSON, err := json.Marshal(approvalForAllEvent) + if err != nil { + return fmt.Errorf("failed to obtain JSON encoding: %v", err) + } + err = ctx.GetStub().SetEvent("ApprovalForAll", approvalForAllEventJSON) + if err != nil { + return fmt.Errorf("failed to set event: %v", err) + } + + approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator}) + if err != nil { + return fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err) + } + + approvalJSON, err := json.Marshal(approved) + if err != nil { + return fmt.Errorf("failed to encode approval JSON of operator %s for account %s: %v", operator, account, err) + } + + err = ctx.GetStub().PutState(approvalKey, approvalJSON) + if err != nil { + return err + } + + return nil +} + +// BalanceOf returns the balance of the given account +func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) { + return balanceOfHelper(ctx, account, id) +} + +// BalanceOfBatch returns the balance of multiple account/token pairs +func (s *SmartContract) BalanceOfBatch(ctx contractapi.TransactionContextInterface, accounts []string, ids []uint64) ([]uint64, error) { + if len(accounts) != len(ids) { + return nil, fmt.Errorf("accounts and ids must have the same length") + } + + balances := make([]uint64, len(accounts)) + + for i := 0; i < len(accounts); i++ { + var err error + balances[i], err = balanceOfHelper(ctx, accounts[i], ids[i]) + if err != nil { + return nil, err + } + } + + return balances, nil +} + +// ClientAccountBalance returns the balance of the requesting client's account +func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface, id uint64) (uint64, error) { + + // Get ID of submitting client identity + clientID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return 0, fmt.Errorf("failed to get client id: %v", err) + } + + return balanceOfHelper(ctx, clientID, id) +} + +// ClientAccountID returns the id of the requesting client's account +// In this implementation, the client account ID is the clientId itself +// Users can use this function to get their own account id, which they can then give to others as the payment address +func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) { + + // Get ID of submitting client identity + clientAccountID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return "", fmt.Errorf("failed to get client id: %v", err) + } + + return clientAccountID, nil +} + +// SetURI set the URI value +// This function triggers URI event for each token id +func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri string) error { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens + err := authorizationHelper(ctx) + if err != nil { + return err + } + + if !strings.Contains(uri, "{id}") { + return fmt.Errorf("failed to set uri, uri should contain '{id}'") + } + + err = ctx.GetStub().PutState(uriKey, []byte(uri)) + if err != nil { + return fmt.Errorf("failed to set uri: %v", err) + } + + return nil +} + +// URI returns the URI +func (s *SmartContract) URI(ctx contractapi.TransactionContextInterface, id uint64) (string, error) { + + uriBytes, err := ctx.GetStub().GetState(uriKey) + if err != nil { + return "", fmt.Errorf("failed to get uri: %v", err) + } + + if uriBytes == nil { + return "", fmt.Errorf("no uri is set: %v", err) + } + + return string(uriBytes), nil +} + +func (s *SmartContract) BroadcastTokenExistance(ctx contractapi.TransactionContextInterface, id uint64) error { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens + err := authorizationHelper(ctx) + if err != nil { + return err + } + + // Get ID of submitting client identity + operator, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Emit TransferSingle event + transferSingleEvent := TransferSingle{operator, "0x0", "0x0", id, 0} + return emitTransferSingle(ctx, transferSingleEvent) +} + +// Helper Functions + +// authorizationHelper checks minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens +func authorizationHelper(ctx contractapi.TransactionContextInterface) error { + + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return fmt.Errorf("failed to get MSPID: %v", err) + } + if clientMSPID != minterMSPID { + return fmt.Errorf("client is not authorized to mint new tokens") + } + + return nil +} + +func mintHelper(ctx contractapi.TransactionContextInterface, operator string, account string, id uint64, amount uint64) error { + if account == "0x0" { + return fmt.Errorf("mint to the zero address") + } + + if amount <= 0 { + return fmt.Errorf("mint amount must be a positive integer") + } + + err := addBalance(ctx, operator, account, id, amount) + if err != nil { + return err + } + + return nil +} + +func addBalance(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error { + // Convert id to string + idString := strconv.FormatUint(uint64(id), 10) + + balanceKey, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{recipient, idString, sender}) + if err != nil { + return fmt.Errorf("failed to create the composite key for prefix %s: %v", balancePrefix, err) + } + + balanceBytes, err := ctx.GetStub().GetState(balanceKey) + if err != nil { + return fmt.Errorf("failed to read account %s from world state: %v", recipient, err) + } + + var balance uint64 = 0 + if balanceBytes != nil { + balance, _ = strconv.ParseUint(string(balanceBytes), 10, 64) + } + + balance += amount + + err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(balance), 10))) + if err != nil { + return err + } + + return nil +} + +func setBalance(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error { + // Convert id to string + idString := strconv.FormatUint(uint64(id), 10) + + balanceKey, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{recipient, idString, sender}) + if err != nil { + return fmt.Errorf("failed to create the composite key for prefix %s: %v", balancePrefix, err) + } + + err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(amount), 10))) + if err != nil { + return err + } + + return nil +} + +func removeBalance(ctx contractapi.TransactionContextInterface, sender string, ids []uint64, amounts []uint64) error { + // Calculate the total amount of each token to withdraw + necessaryFunds := make(map[uint64]uint64) // token id -> necessary amount + + for i := 0; i < len(amounts); i++ { + necessaryFunds[ids[i]] += amounts[i] + } + + // Check whether the sender has the necessary funds and withdraw them from the account + for tokenId, neededAmount := range necessaryFunds { + idString := strconv.FormatUint(uint64(tokenId), 10) + + var partialBalance uint64 + var selfRecipientKeyNeedsToBeRemoved bool + var selfRecipientKey string + + balanceIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(balancePrefix, []string{sender, idString}) + if err != nil { + return fmt.Errorf("failed to get state for prefix %v: %v", balancePrefix, err) + } + defer balanceIterator.Close() + + // Iterate over keys that store balances and add them to partialBalance until + // either the necessary amount is reached or the keys ended + for balanceIterator.HasNext() && partialBalance < neededAmount { + queryResponse, err := balanceIterator.Next() + if err != nil { + return fmt.Errorf("failed to get the next state for prefix %v: %v", balancePrefix, err) + } + + partBalAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64) + partialBalance += partBalAmount + + _, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(queryResponse.Key) + if err != nil { + return err + } + + if compositeKeyParts[2] == sender { + selfRecipientKeyNeedsToBeRemoved = true + selfRecipientKey = queryResponse.Key + } else { + err = ctx.GetStub().DelState(queryResponse.Key) + if err != nil { + return fmt.Errorf("failed to delete the state of %v: %v", queryResponse.Key, err) + } + } + } + + if partialBalance < neededAmount { + return fmt.Errorf("sender has insufficient funds for token %v, needed funds: %v, available fund: %v", tokenId, neededAmount, partialBalance) + } else if partialBalance > neededAmount { + // Send the remainder back to the sender + remainder := partialBalance - neededAmount + if selfRecipientKeyNeedsToBeRemoved { + // Set balance for the key that has the same address for sender and recipient + err = setBalance(ctx, sender, sender, tokenId, remainder) + if err != nil { + return err + } + } else { + err = addBalance(ctx, sender, sender, tokenId, remainder) + if err != nil { + return err + } + } + + } else { + // Delete self recipient key + err = ctx.GetStub().DelState(selfRecipientKey) + if err != nil { + return fmt.Errorf("failed to delete the state of %v: %v", selfRecipientKey, err) + } + } + } + + return nil +} + +func emitTransferSingle(ctx contractapi.TransactionContextInterface, transferSingleEvent TransferSingle) error { + transferSingleEventJSON, err := json.Marshal(transferSingleEvent) + if err != nil { + return fmt.Errorf("failed to obtain JSON encoding: %v", err) + } + + err = ctx.GetStub().SetEvent("TransferSingle", transferSingleEventJSON) + if err != nil { + return fmt.Errorf("failed to set event: %v", err) + } + + return nil +} + +func emitTransferBatch(ctx contractapi.TransactionContextInterface, transferBatchEvent TransferBatch) error { + transferBatchEventJSON, err := json.Marshal(transferBatchEvent) + if err != nil { + return fmt.Errorf("failed to obtain JSON encoding: %v", err) + } + err = ctx.GetStub().SetEvent("TransferBatch", transferBatchEventJSON) + if err != nil { + return fmt.Errorf("failed to set event: %v", err) + } + + return nil +} + +func emitTransferBatchMultiRecipient(ctx contractapi.TransactionContextInterface, transferBatchMultiRecipientEvent TransferBatchMultiRecipient) error { + transferBatchMultiRecipientEventJSON, err := json.Marshal(transferBatchMultiRecipientEvent) + if err != nil { + return fmt.Errorf("failed to obtain JSON encoding: %v", err) + } + err = ctx.GetStub().SetEvent("TransferBatchMultiRecipient", transferBatchMultiRecipientEventJSON) + if err != nil { + return fmt.Errorf("failed to set event: %v", err) + } + + return nil +} + +// balanceOfHelper returns the balance of the given account +func balanceOfHelper(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) { + + if account == "0x0" { + return 0, fmt.Errorf("balance query for the zero address") + } + + // Convert id to string + idString := strconv.FormatUint(uint64(id), 10) + + var balance uint64 + + balanceIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(balancePrefix, []string{account, idString}) + if err != nil { + return 0, fmt.Errorf("failed to get state for prefix %v: %v", balancePrefix, err) + } + defer balanceIterator.Close() + + for balanceIterator.HasNext() { + queryResponse, err := balanceIterator.Next() + if err != nil { + return 0, fmt.Errorf("failed to get the next state for prefix %v: %v", balancePrefix, err) + } + + balAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64) + balance += balAmount + } + + return balance, nil +} diff --git a/token-erc-1155/chaincode-go/erc1155.go b/token-erc-1155/chaincode-go/erc1155.go new file mode 100644 index 00000000..3a111b97 --- /dev/null +++ b/token-erc-1155/chaincode-go/erc1155.go @@ -0,0 +1,27 @@ +/* + 2021 Baran Kılıç + + SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "erc1155/chaincode" + + "github.com/hyperledger/fabric-contract-api-go/contractapi" +) + +func main() { + smartContract := new(chaincode.SmartContract) + + cc, err := contractapi.NewChaincode(smartContract) + + if err != nil { + panic(err.Error()) + } + + if err := cc.Start(); err != nil { + panic(err.Error()) + } +} diff --git a/token-erc-1155/chaincode-go/go.mod b/token-erc-1155/chaincode-go/go.mod new file mode 100644 index 00000000..4b49a6f6 --- /dev/null +++ b/token-erc-1155/chaincode-go/go.mod @@ -0,0 +1,5 @@ +module erc1155 + +go 1.16 + +require github.com/hyperledger/fabric-contract-api-go v1.1.1 diff --git a/token-erc-1155/chaincode-go/go.sum b/token-erc-1155/chaincode-go/go.sum new file mode 100644 index 00000000..8ff9fad9 --- /dev/null +++ b/token-erc-1155/chaincode-go/go.sum @@ -0,0 +1,145 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= +github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212 h1:1i4lnpV8BDgKOLi1hgElfBqdHXjXieSuj8629mwBZ8o= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc= +github.com/hyperledger/fabric-contract-api-go v1.1.1 h1:gDhOC18gjgElNZ85kFWsbCQq95hyUP/21n++m0Sv6B0= +github.com/hyperledger/fabric-contract-api-go v1.1.1/go.mod h1:+39cWxbh5py3NtXpRA63rAH7NzXyED+QJx1EZr0tJPo= +github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e h1:9PS5iezHk/j7XriSlNuSQILyCOfcZ9wZ3/PiucmSE8E= +github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ= +golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 2b47c9f0c00788a82a8eabf6d0f8bee81b4c430f Mon Sep 17 00:00:00 2001 From: Arnaud J Le Hors Date: Thu, 2 Sep 2021 11:26:14 +0200 Subject: [PATCH 14/15] Update Compose Spec & VM Spec Version (#482) Based on PR 449 by Brett Logan Signed-off-by: Arnaud J Le Hors --- ci/azure-pipelines.yml | 28 +++++++++---------- .../docker/docker-compose-ca-org3.yaml | 2 +- .../docker/docker-compose-couch-org3.yaml | 2 +- .../addOrg3/docker/docker-compose-org3.yaml | 2 +- test-network/docker/docker-compose-ca.yaml | 2 +- test-network/docker/docker-compose-couch.yaml | 2 +- .../docker/docker-compose-test-net.yaml | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index ea01dbe7..6b1908f1 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -17,7 +17,7 @@ jobs: - job: CommercialPaper_Go displayName: Commercial Paper (Go) pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 steps: - template: templates/install-deps.yml - template: templates/commercial-paper/azure-pipelines-go.yml @@ -25,7 +25,7 @@ jobs: - job: CommercialPaper_Java displayName: Commercial Paper (Java) pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 steps: - template: templates/install-deps.yml - template: templates/commercial-paper/azure-pipelines-java.yml @@ -33,7 +33,7 @@ jobs: - job: CommercialPaper_JavaScript displayName: Commercial Paper (JavaScript) pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 steps: - template: templates/install-deps.yml - template: templates/commercial-paper/azure-pipelines-javascript.yml @@ -41,7 +41,7 @@ jobs: - job: FabCar_Go displayName: FabCar (Go) pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 steps: - template: templates/install-deps.yml - template: templates/fabcar/azure-pipelines-go.yml @@ -49,7 +49,7 @@ jobs: - job: FabCar_Java displayName: FabCar (Java) pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 steps: - template: templates/install-deps.yml - template: templates/fabcar/azure-pipelines-java.yml @@ -57,7 +57,7 @@ jobs: - job: FabCar_JavaScript displayName: FabCar (JavaScript) pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 steps: - template: templates/install-deps.yml - template: templates/fabcar/azure-pipelines-javascript.yml @@ -65,7 +65,7 @@ jobs: - job: Fabcar_TypeScript displayName: FabCar (TypeScript) pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 steps: - template: templates/install-deps.yml - template: templates/fabcar/azure-pipelines-typescript.yml @@ -73,7 +73,7 @@ jobs: - job: Lint displayName: Lint pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 steps: - task: GoTool@0 inputs: @@ -92,7 +92,7 @@ jobs: - job: TestNetworkBasic displayName: Test Network pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 strategy: matrix: Basic-Go: @@ -118,7 +118,7 @@ jobs: - job: TestNetworkLedger displayName: Test Network pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 strategy: matrix: Ledger-Go: @@ -136,7 +136,7 @@ jobs: - job: TestNetworkPrivate displayName: Test Network pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 strategy: matrix: Private-Go: @@ -151,7 +151,7 @@ jobs: - job: TestNetworkSBE displayName: Test Network pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 strategy: matrix: SBE-Typescript: @@ -169,7 +169,7 @@ jobs: - job: TestNetworkSecured displayName: Test Network pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 strategy: matrix: Secured-Go: @@ -184,7 +184,7 @@ jobs: - job: TestNetworkEvents displayName: Test Network pool: - vmImage: ubuntu-18.04 + vmImage: ubuntu-20.04 strategy: matrix: Events-Javascript: diff --git a/test-network/addOrg3/docker/docker-compose-ca-org3.yaml b/test-network/addOrg3/docker/docker-compose-ca-org3.yaml index 7c447fd5..74dbf4d6 100644 --- a/test-network/addOrg3/docker/docker-compose-ca-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-ca-org3.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2.4' +version: '3.7' networks: test: diff --git a/test-network/addOrg3/docker/docker-compose-couch-org3.yaml b/test-network/addOrg3/docker/docker-compose-couch-org3.yaml index e280ff0f..d10766f3 100644 --- a/test-network/addOrg3/docker/docker-compose-couch-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-couch-org3.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2.4' +version: '3.7' networks: test: diff --git a/test-network/addOrg3/docker/docker-compose-org3.yaml b/test-network/addOrg3/docker/docker-compose-org3.yaml index 5c394d38..a596ea56 100644 --- a/test-network/addOrg3/docker/docker-compose-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-org3.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2.4' +version: '3.7' volumes: peer0.org3.example.com: diff --git a/test-network/docker/docker-compose-ca.yaml b/test-network/docker/docker-compose-ca.yaml index 67f17a0c..f678755b 100644 --- a/test-network/docker/docker-compose-ca.yaml +++ b/test-network/docker/docker-compose-ca.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2.4' +version: '3.7' networks: test: diff --git a/test-network/docker/docker-compose-couch.yaml b/test-network/docker/docker-compose-couch.yaml index e1aab1d3..f67e3a1c 100644 --- a/test-network/docker/docker-compose-couch.yaml +++ b/test-network/docker/docker-compose-couch.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2.4' +version: '3.7' networks: test: diff --git a/test-network/docker/docker-compose-test-net.yaml b/test-network/docker/docker-compose-test-net.yaml index f156f030..ffafc662 100644 --- a/test-network/docker/docker-compose-test-net.yaml +++ b/test-network/docker/docker-compose-test-net.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # -version: '2.4' +version: '3.7' volumes: orderer.example.com: From b8edd6593fa50175d0f3e08e29d841597f21fa47 Mon Sep 17 00:00:00 2001 From: nao Date: Fri, 3 Sep 2021 16:09:51 +0900 Subject: [PATCH 15/15] Fix: logspec output (#470) If ORDERER_KAFKA_VERBOSE is true, the output of /logspec will contain the following kafka information even if it is raft orderer. {"spec":"orderer.consensus.kafka.sarama=debug:info"} This PR deletes environment valiable for kafka in docker-compose. Signed-off-by: Nao Nishijima --- test-network/docker/docker-compose-test-net.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-network/docker/docker-compose-test-net.yaml b/test-network/docker/docker-compose-test-net.yaml index ffafc662..86fc85bf 100644 --- a/test-network/docker/docker-compose-test-net.yaml +++ b/test-network/docker/docker-compose-test-net.yaml @@ -32,8 +32,6 @@ services: - ORDERER_GENERAL_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key - ORDERER_GENERAL_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt - ORDERER_GENERAL_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt] - - ORDERER_KAFKA_TOPIC_REPLICATIONFACTOR=1 - - ORDERER_KAFKA_VERBOSE=true - ORDERER_GENERAL_CLUSTER_CLIENTCERTIFICATE=/var/hyperledger/orderer/tls/server.crt - ORDERER_GENERAL_CLUSTER_CLIENTPRIVATEKEY=/var/hyperledger/orderer/tls/server.key - ORDERER_GENERAL_CLUSTER_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]