├── .dockerignore ├── .eslintrc.js ├── .gitignore ├── README-ko.md ├── README.md ├── blockchainNetwork ├── .babelrc ├── .dockerignore ├── .gitignore ├── Dockerfile ├── chaincode │ └── src │ │ └── bcfit │ │ ├── contract.go │ │ ├── member.go │ │ ├── product.go │ │ └── secret_map.go ├── index.js ├── package-lock.json ├── package.json └── set-up │ ├── CouchDBKeyValueStore.js │ ├── client.js │ ├── config.js │ ├── enroll.js │ ├── invoke.js │ ├── query.js │ ├── setup.js │ └── utils.js ├── blockchainNetworkWithCouchDB ├── .babelrc ├── .dockerignore ├── Dockerfile ├── chaincode │ └── src │ │ └── bcfit │ │ ├── contract.go │ │ ├── member.go │ │ ├── product.go │ │ └── secret_map.go ├── index.js ├── package-lock.json ├── package.json └── set-up │ ├── CouchDBKeyValueStore.js │ ├── client.js │ ├── config.js │ ├── enroll.js │ ├── invoke.js │ ├── query.js │ ├── setup.js │ └── utils.js ├── build.sh ├── ccfunctions.md ├── clean.sh ├── configtx.yaml ├── configtxgen ├── configuration ├── .dockerignore ├── .gitignore ├── config.js ├── package-lock.json └── package.json ├── crypto-config.yaml ├── cryptogen ├── docker-compose-couchdb.yaml ├── docker-compose.yaml ├── docker-images.sh ├── fitcoinCertificateAuthority ├── Dockerfile └── fabric-ca-server-config.yaml ├── fitcoinPeer └── Dockerfile ├── generate-certs.sh ├── generate-cfgtx.sh ├── images └── Pattern1-Build-a-network.png ├── orderer └── Dockerfile ├── shopCertificateAuthority ├── Dockerfile └── fabric-ca-server-config.yaml ├── shopPeer └── Dockerfile └── test ├── .babelrc ├── .gitignore ├── client.js ├── config.js ├── index.js ├── indexCouchDB.js ├── package-lock.json ├── package.json ├── testNetwork.js └── testNetworkCouchDB.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .vscode 4 | .idea 5 | .env 6 | .env.sample 7 | npm-debug.log 8 | .jshintignore 9 | .jshintrc 10 | .npmrc 11 | bin 12 | dist 13 | fitcoin-peer 14 | shop-peer 15 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": 2017, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "indent": [ 13 | "error", 14 | 2 15 | ], 16 | "linebreak-style": [ 17 | "error", 18 | "unix" 19 | ], 20 | "semi": [ 21 | "error", 22 | "always" 23 | ], 24 | "no-console" : "off", 25 | "guard-for-in": 0, 26 | "prefer-promise-reject-errors": 2, 27 | "no-invalid-this": 0, 28 | }, 29 | "globals": { 30 | "jasmine": true 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /bin 3 | /src 4 | /pkg 5 | 6 | /**/ca 7 | /**/tls 8 | /**/crypto 9 | /**/blockchainNetwork/certs/** 10 | /cli/peers/** 11 | /**/admincerts 12 | /**/cacerts 13 | /**/keystore 14 | /**/signcerts 15 | /**/tlscerts 16 | /**/*.pem 17 | /**/fitcoin-peer 18 | /**/shop-peer 19 | /**/channel.tx 20 | 21 | node_modules 22 | -------------------------------------------------------------------------------- /README-ko.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Fabric Node SDK를 사용하여 블록체인 네트워크 생성하고 배포하기 2 | 3 | *다른 언어로 보기: [English](README.md)* 4 | 5 | ## 블록체인 네트워크 설정하기 6 | 7 | 8 | 9 | 블록체인 애플리케이션 빌드하기 파트1에 오신 것을 환영합니다. 첫번째 코드 패턴은 블록체인을 백엔드로 사용하여 피트니스 활동을 기록하고 fitcoin을 통한 상품의 주문과 같은 트랜잭션을 관리하는 비교적 더 큰 규모의 애플리케이션입니다. 이 시리즈의 첫 단계에서는 Hyperledger Fabric Node SDK를 사용하여 Hyperledger 블록체인 네트워크를 만들고 배포하는데 중점을 둡니다. 이 코드 패턴에는 두 명의 참가자, 즉 구매자 그리고 판매자/상점 피어가 있습니다. 구매자는 애플리케이션을 다운로드한 후 자신의 단계를 블록체인에 등록하고, 판매자는 구매자가 구매를 하기에 충분한 fitcoin을 가지고 있는지 확인합니다. 10 | 11 | 12 | 13 | IBM Cloud의 평생 무료인 lite 계정이 있다면 이 애플리케이션을 로컬에서 실행하시거나 [IBM Blockchain Starter Plan](https://www.ibm.com/blogs/blockchain/2018/03/getting-started-on-the-ibm-blockchain-platform-starter-plan/)을 사용하실 수 있습니다. 14 | 15 | 16 | 17 | ## 구성 요소 18 | 19 | * Hyperledger Fabric 20 | 21 | * Docker 22 | 23 | * Node.js용 Hyperledger Fabric SDK 24 | 25 | 26 | 27 | 28 | 29 | ## 애플리케이션 흐름도 30 | 31 | ![Application Workflow](images/Pattern1-Build-a-network.png) 32 | 33 | 34 | 35 | ## 사전에 필요한 지식 36 | 37 | * [Docker](https://www.docker.com/products/overview) - v1.13 or higher 38 | 39 | * [Docker Compose](https://docs.docker.com/compose/overview/) - v1.8 or higher 40 | 41 | 42 | 43 | ## 순서 44 | 45 | 1. [Build.sh를 실행하여 네트워크 빌드](#1-buildsh-스크립트-실행) 46 | 47 | 2. [네트워크 시작](#2-네트워크-시작) 48 | 49 | 3. [로그에서 결과 확인](#3-로그-확인) 50 | 51 | 4. [블록체인 네트워크 확인](#4-블록체인-네트워크-확인) 52 | 53 | 54 | 55 | ## 1. Build.sh 스크립트 실행 56 | 57 | 다음 순서를 수행합니다: 58 | 59 | 60 | 61 | a. 기존에 존재하는 블록체인 도커 이미지를 삭제하여 시스템을 초기화한다. 62 | 63 | 64 | 65 | b. 인증서를 발급한다. 66 | 67 | 68 | 69 | * `crypto-config.yaml`(암호 설정파일)은 소속 기관이나 부서 등 Peers와 Orderers간 서로의 신분의 내용을 정의합니다. 70 | 71 | 72 | 73 | c. Peers, Orderers 그리고 Channel을 생성한다. 74 | 75 | 76 | 77 | * `configtx.yaml` 파일은 블록체인 네트워크 또는 채널을 초기화하고, Orderer에게 체인의 가장 첫번째인 제네시스 블럭을 제공합니다. (Shop 또는 Fincoin 피어일 경우 멤버십 서비스는 각 채널 피어에 설치됨.) 78 | 79 | 80 | 81 | d. Orderer, Peer, Channel, Network 도커 이미지를 생성한다. 82 | 83 | 84 | 85 | ### 새로운 터미널을 열어 다음 명령어 실행: 86 | 87 | ```bash 88 | 89 | export FABRIC_CFG_PATH=$(pwd) 90 | 91 | chmod +x cryptogen 92 | 93 | chmod +x configtxgen 94 | 95 | chmod +x generate-certs.sh 96 | 97 | chmod +x generate-cfgtx.sh 98 | 99 | chmod +x docker-images.sh 100 | 101 | chmod +x build.sh 102 | 103 | chmod +x clean.sh 104 | 105 | ./build.sh 106 | 107 | ``` 108 | 109 | 110 | 111 | ## 2. 네트워크 시작 112 | 113 | 114 | 115 | 다음 테스트 실행 후 이 순서를 재진행하는 경우 'LOCALCONFIG' 환경변수가 정의되지 않도록 합니다. 116 | 117 | ```bash 118 | 119 | unset LOCALCONFIG 120 | 121 | ``` 122 | 123 | 124 | 125 | 피어노드에 체인코드를 설치하고 블록체인 네트워크를 시작하기 위한 두 가지 옵션이 있습니다. 다음 중 하나를 선택하세요. 126 | 127 | * LevelDB 사용하여 블록체인 상태를 저장하는 경우, 다음 명령어를 실행하여 네트워크를 시작합니다: 128 | 129 | ```bash 130 | 131 | docker-compose -p "fitcoin" -f "docker-compose.yaml" up -d 132 | 133 | ``` 134 | 135 | * CouchDB 사용하여 블록체인 상태를 저장하는 경우, 다음 명령어를 실행하여 네트워크를 시작합니다: 136 | 137 | ```bash 138 | 139 | docker-compose -p "fitcoin" -f "docker-compose-couchdb.yaml" up -d 140 | 141 | ``` 142 | 143 | 144 | 145 | ## 3. 로그 확인 146 | 147 | 148 | 149 | 다음 스크립트를 실행하여 로그 결과를 확인합니다. 150 | 151 | 152 | 153 | **Command** 154 | 155 | ```bash 156 | 157 | docker logs blockchain-setup 158 | 159 | ``` 160 | 161 | **Output:** 162 | 163 | ```bash 164 | 165 | Register CA fitcoin-org 166 | 167 | CA registration complete FabricCAServices : {hostname: fitcoin-ca, port: 7054} 168 | 169 | Register CA shop-org 170 | 171 | CA registration complete FabricCAServices : {hostname: shop-ca, port: 7054} 172 | 173 | info: [EventHub.js]: _connect - options {"grpc.ssl_target_name_override":"shop-peer","grpc.default_authority":"shop-peer"} 174 | 175 | info: [EventHub.js]: _connect - options {"grpc.ssl_target_name_override":"fitcoin-peer","grpc.default_authority":"fitcoin-peer"} 176 | 177 | Default channel not found, attempting creation... 178 | 179 | Successfully created a new default channel. 180 | 181 | Joining peers to the default channel. 182 | 183 | Chaincode is not installed, attempting installation... 184 | 185 | Base container image present. 186 | 187 | info: [packager/Golang.js]: packaging GOLANG from bcfit 188 | 189 | info: [packager/Golang.js]: packaging GOLANG from bcfit 190 | 191 | Successfully installed chaincode on the default channel. 192 | 193 | Successfully instantiated chaincode on all peers. 194 | 195 | ``` 196 | 197 | 198 | 199 | 200 | 201 | ## 4. 블록체인 네트워크 확인 202 | 203 | 204 | 205 | 다음 명령을 실행하여 네트워크에서 `invoke`와 `query` 기능을 테스트하세요: 206 | 207 | ```bash 208 | 209 | cd configuration 210 | 211 | export LOCALCONFIG=true 212 | 213 | node config.js 214 | 215 | cd .. 216 | 217 | cd test/ 218 | 219 | npm install 220 | 221 | ``` 222 | 223 | 224 | 225 | LevelDB 사용 시, 다음 명령어를 실행합니다: 226 | 227 | ```bash 228 | 229 | node index.js 230 | 231 | ``` 232 | 233 | 234 | 235 | CouchDB 사용 시, 다음 명령어를 실행합니다: 236 | 237 | ```bash 238 | 239 | node indexCouchDB.js 240 | 241 | ``` 242 | 243 | 244 | 245 | 246 | 247 | ## 추가 자료 248 | 249 | * [Hyperledger Fabric Documentation](https://hyperledger-fabric.readthedocs.io/en/release-1.1/) 250 | 251 | * [Hyperledger Fabric code on GitHub](https://github.com/hyperledger/fabric) 252 | 253 | 254 | 255 | ## 라이센스 256 | 257 | [Apache 2.0](LICENSE) 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING: This repository is no longer maintained :warning: 2 | This repository will not be updated. The repository will be kept available in read-only mode. 3 | 4 | # Creating and Deploying a Blockchain Network using Hyperledger Fabric Node SDK 5 | 6 | *Read this in other languages: [한국어](README-ko.md)* 7 | 8 | ## Instructions for setting the blockchainNetwork 9 | 10 | Welcome to Part 1 of building a Blockchain Application. This first pattern is part of a larger application that uses blockchain as a back-end to record fitness activities and manage transactions such as handling acquisition of products via fitcoins. The first step in this series is focused on creating and deploy a Hyperledger Blockchain Network using the Hyperledger Fabric Node SDK. We have two participants, namely a buyer and seller/shop peers. The buyer is the one who downloads the application and subsequently registers his steps on the blockchain. The seller is the one who verifying that the buyer has the right number of fitcoins to make purchases. A developer who has a lite account can run this application locally or adapt it to the [IBM Blockchain Starter Plan](https://www.ibm.com/blogs/blockchain/2018/03/getting-started-on-the-ibm-blockchain-platform-starter-plan/). 11 | 12 | ## Included Components 13 | * Hyperledger Fabric 14 | * Docker 15 | * Hyperledger Fabric SDK for node.js 16 | 17 | 18 | ## Application Workflow Diagram 19 | ![Application Workflow](images/Pattern1-Build-a-network.png) 20 | 21 | ## Prerequisites 22 | * [Docker](https://www.docker.com/products) - v1.13 or higher 23 | * [Docker Compose](https://docs.docker.com/compose/overview/) - v1.8 or higher 24 | 25 | ## Steps 26 | 1. [Run Build.sh Script to build network](#1-run-the-build.sh-script) 27 | 2. [Start the Network](#2-start-the-network) 28 | 3. [Check the logs to see the results](#3-check-the-logs) 29 | 4. [Check the Blockchain Network](#4-check-the-blockchainnetwork) 30 | 31 | ## 1. Run the Build.sh Script 32 | This accomplishes the following: 33 | 34 | a. Clean up system by removing any existing blockchain docker images 35 | 36 | b. Generate certificates 37 | 38 | * The `crypto-config.yaml` (Crypto configuration file) defines the identity of "who is who". It tells peers and orderers what organization they belong to and what domain they belong to. 39 | 40 | c. Create Peers, Orderers and Channel 41 | 42 | * The `configtx.yaml` file initializes a blockchain network or channel and services with an Orderer Genesis Block which serves as the first block on a chain. Additionally, membership services are installed on each channel peer (in this case, the Shop and Fitcoin Peers). 43 | 44 | d. Build docker images of the orderer, peers, channel, network 45 | 46 | ### Open a new terminal and run the following command: 47 | ```bash 48 | export FABRIC_CFG_PATH=$(pwd) 49 | chmod +x cryptogen 50 | chmod +x configtxgen 51 | chmod +x generate-certs.sh 52 | chmod +x generate-cfgtx.sh 53 | chmod +x docker-images.sh 54 | chmod +x build.sh 55 | chmod +x clean.sh 56 | ./build.sh 57 | ``` 58 | 59 | ## 2. Start the Network 60 | 61 | Make sure the 'LOCALCONFIG' environment variable is unset if you are re-running this step after running the test below 62 | ```bash 63 | unset LOCALCONFIG 64 | ``` 65 | 66 | There 2 options to install chaincode on the peer nodes and start the Blockchain network. You can select any one of the following: 67 | * Using LevelDB to store the blockchain state database. Run the following command to start the network: 68 | ```bash 69 | docker-compose -p "fitcoin" -f "docker-compose.yaml" up -d 70 | ``` 71 | * Using CouchDB to store the blockchain state database. Run the following command to start the network: 72 | ```bash 73 | docker-compose -p "fitcoin" -f "docker-compose-couchdb.yaml" up -d 74 | ``` 75 | 76 | ## 3. Check the logs 77 | 78 | You will see the results of running the script 79 | 80 | **Command** 81 | ```bash 82 | docker logs blockchain-setup 83 | ``` 84 | **Output:** 85 | ```bash 86 | Register CA fitcoin-org 87 | CA registration complete FabricCAServices : {hostname: fitcoin-ca, port: 7054} 88 | Register CA shop-org 89 | CA registration complete FabricCAServices : {hostname: shop-ca, port: 7054} 90 | info: [EventHub.js]: _connect - options {"grpc.ssl_target_name_override":"shop-peer","grpc.default_authority":"shop-peer"} 91 | info: [EventHub.js]: _connect - options {"grpc.ssl_target_name_override":"fitcoin-peer","grpc.default_authority":"fitcoin-peer"} 92 | Default channel not found, attempting creation... 93 | Successfully created a new default channel. 94 | Joining peers to the default channel. 95 | Chaincode is not installed, attempting installation... 96 | Base container image present. 97 | info: [packager/Golang.js]: packaging GOLANG from bcfit 98 | info: [packager/Golang.js]: packaging GOLANG from bcfit 99 | Successfully installed chaincode on the default channel. 100 | Successfully instantiated chaincode on all peers. 101 | ``` 102 | 103 | 104 | ## 4. Check the BlockchainNetwork 105 | 106 | Execute the following commands to to test the network by performing the `invoke` and `query` operations on the network: 107 | ```bash 108 | cd configuration 109 | export LOCALCONFIG=true 110 | node config.js 111 | cd .. 112 | cd test/ 113 | npm install 114 | ``` 115 | 116 | If you are using LevelDB, then run the following command: 117 | ```bash 118 | node index.js 119 | ``` 120 | 121 | If you are using CouchDB, then run the following command: 122 | ```bash 123 | node indexCouchDB.js 124 | ``` 125 | 126 | 127 | ## Additional Resources 128 | * [Hyperledger Fabric Documentation](https://hyperledger-fabric.readthedocs.io/en/release-1.1/) 129 | * [Hyperledger Fabric code on GitHub](https://github.com/hyperledger/fabric) 130 | 131 | ## License 132 | This code pattern is licensed under the Apache Software License, Version 2. Separate third party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the [Developer Certificate of Origin, Version 1.1 (DCO)](https://developercertificate.org/) and the [Apache Software License, Version 2](https://www.apache.org/licenses/LICENSE-2.0.txt). 133 | 134 | [Apache Software License (ASL) FAQ](https://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) 135 | -------------------------------------------------------------------------------- /blockchainNetwork/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "6.11.3" 8 | } 9 | } 10 | ] 11 | ], 12 | "sourceMaps": "inline" 13 | } -------------------------------------------------------------------------------- /blockchainNetwork/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .vscode 4 | .idea 5 | .env 6 | .env.sample 7 | npm-debug.log 8 | .jshintignore 9 | .jshintrc 10 | .npmrc 11 | bin 12 | dist 13 | fitcoin-peer 14 | shop-peer 15 | -------------------------------------------------------------------------------- /blockchainNetwork/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /bin 3 | /src 4 | /pkg 5 | 6 | /**/ca 7 | /**/tls 8 | /**/crypto 9 | /**/blockchainNetwork/certs/** 10 | /cli/peers/** 11 | /**/admincerts 12 | /**/cacerts 13 | /**/keystore 14 | /**/signcerts 15 | /**/tlscerts 16 | /**/*.pem 17 | /**/fitcoin-peer 18 | /**/shop-peer 19 | /**/channel.tx 20 | -------------------------------------------------------------------------------- /blockchainNetwork/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/node:6.11.3 2 | 3 | ENV NODE_ENV production 4 | ENV PORT 3000 5 | ENV DOCKER_SOCKET_PATH /host/var/run/docker.sock 6 | ENV DOCKER_CCENV_IMAGE=hyperledger/fabric-ccenv:x86_64-1.1.0 7 | 8 | RUN mkdir /app 9 | COPY . /app 10 | WORKDIR /app 11 | RUN npm install 12 | 13 | EXPOSE 3000 14 | 15 | CMD ["node", "index.js"] 16 | -------------------------------------------------------------------------------- /blockchainNetwork/chaincode/src/bcfit/contract.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | "math/rand" 26 | "strconv" 27 | "strings" 28 | "time" 29 | 30 | "github.com/hyperledger/fabric/core/chaincode/shim" 31 | pb "github.com/hyperledger/fabric/protos/peer" 32 | ) 33 | 34 | // ============================================================================================================================ 35 | // Make Purchase - creates purchase Contract 36 | // Inputs - userID, sellerID, productID, quantity 37 | // ============================================================================================================================ 38 | func (t *SimpleChaincode) makePurchase(stub shim.ChaincodeStubInterface, args []string) pb.Response { 39 | if len(args) != 4 { 40 | return shim.Error("Incorrect number of arguments") 41 | } 42 | var err error 43 | 44 | //creates contract struct with properties, and get sellerID, userID, productID, quantity from args 45 | var contract Contract 46 | contract.Id = "c" + randomInts(6) 47 | contract.UserId = args[0] 48 | contract.SellerId = args[1] 49 | contract.ProductId = args[2] 50 | quantity, err := strconv.Atoi(args[3]) 51 | if err != nil { 52 | return shim.Error("4th argument 'quantity' must be a numeric string") 53 | } 54 | contract.Quantity = quantity 55 | 56 | //get seller 57 | sellerAsBytes, err := stub.GetState(contract.SellerId) 58 | if err != nil { 59 | return shim.Error("Failed to get seller") 60 | } 61 | var seller Seller 62 | json.Unmarshal(sellerAsBytes, &seller) 63 | if seller.Type != TYPE_SELLER { 64 | return shim.Error("Not seller type") 65 | } 66 | 67 | //find the product 68 | var product Product 69 | productFound := false 70 | for h := 0; h < len(seller.Products); h++ { 71 | if seller.Products[h].Id == contract.ProductId { 72 | productFound = true 73 | product = seller.Products[h] 74 | break 75 | } 76 | } 77 | 78 | //if product not found return error 79 | if productFound != true { 80 | return shim.Error("Product not found") 81 | } 82 | 83 | //calculates cost and assigns to contract 84 | contract.Cost = product.Price * contract.Quantity 85 | //gets product name 86 | contract.ProductName = product.Name 87 | //assign 'Pending' state 88 | contract.State = STATE_PENDING 89 | 90 | // get user's current state 91 | var user User 92 | userAsBytes, err := stub.GetState(contract.UserId) 93 | if err != nil { 94 | return shim.Error("Failed to get user") 95 | } 96 | json.Unmarshal(userAsBytes, &user) 97 | if user.Type != TYPE_USER { 98 | return shim.Error("Not user type") 99 | } 100 | 101 | //check if user has enough Fitcoinsbalance 102 | if user.FitcoinsBalance < contract.Cost { 103 | return shim.Error("Insufficient funds") 104 | } 105 | 106 | //store contract 107 | contractAsBytes, _ := json.Marshal(contract) 108 | err = stub.PutState(contract.Id, contractAsBytes) 109 | if err != nil { 110 | return shim.Error(err.Error()) 111 | } 112 | 113 | //append contractId 114 | user.ContractIds = append(user.ContractIds, contract.Id) 115 | 116 | //update user's state 117 | updatedUserAsBytes, _ := json.Marshal(user) 118 | err = stub.PutState(contract.UserId, updatedUserAsBytes) 119 | if err != nil { 120 | return shim.Error(err.Error()) 121 | } 122 | 123 | //return contract info 124 | fmt.Println("contractAsBytes") 125 | fmt.Println(contractAsBytes) 126 | return shim.Success(contractAsBytes) 127 | 128 | } 129 | 130 | // ============================================================================================================================ 131 | // Transact Purchase - update user account, update seller's account and product inventory, update contract state 132 | // Inputs - memberId, contractID, newState(complete or declined) 133 | // ============================================================================================================================ 134 | func (t *SimpleChaincode) transactPurchase(stub shim.ChaincodeStubInterface, args []string) pb.Response { 135 | if len(args) != 3 { 136 | return shim.Error("Incorrect number of arguments") 137 | } 138 | //get contractID args 139 | memberId := args[0] 140 | contractId := args[1] 141 | newState := args[2] 142 | 143 | // Get contract from the ledger 144 | contractAsBytes, err := stub.GetState(contractId) 145 | if err != nil { 146 | return shim.Error("Failed to get contract") 147 | } 148 | var contract Contract 149 | json.Unmarshal(contractAsBytes, &contract) 150 | 151 | //ensure call is called by authorized user 152 | if memberId != contract.SellerId && memberId != contract.UserId { 153 | return shim.Error("Member not authorized to update contract") 154 | } 155 | 156 | //if current contract state is pending, then execute transaction 157 | if contract.State == STATE_PENDING { 158 | if newState == STATE_COMPLETE && memberId == contract.SellerId { 159 | //get seller 160 | var member Seller 161 | memberAsBytes, err := stub.GetState(memberId) 162 | if err != nil { 163 | return shim.Error("Failed to get member") 164 | } 165 | json.Unmarshal(memberAsBytes, &member) 166 | 167 | //get contract user's current state 168 | var contractUser User 169 | contractUserAsBytes, err := stub.GetState(contract.UserId) 170 | if err != nil { 171 | return shim.Error("Failed to get contract owner") 172 | } 173 | json.Unmarshal(contractUserAsBytes, &contractUser) 174 | 175 | //update user's FitcoinsBalance 176 | if (contractUser.FitcoinsBalance - contract.Cost) >= 0 { 177 | contractUser.FitcoinsBalance = contractUser.FitcoinsBalance - contract.Cost 178 | } else { 179 | return shim.Error("Insufficient fitcoins") 180 | } 181 | 182 | //update seller's product count 183 | productFound := false 184 | for h := 0; h < len(member.Products); h++ { 185 | if member.Products[h].Id == contract.ProductId { 186 | productFound = true 187 | if member.Products[h].Count >= contract.Quantity { 188 | member.Products[h].Count = member.Products[h].Count - contract.Quantity 189 | } 190 | break 191 | } 192 | } 193 | //if product not found return error 194 | if productFound == true { 195 | //update seller's FitcoinsBalance 196 | member.FitcoinsBalance = member.FitcoinsBalance + contract.Cost 197 | //update user state 198 | updatedUserAsBytes, _ := json.Marshal(contractUser) 199 | err = stub.PutState(contract.UserId, updatedUserAsBytes) 200 | if err != nil { 201 | return shim.Error(err.Error()) 202 | } 203 | //update seller state 204 | updatedSellerAsBytes, _ := json.Marshal(member) 205 | err = stub.PutState(contract.SellerId, updatedSellerAsBytes) 206 | if err != nil { 207 | return shim.Error(err.Error()) 208 | } 209 | contract.State = STATE_COMPLETE 210 | 211 | } else { 212 | contract.State = STATE_DECLINED 213 | declinedContractAsBytes, _ := json.Marshal(contract) 214 | err = stub.PutState(contract.Id, declinedContractAsBytes) 215 | if err != nil { 216 | return shim.Error(err.Error()) 217 | } 218 | return shim.Error("Product not available for sale. Cancelling contract.") 219 | } 220 | } else if newState == STATE_DECLINED { 221 | contract.State = STATE_DECLINED 222 | } else { 223 | return shim.Error("Invalid new state") 224 | } 225 | 226 | // update contract state on ledger 227 | updatedContractAsBytes, _ := json.Marshal(contract) 228 | err = stub.PutState(contract.Id, updatedContractAsBytes) 229 | if err != nil { 230 | return shim.Error(err.Error()) 231 | } 232 | //return contract info 233 | return shim.Success(updatedContractAsBytes) 234 | } else { 235 | return shim.Error("Contract already Complete or Declined") 236 | } 237 | } 238 | 239 | // ============================================================================================================================ 240 | // Get all user contracts 241 | // Inputs - userID 242 | // ============================================================================================================================ 243 | func (t *SimpleChaincode) getAllUserContracts(stub shim.ChaincodeStubInterface, args []string) pb.Response { 244 | if len(args) != 1 { 245 | return shim.Error("Incorrect number of arguments") 246 | } 247 | var err error 248 | 249 | //get userID from args 250 | user_id := args[0] 251 | 252 | //get user 253 | userAsBytes, err := stub.GetState(user_id) 254 | if err != nil { 255 | return shim.Error("Failed to get user") 256 | } 257 | var user User 258 | json.Unmarshal(userAsBytes, &user) 259 | if user.Type != TYPE_USER { 260 | return shim.Error("Not user type") 261 | } 262 | 263 | //get user contracts 264 | var contracts []Contract 265 | for h := 0; h < len(user.ContractIds); h++ { 266 | //get contract from the ledger 267 | contractAsBytes, err := stub.GetState(user.ContractIds[h]) 268 | if err != nil { 269 | return shim.Error("Failed to get contract") 270 | } 271 | var contract Contract 272 | json.Unmarshal(contractAsBytes, &contract) 273 | contracts = append(contracts, contract) 274 | } 275 | //change to array of bytes 276 | contractsAsBytes, _ := json.Marshal(contracts) 277 | return shim.Success(contractsAsBytes) 278 | 279 | } 280 | 281 | // ============================================================================================================================ 282 | // Get all contracts 283 | // Inputs - (none) 284 | // ============================================================================================================================ 285 | func (t *SimpleChaincode) getAllContracts(stub shim.ChaincodeStubInterface, args []string) pb.Response { 286 | var err error 287 | var contracts []Contract 288 | 289 | // ---- Get All Contracts ---- // 290 | resultsIterator, err := stub.GetStateByRange("c0", "c9999999999999999999") 291 | if err != nil { 292 | return shim.Error(err.Error()) 293 | } 294 | defer resultsIterator.Close() 295 | 296 | for resultsIterator.HasNext() { 297 | aKeyValue, err := resultsIterator.Next() 298 | if err != nil { 299 | return shim.Error(err.Error()) 300 | } 301 | queryKeyAsStr := aKeyValue.Key 302 | queryValAsBytes := aKeyValue.Value 303 | fmt.Println("on contract id - ", queryKeyAsStr) 304 | var contract Contract 305 | json.Unmarshal(queryValAsBytes, &contract) 306 | contracts = append(contracts, contract) 307 | } 308 | 309 | //change to array of bytes 310 | contractsAsBytes, _ := json.Marshal(contracts) 311 | return shim.Success(contractsAsBytes) 312 | 313 | } 314 | 315 | //generate an array of random ints 316 | func randomArray(len int) []int { 317 | a := make([]int, len) 318 | for i := 0; i <= len-1; i++ { 319 | a[i] = rand.Intn(10) 320 | } 321 | return a 322 | } 323 | 324 | // Generate a random string of ints with length len 325 | func randomInts(len int) string { 326 | rand.Seed(time.Now().UnixNano()) 327 | intArray := randomArray(len) 328 | var stringInt []string 329 | for _, i := range intArray { 330 | stringInt = append(stringInt, strconv.Itoa(i)) 331 | } 332 | return strings.Join(stringInt, "") 333 | } 334 | -------------------------------------------------------------------------------- /blockchainNetwork/chaincode/src/bcfit/member.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "strconv" 25 | "strings" 26 | 27 | "github.com/hyperledger/fabric/core/chaincode/shim" 28 | pb "github.com/hyperledger/fabric/protos/peer" 29 | ) 30 | 31 | // ============================================================================================================================ 32 | // Create member 33 | // Inputs - id, type(user or seller) 34 | // ============================================================================================================================ 35 | func (t *SimpleChaincode) createMember(stub shim.ChaincodeStubInterface, args []string) pb.Response { 36 | var err error 37 | if len(args) != 2 { 38 | return shim.Error("Incorrect number of arguments") 39 | } 40 | 41 | //get id and type from args 42 | member_id := args[0] 43 | member_type := strings.ToLower(args[1]) 44 | 45 | //check if type is 'user' 46 | if member_type == TYPE_USER { 47 | 48 | //create user 49 | var user User 50 | user.Id = member_id 51 | user.Type = TYPE_USER 52 | user.FitcoinsBalance = 0 53 | user.StepsUsedForConversion = 0 54 | user.TotalSteps = 0 55 | 56 | //store user 57 | userAsBytes, _ := json.Marshal(user) 58 | err = stub.PutState(user.Id, userAsBytes) 59 | if err != nil { 60 | return shim.Error(err.Error()) 61 | } 62 | 63 | //return user info 64 | return shim.Success(userAsBytes) 65 | 66 | } else if member_type == TYPE_SELLER { 67 | //check if type is 'seller' 68 | 69 | //create seller 70 | var seller Seller 71 | seller.Id = member_id 72 | seller.Type = TYPE_SELLER 73 | seller.FitcoinsBalance = 0 74 | 75 | // store seller 76 | sellerAsBytes, _ := json.Marshal(seller) 77 | err = stub.PutState(seller.Id, sellerAsBytes) 78 | if err != nil { 79 | return shim.Error(err.Error()) 80 | } 81 | 82 | //get and update sellerIDs 83 | sellerIdsBytes, err := stub.GetState("sellerIds") 84 | if err != nil { 85 | return shim.Error("Unable to get users.") 86 | } 87 | var sellerIds []string 88 | // add sellerID to update sellers 89 | json.Unmarshal(sellerIdsBytes, &sellerIds) 90 | sellerIds = append(sellerIds, seller.Id) 91 | updatedSellerIdsBytes, _ := json.Marshal(sellerIds) 92 | err = stub.PutState("sellerIds", updatedSellerIdsBytes) 93 | 94 | //return seller info 95 | return shim.Success(sellerAsBytes) 96 | 97 | } 98 | 99 | return shim.Success(nil) 100 | 101 | } 102 | 103 | // ============================================================================================================================ 104 | // Generate Fitcoins for the user 105 | // Inputs - userId, transactionSteps 106 | // ============================================================================================================================ 107 | func (t *SimpleChaincode) generateFitcoins(stub shim.ChaincodeStubInterface, args []string) pb.Response { 108 | if len(args) != 2 { 109 | return shim.Error("Incorrect number of arguments") 110 | } 111 | var err error 112 | 113 | //get user_id and newSteps from args 114 | user_id := args[0] 115 | newTransactionSteps, err := strconv.Atoi(args[1]) 116 | if err != nil { 117 | return shim.Error(err.Error()) 118 | } 119 | 120 | //get user 121 | var user User 122 | userAsBytes, err := stub.GetState(user_id) 123 | if err != nil { 124 | return shim.Error("Failed to get user") 125 | } 126 | json.Unmarshal(userAsBytes, &user) 127 | if user.Type != TYPE_USER { 128 | return shim.Error("Not user type") 129 | } 130 | 131 | //update user account 132 | var newSteps = newTransactionSteps - user.StepsUsedForConversion 133 | if newSteps > STEPS_TO_FITCOIN { 134 | var newFitcoins = newSteps / STEPS_TO_FITCOIN 135 | var remainderSteps = newSteps % STEPS_TO_FITCOIN 136 | user.FitcoinsBalance = user.FitcoinsBalance + newFitcoins 137 | user.StepsUsedForConversion = newTransactionSteps - remainderSteps 138 | user.TotalSteps = newTransactionSteps 139 | 140 | //update users state 141 | updatedUserAsBytes, _ := json.Marshal(user) 142 | err = stub.PutState(user_id, updatedUserAsBytes) 143 | if err != nil { 144 | return shim.Error(err.Error()) 145 | } 146 | 147 | //return user info 148 | return shim.Success(updatedUserAsBytes) 149 | } 150 | 151 | return shim.Success(userAsBytes) 152 | 153 | } 154 | -------------------------------------------------------------------------------- /blockchainNetwork/chaincode/src/bcfit/product.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "strconv" 25 | 26 | "github.com/hyperledger/fabric/core/chaincode/shim" 27 | pb "github.com/hyperledger/fabric/protos/peer" 28 | ) 29 | 30 | // ============================================================================================================================ 31 | // Create product inventory for seller 32 | // Inputs - sellerId, productID, productName, productCount, productPrice 33 | // ============================================================================================================================ 34 | func (t *SimpleChaincode) createProduct(stub shim.ChaincodeStubInterface, args []string) pb.Response { 35 | 36 | return t.updateProduct(stub, args) 37 | } 38 | 39 | // ============================================================================================================================ 40 | // Update product inventory for seller 41 | // Inputs - sellerId, productID, newProductName, newProductCount, newProductPrice 42 | // ============================================================================================================================ 43 | func (t *SimpleChaincode) updateProduct(stub shim.ChaincodeStubInterface, args []string) pb.Response { 44 | if len(args) != 5 { 45 | return shim.Error("Incorrect number of arguments") 46 | } 47 | var err error 48 | 49 | //get sellerID from args 50 | seller_id := args[0] 51 | //get productID from args 52 | product_id := args[1] 53 | 54 | //get new product properties from args 55 | newProductName := args[2] 56 | newProductCount, err := strconv.Atoi(args[3]) 57 | if err != nil { 58 | return shim.Error("3rd argument 'productCount' must be a numeric string") 59 | } 60 | newProductPrice, err := strconv.Atoi(args[4]) 61 | if err != nil { 62 | return shim.Error("4th argument 'productPrice' must be a numeric string") 63 | } 64 | 65 | //get seller 66 | sellerAsBytes, err := stub.GetState(seller_id) 67 | if err != nil { 68 | return shim.Error("Failed to get seller") 69 | } 70 | var seller Seller 71 | json.Unmarshal(sellerAsBytes, &seller) 72 | if seller.Type != TYPE_SELLER { 73 | return shim.Error("Not seller type") 74 | } 75 | 76 | //find the product and update the properties 77 | productFound := false 78 | for h := 0; h < len(seller.Products); h++ { 79 | if seller.Products[h].Id == product_id { 80 | productFound = true 81 | seller.Products[h].Name = newProductName 82 | seller.Products[h].Count = newProductCount 83 | seller.Products[h].Price = newProductPrice 84 | break 85 | } 86 | } 87 | //if product not found return error 88 | if productFound != true { 89 | var product Product 90 | product.Id = product_id 91 | product.Name = newProductName 92 | product.Count = newProductCount 93 | product.Price = newProductPrice 94 | //append product 95 | seller.Products = append(seller.Products, product) 96 | } 97 | 98 | //update seller's state 99 | updatedSellerAsBytes, _ := json.Marshal(seller) 100 | err = stub.PutState(seller_id, updatedSellerAsBytes) 101 | if err != nil { 102 | return shim.Error(err.Error()) 103 | } 104 | 105 | //return seller info 106 | return shim.Success(updatedSellerAsBytes) 107 | 108 | } 109 | 110 | // ============================================================================================================================ 111 | // Get product inventory for seller 112 | // Inputs - sellerId, productID 113 | // ============================================================================================================================ 114 | func (t *SimpleChaincode) getProductByID(stub shim.ChaincodeStubInterface, args []string) pb.Response { 115 | if len(args) != 2 { 116 | return shim.Error("Incorrect number of arguments") 117 | } 118 | var err error 119 | 120 | //get sellerID, productID from args 121 | seller_id := args[0] 122 | product_id := args[1] 123 | 124 | //get seller 125 | sellerAsBytes, err := stub.GetState(seller_id) 126 | if err != nil { 127 | return shim.Error("Failed to get seller") 128 | } 129 | var seller Seller 130 | json.Unmarshal(sellerAsBytes, &seller) 131 | if seller.Type != TYPE_SELLER { 132 | return shim.Error("Not seller type") 133 | } 134 | 135 | //find the product 136 | var product Product 137 | productFound := false 138 | for h := 0; h < len(seller.Products); h++ { 139 | if seller.Products[h].Id == product_id { 140 | productFound = true 141 | product = seller.Products[h] 142 | break 143 | } 144 | } 145 | 146 | //if product not found return error 147 | if productFound != true { 148 | return shim.Error("Product not found") 149 | } 150 | 151 | //return product type 152 | productAsBytes, _ := json.Marshal(product) 153 | return shim.Success(productAsBytes) 154 | 155 | } 156 | 157 | // ============================================================================================================================ 158 | // Get all products for sale 159 | // Inputs - (none) 160 | // ============================================================================================================================ 161 | func (t *SimpleChaincode) getProductsForSale(stub shim.ChaincodeStubInterface, args []string) pb.Response { 162 | var err error 163 | 164 | //get sellers array 165 | sellerIdsBytes, err := stub.GetState("sellerIds") 166 | if err != nil { 167 | return shim.Error("Unable to get sellers.") 168 | } 169 | var sellerIds []string 170 | json.Unmarshal(sellerIdsBytes, &sellerIds) 171 | 172 | // create return object array 173 | type ReturnProductSale struct { 174 | SellerID string `json:"sellerid"` 175 | ProductId string `json:"productid"` 176 | Name string `json:"name"` 177 | Count int `json:"count"` 178 | Price int `json:"price"` 179 | } 180 | var returnProducts []ReturnProductSale 181 | 182 | //go through all sellerIDs 183 | for g := 0; g < len(sellerIds); g++ { 184 | 185 | //get seller 186 | sellerAsBytes, err := stub.GetState(sellerIds[g]) 187 | if err != nil { 188 | return shim.Error("Failed to get seller") 189 | } 190 | var seller Seller 191 | json.Unmarshal(sellerAsBytes, &seller) 192 | 193 | for h := 0; h < len(seller.Products); h++ { 194 | if seller.Products[h].Count > 0 { 195 | var returnProduct ReturnProductSale 196 | returnProduct.SellerID = seller.Id 197 | returnProduct.ProductId = seller.Products[h].Id 198 | returnProduct.Name = seller.Products[h].Name 199 | returnProduct.Count = seller.Products[h].Count 200 | returnProduct.Price = seller.Products[h].Price 201 | //append to array 202 | returnProducts = append(returnProducts, returnProduct) 203 | } 204 | } 205 | } 206 | 207 | //return products for sole 208 | returnProductsBytes, _ := json.Marshal(returnProducts) 209 | return shim.Success(returnProductsBytes) 210 | } 211 | -------------------------------------------------------------------------------- /blockchainNetwork/chaincode/src/bcfit/secret_map.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | 26 | "github.com/hyperledger/fabric/core/chaincode/shim" 27 | pb "github.com/hyperledger/fabric/protos/peer" 28 | ) 29 | 30 | //steps to fitcoin constant 31 | const STEPS_TO_FITCOIN = 100 32 | 33 | //contract state 34 | const STATE_COMPLETE = "complete" 35 | const STATE_PENDING = "pending" 36 | const STATE_DECLINED = "declined" 37 | 38 | //member type 39 | const TYPE_USER = "user" 40 | const TYPE_SELLER = "seller" 41 | 42 | // SimpleChaincode example simple Chaincode implementation 43 | type SimpleChaincode struct { 44 | } 45 | 46 | // Member object for participants 47 | type Member struct { 48 | Id string `json:"id"` 49 | Type string `json:"memberType"` 50 | FitcoinsBalance int `json:"fitcoinsBalance"` 51 | } 52 | 53 | // User 54 | type User struct { 55 | Member 56 | TotalSteps int `json:"totalSteps"` 57 | StepsUsedForConversion int `json:"stepsUsedForConversion"` 58 | ContractIds []string `json:"contractIds"` 59 | } 60 | 61 | // Seller 62 | type Seller struct { 63 | Member 64 | Products []Product `json:"products"` 65 | } 66 | 67 | // Product 68 | type Product struct { 69 | Id string `json:"id"` 70 | Name string `json:"name"` 71 | Count int `json:"count"` 72 | Price int `json:"price"` 73 | } 74 | 75 | // Contract 76 | type Contract struct { 77 | Id string `json:"id"` 78 | SellerId string `json:"sellerId"` 79 | UserId string `json:"userId"` 80 | ProductId string `json:"productId"` 81 | ProductName string `json:"productName"` 82 | Quantity int `json:"quantity"` 83 | Cost int `json:"cost"` 84 | State string `json:"state"` 85 | } 86 | 87 | // ============================================================================================================================ 88 | // Main 89 | // ============================================================================================================================ 90 | func main() { 91 | err := shim.Start(new(SimpleChaincode)) 92 | if err != nil { 93 | fmt.Printf("Error starting chaincode: %s", err) 94 | } 95 | } 96 | 97 | // ============================================================================================================================ 98 | // Init - initialize the chaincode 99 | // ============================================================================================================================ 100 | func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { 101 | 102 | //store sellerIds 103 | var sellerIds []string 104 | sellerIdsBytes, err := json.Marshal(sellerIds) 105 | if err != nil { 106 | return shim.Error("Error initializing sellers.") 107 | } 108 | err = stub.PutState("sellerIds", sellerIdsBytes) 109 | 110 | return shim.Success(nil) 111 | } 112 | 113 | // ============================================================================================================================ 114 | // Invoke - Our entry point for Invocations 115 | // ============================================================================================================================ 116 | func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 117 | function, args := stub.GetFunctionAndParameters() 118 | fmt.Println(" ") 119 | fmt.Println("starting invoke, for - " + function) 120 | 121 | //call functions 122 | if function == "createMember" { 123 | return t.createMember(stub, args) 124 | } else if function == "generateFitcoins" { 125 | return t.generateFitcoins(stub, args) 126 | } else if function == "getState" { 127 | return t.getState(stub, args) 128 | } else if function == "createProduct" { 129 | return t.createProduct(stub, args) 130 | } else if function == "updateProduct" { 131 | return t.updateProduct(stub, args) 132 | } else if function == "getProductByID" { 133 | return t.getProductByID(stub, args) 134 | } else if function == "getProductsForSale" { 135 | return t.getProductsForSale(stub, args) 136 | } else if function == "makePurchase" { 137 | return t.makePurchase(stub, args) 138 | } else if function == "transactPurchase" { 139 | return t.transactPurchase(stub, args) 140 | } else if function == "getAllUserContracts" { 141 | return t.getAllUserContracts(stub, args) 142 | } else if function == "getAllContracts" { 143 | return t.getAllContracts(stub, args) 144 | } 145 | 146 | return shim.Error("Function with the name " + function + " does not exist.") 147 | } 148 | 149 | // ============================================================================================================================ 150 | // Get state with userId, sellerID, contractID 151 | // Inputs - id 152 | // ============================================================================================================================ 153 | func (t *SimpleChaincode) getState(stub shim.ChaincodeStubInterface, args []string) pb.Response { 154 | if len(args) != 1 { 155 | return shim.Error("Incorrect number of arguments") 156 | } 157 | 158 | //get id 159 | id := args[0] 160 | 161 | // Get the state from the ledger 162 | dataAsBytes, err := stub.GetState(id) 163 | if err != nil { 164 | return shim.Error("Failed to get state") 165 | } 166 | 167 | //return user info 168 | return shim.Success(dataAsBytes) 169 | } 170 | -------------------------------------------------------------------------------- /blockchainNetwork/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./set-up/setup.js'); -------------------------------------------------------------------------------- /blockchainNetwork/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blockchain", 3 | "version": "1.0.0", 4 | "description": "Setup Blockchain for fitcoin network", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "babel-core": "^6.26.0", 13 | "babel-polyfill": "^6.26.0", 14 | "babel-preset-env": "^1.6.1", 15 | "events": "^1.1.1", 16 | "fabric-ca-client": "^1.0.4", 17 | "fabric-client": "^1.0.4", 18 | "fs": "0.0.1-security", 19 | "fs-extra": "^5.0.0", 20 | "grpc": "^1.8.0", 21 | "http": "0.0.0", 22 | "json-style-converter": "^1.0.3", 23 | "long": "^3.2.0", 24 | "nano": "^6.4.2", 25 | "path": "^0.12.7", 26 | "url": "^0.11.0", 27 | "util": "^0.10.3" 28 | } 29 | } -------------------------------------------------------------------------------- /blockchainNetwork/set-up/CouchDBKeyValueStore.js: -------------------------------------------------------------------------------- 1 | /*eslint no-unused-vars: 0*/ 2 | /* 3 | Copyright 2016 IBM All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the 'License'); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an 'AS IS' BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 'use strict'; 18 | var api = require('fabric-client/lib/api.js'); 19 | //var fs = require('fs-extra'); 20 | //var path = require('path'); 21 | var util = require('util'); 22 | var utils = require('fabric-client/lib/utils.js'); 23 | var nano = require('nano'); 24 | var logger = utils.getLogger('CouchDBKeyValueStore.js'); 25 | /** 26 | * This is a sample database implementation of the [KeyValueStore]{@link module:api.KeyValueStore} API. 27 | * It uses a local or remote CouchDB database instance to store the keys. 28 | * 29 | * @class 30 | * @extends module:api.KeyValueStore 31 | */ 32 | var CouchDBKeyValueStore = class extends api.KeyValueStore { 33 | /** 34 | * @typedef {Object} CouchDBOpts 35 | * @property {string} url The CouchDB instance url, in the form of http(s)://:@host:port 36 | * @property {string} name Optional. Identifies the name of the database to use. Default: member_db. 37 | */ 38 | /** 39 | * constructor 40 | * 41 | * @param {CouchDBOpts} options Settings used to connect to a CouchDB instance 42 | */ 43 | constructor(options) { 44 | logger.debug('constructor', { 45 | options: options 46 | }); 47 | if(!options || !options.url) { 48 | throw new Error('Must provide the CouchDB database url to store membership data.'); 49 | } 50 | // Create the keyValStore instance 51 | super(); 52 | var self = this; 53 | // url is the database instance url 54 | this._url = options.url; 55 | // Name of the database, optional 56 | if(!options.name) { 57 | this._name = 'member_db'; 58 | } else { 59 | this._name = options.name; 60 | } 61 | return new Promise(function (resolve, reject) { 62 | // Initialize the CouchDB database client 63 | var dbClient = nano(self._url); 64 | // Check if the database already exists. If not, create it. 65 | dbClient.db.get(self._name, function (err, body) { 66 | // Check for error 67 | if(err) { 68 | // Database doesn't exist 69 | if(err.error == 'not_found') { 70 | logger.debug('No %s found, creating %s', self._name, self._name); 71 | dbClient.db.create(self._name, function (err, body) { 72 | if(err) { 73 | return reject(new Error(util.format('Failed to create %s database due to error: %s', self._name, err.stack ? err.stack : err))); 74 | } 75 | logger.debug('Created %s database', self._name); 76 | // Specify it as the database to use 77 | self._database = dbClient.use(self._name); 78 | resolve(self); 79 | }); 80 | } else { 81 | // Other error 82 | return reject(new Error(util.format('Error creating %s database to store membership data: %s', self._name, err.stack ? err.stack : err))); 83 | } 84 | } else { 85 | // Database exists 86 | logger.debug('%s already exists', self._name); 87 | // Specify it as the database to use 88 | self._database = dbClient.use(self._name); 89 | resolve(self); 90 | } 91 | }); 92 | }); 93 | } 94 | getValue(name) { 95 | logger.debug('getValue', { 96 | key: name 97 | }); 98 | var self = this; 99 | return new Promise(function (resolve, reject) { 100 | self._database.get(name, function (err, body) { 101 | // Check for error on retrieving from database 102 | if(err) { 103 | if(err.error !== 'not_found') { 104 | logger.error('getValue: %s, ERROR: [%s.get] - ', name, self._name, err.error); 105 | return reject(err.error); 106 | } else { 107 | logger.debug('getValue: %s, Entry does not exist', name); 108 | return resolve(null); 109 | } 110 | } else { 111 | logger.debug('getValue: %s, Retrieved message from %s.', name, self._name); 112 | return resolve(body.member); 113 | } 114 | }); 115 | }); 116 | } 117 | setValue(name, value) { 118 | logger.debug('setValue', { 119 | key: name 120 | }); 121 | var self = this; 122 | return new Promise(function (resolve, reject) { 123 | // Attempt to retrieve from the database to see if the entry exists 124 | self._database.get(name, function (err, body) { 125 | // Check for error on retrieving from database 126 | if(err) { 127 | if(err.error !== 'not_found') { 128 | logger.error('setValue: %s, ERROR: [%s.get] - ', name, self._name, err.error); 129 | reject(err.error); 130 | } else { 131 | // Entry does not exist 132 | logger.debug('setValue: %s, Entry does not exist, insert it.', name); 133 | self._dbInsert({ 134 | _id: name, 135 | member: value 136 | }).then(function (status) { 137 | logger.debug('setValue add: ' + name + ', status: ' + status); 138 | resolve(value); 139 | //if(status == true) resolve(value); 140 | //else reject(new Error('Couch database insert add failed.')); 141 | }).catch(err => { 142 | reject(new Error('Couch database insert update failed - ' + err.message)); 143 | }); 144 | } 145 | } else { 146 | // Entry already exists and must be updated 147 | // Update the database entry using the latest rev number 148 | logger.debug('setValue: %s, Retrieved entry from %s. Latest rev number: %s', name, self._name, body._rev); 149 | self._dbInsert({ 150 | _id: name, 151 | _rev: body._rev, 152 | member: value 153 | }).then(function (status) { 154 | logger.debug('setValue update: ' + name + ', status: ' + status); 155 | resolve(value); 156 | //if(status == true) resolve(value); 157 | //else reject(new Error('Couch database insert add failed.')); 158 | }).catch(err => { 159 | reject(new Error('Couch database insert update failed - ' + err.message)); 160 | }); 161 | } 162 | }); 163 | }); 164 | } 165 | _dbInsert(options) { 166 | logger.debug('setValue, _dbInsert', { 167 | options: options 168 | }); 169 | var self = this; 170 | return new Promise(function (resolve, reject) { 171 | self._database.insert(options, function (err, body, header) { 172 | if(err) { 173 | logger.error('setValue, _dbInsert, ERROR: [%s.insert] - ', self._name, err.error); 174 | reject(new Error(err.error)); 175 | } else { 176 | logger.debug('setValue, _dbInsert, Inserted member into %s.', self._name); 177 | resolve(true); 178 | } 179 | }); 180 | }); 181 | } 182 | }; 183 | module.exports = CouchDBKeyValueStore; -------------------------------------------------------------------------------- /blockchainNetwork/set-up/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Long = require('long'); 3 | var resolve = require('path').resolve; 4 | var EventEmitter = require('events').EventEmitter; 5 | var hfc = require('fabric-client'); 6 | var CAClient = require('fabric-ca-client'); 7 | var CouchDBKeyValueStore = require('./CouchDBKeyValueStore.js'); 8 | var CKS = require('fabric-client/lib/impl/CryptoKeyStore.js'); 9 | import enrollUser from './enroll'; 10 | var utils = require('./utils.js'); 11 | process.env.GOPATH = resolve(__dirname, '../chaincode'); 12 | const JOIN_TIMEOUT = 120000, 13 | TRANSACTION_TIMEOUT = 120000; 14 | export class OrganizationClient extends EventEmitter { 15 | constructor(channelName, ordererConfig, peerConfig, caConfig, admin) { 16 | super(); 17 | this._channelName = channelName; 18 | this._ordererConfig = ordererConfig; 19 | this._peerConfig = peerConfig; 20 | this._caConfig = caConfig; 21 | this._admin = admin; 22 | this._peers = []; 23 | this._eventHubs = []; 24 | this._client = new hfc(); 25 | // Setup channel 26 | this._channel = this._client.newChannel(channelName); 27 | // Setup orderer and peers 28 | const orderer = this._client.newOrderer(ordererConfig.url, { 29 | pem: ordererConfig.pem, 30 | 'ssl-target-name-override': ordererConfig.hostname 31 | }); 32 | this._channel.addOrderer(orderer); 33 | const defaultPeer = this._client.newPeer(peerConfig.url, { 34 | pem: peerConfig.pem, 35 | 'ssl-target-name-override': peerConfig.hostname 36 | }); 37 | this._peers.push(defaultPeer); 38 | this._channel.addPeer(defaultPeer); 39 | this._adminUser = null; 40 | this._ca = null; 41 | } 42 | async login() { 43 | try { 44 | this._client.setStateStore(await hfc.newDefaultKeyValueStore({ 45 | path: `./${this._peerConfig.hostname}` 46 | })); 47 | this._ca = await new CAClient(this._caConfig.url, { 48 | verify: false 49 | }); 50 | console.log("CA registration complete "); 51 | this._adminUser = await enrollUser(this._client, "admin", "adminpw", this._ca, { 52 | mspId: this._caConfig.mspId, 53 | adminUser: null, 54 | affiliationOrg: this._peerConfig.org, 55 | noOfAttempts: 5 56 | }); 57 | } catch(e) { 58 | console.log(`Failed to enroll user. Error: ${e.message}`); 59 | throw e; 60 | } 61 | } 62 | initEventHubs() { 63 | // Setup event hubs 64 | try { 65 | const defaultEventHub = this._client.newEventHub(); 66 | defaultEventHub.setPeerAddr(this._peerConfig.eventHubUrl, { 67 | pem: this._peerConfig.pem, 68 | 'ssl-target-name-override': this._peerConfig.hostname 69 | }); 70 | defaultEventHub.connect(); 71 | defaultEventHub.registerBlockEvent(block => { 72 | this.emit('block', utils.unmarshalBlock(block)); 73 | }); 74 | this._eventHubs.push(defaultEventHub); 75 | } catch(e) { 76 | console.log(`Failed to configure event hubs. Error ${e.message}`); 77 | throw e; 78 | } 79 | } 80 | async createOrgAdmin() { 81 | return this._client.createUser({ 82 | username: `Admin@${this._peerConfig.hostname}`, 83 | mspid: this._caConfig.mspId, 84 | cryptoContent: { 85 | privateKeyPEM: this._admin.key, 86 | signedCertPEM: this._admin.cert 87 | } 88 | }); 89 | } 90 | async initialize() { 91 | try { 92 | await this._channel.initialize(); 93 | } catch(e) { 94 | console.log(`Failed to initialize chain. Error: ${e.message}`); 95 | throw e; 96 | } 97 | } 98 | async createChannel(envelope) { 99 | const txId = this._client.newTransactionID(); 100 | const channelConfig = this._client.extractChannelConfig(envelope); 101 | const signature = this._client.signChannelConfig(channelConfig); 102 | const request = { 103 | name: this._channelName, 104 | orderer: this._channel.getOrderers()[0], 105 | config: channelConfig, 106 | signatures: [signature], 107 | txId 108 | }; 109 | const response = await this._client.createChannel(request); 110 | // Wait for 5sec to create channel 111 | //console.log("channel log "); 112 | //console.log(response); 113 | await new Promise(resolve => { 114 | setTimeout(resolve, 5000); 115 | }); 116 | return response; 117 | } 118 | async joinChannel() { 119 | try { 120 | const genesisBlock = await this._channel.getGenesisBlock({ 121 | txId: this._client.newTransactionID() 122 | }); 123 | const request = { 124 | targets: this._peers, 125 | txId: this._client.newTransactionID(), 126 | block: genesisBlock 127 | }; 128 | const joinedChannelPromises = this._eventHubs.map(eh => { 129 | eh.connect(); 130 | return new Promise((resolve, reject) => { 131 | let blockRegistration; 132 | const cb = block => { 133 | clearTimeout(responseTimeout); 134 | eh.unregisterBlockEvent(blockRegistration); 135 | if(block.data.data.length === 1) { 136 | const channelHeader = block.data.data[0].payload.header.channel_header; 137 | if(channelHeader.channel_id === this._channelName) { 138 | resolve(); 139 | } else { 140 | reject(new Error('Peer did not join an expected channel.')); 141 | } 142 | } 143 | }; 144 | blockRegistration = eh.registerBlockEvent(cb); 145 | const responseTimeout = setTimeout(() => { 146 | eh.unregisterBlockEvent(blockRegistration); 147 | reject(new Error('Peer did not respond in a timely fashion!')); 148 | }, JOIN_TIMEOUT); 149 | }); 150 | }); 151 | const completedPromise = joinedChannelPromises.concat([ 152 | this._channel.joinChannel(request) 153 | ]); 154 | await Promise.all(completedPromise); 155 | } catch(e) { 156 | console.log(`Error joining peer to channel. Error: ${e.message}`); 157 | throw e; 158 | } 159 | } 160 | async checkChannelMembership() { 161 | try { 162 | const { 163 | channels 164 | } = await this._client.queryChannels(this._peers[0]); 165 | if(!Array.isArray(channels)) { 166 | return false; 167 | } 168 | return channels.some(({ 169 | channel_id 170 | }) => channel_id === this._channelName); 171 | } catch(e) { 172 | return false; 173 | } 174 | } 175 | async checkInstalled(chaincodeId, chaincodeVersion, chaincodePath) { 176 | let { 177 | chaincodes 178 | } = await this._channel.queryInstantiatedChaincodes(); 179 | if(!Array.isArray(chaincodes)) { 180 | return false; 181 | } 182 | return chaincodes.some(cc => cc.name === chaincodeId && cc.path === chaincodePath && cc.version === chaincodeVersion); 183 | } 184 | async install(chaincodeId, chaincodeVersion, chaincodePath) { 185 | const request = { 186 | targets: this._peers, 187 | chaincodePath, 188 | chaincodeId, 189 | chaincodeVersion 190 | }; 191 | // Make install proposal to all peers 192 | let results; 193 | try { 194 | results = await this._client.installChaincode(request); 195 | } catch(e) { 196 | console.log(`Error sending install proposal to peer! Error: ${e.message}`); 197 | throw e; 198 | } 199 | const proposalResponses = results[0]; 200 | const allGood = proposalResponses.every(pr => pr.response && pr.response.status == 200); 201 | return allGood; 202 | } 203 | async instantiate(chaincodeId, chaincodeVersion, chaincodePath, ...args) { 204 | let proposalResponses, proposal; 205 | const txId = this._client.newTransactionID(); 206 | try { 207 | const request = { 208 | chaincodeType: 'golang', 209 | chaincodePath, 210 | chaincodeId, 211 | chaincodeVersion, 212 | fcn: 'init', 213 | args: utils.marshalArgs(args), 214 | txId 215 | }; 216 | const results = await this._channel.sendInstantiateProposal(request, 100000); 217 | proposalResponses = results[0]; 218 | proposal = results[1]; 219 | let allGood = proposalResponses.every(pr => pr.response && pr.response.status == 200); 220 | if(!allGood) { 221 | throw new Error(`Proposal rejected by some (all) of the peers: ${proposalResponses}`); 222 | } 223 | } catch(e) { 224 | throw e; 225 | } 226 | try { 227 | const request = { 228 | proposalResponses, 229 | proposal 230 | }; 231 | const deployId = txId.getTransactionID(); 232 | const transactionCompletePromises = this._eventHubs.map(eh => { 233 | eh.connect(); 234 | return new Promise((resolve, reject) => { 235 | // Set timeout for the transaction response from the current peer 236 | const responseTimeout = setTimeout(() => { 237 | eh.unregisterTxEvent(deployId); 238 | reject(new Error('Peer did not respond in a timely fashion!')); 239 | }, TRANSACTION_TIMEOUT); 240 | eh.registerTxEvent(deployId, (tx, code) => { 241 | clearTimeout(responseTimeout); 242 | eh.unregisterTxEvent(deployId); 243 | if(code != 'VALID') { 244 | reject(new Error(`Peer has rejected transaction with code: ${code}`)); 245 | } else { 246 | resolve(); 247 | } 248 | }); 249 | }); 250 | }); 251 | transactionCompletePromises.push(this._channel.sendTransaction(request)); 252 | await transactionCompletePromises; 253 | } catch(e) { 254 | throw e; 255 | } 256 | } 257 | async getBlocks(currentBlock, noOfLastBlocks) { 258 | if(currentBlock === 0) { 259 | return []; 260 | } 261 | if(!currentBlock) { 262 | currentBlock = -1; 263 | } 264 | if(!noOfLastBlocks) { 265 | noOfLastBlocks = 10; 266 | } 267 | currentBlock = typeof currentBlock !== 'number' ? Number(currentBlock) : currentBlock; 268 | noOfLastBlocks = typeof noOfLastBlocks !== 'number' ? Number(noOfLastBlocks) : noOfLastBlocks; 269 | var { 270 | height 271 | } = await this._channel.queryInfo(); 272 | if(currentBlock == -1) { 273 | currentBlock = height; 274 | } 275 | if(height.comp(currentBlock) >= 0) { 276 | height = Long.fromNumber(currentBlock, height.unsigned); 277 | } 278 | let blockCount; 279 | if(height.comp(noOfLastBlocks) > 0) { 280 | blockCount = Long.fromNumber(noOfLastBlocks, height.unsigned); 281 | } else { 282 | blockCount = height; 283 | } 284 | blockCount = blockCount.toNumber(); 285 | const queryBlock = this._channel.queryBlock.bind(this._channel); 286 | const blockPromises = {}; 287 | blockPromises[Symbol.iterator] = function* () { 288 | for(let i = 1; i <= blockCount; i++) { 289 | yield queryBlock(height.sub(i).toNumber()); 290 | } 291 | }; 292 | const blocks = await Promise.all([...blockPromises]); 293 | return blocks.map(utils.unmarshalBlock); 294 | } 295 | async registerAndEnroll(enrollmentID) { 296 | try { 297 | if(!enrollmentID && enrollmentID === "") { 298 | throw new Error(`Invalid User Id`); 299 | } 300 | let adminUser = await this._client.getUserContext('admin', true); 301 | //let adminUser = this._adminUser; 302 | if(!adminUser && !adminUser.isEnrolled()) { 303 | throw new Error(`Admin user not present to register user : ` + enrollmentID); 304 | } 305 | return enrollUser(this._client, enrollmentID, "", this._ca, { 306 | mspId: this._caConfig.mspId, 307 | adminUser: adminUser, 308 | affiliationOrg: this._peerConfig.org, 309 | noOfAttempts: 3 310 | }); 311 | } catch(e) { 312 | throw e; 313 | } 314 | } 315 | async getTransactionDetails(txId) { 316 | try { 317 | var transactionData = await this._channel.queryTransaction(txId); 318 | transactionData = transactionData ? transactionData.transactionEnvelope.payload.data.actions : ""; 319 | var execution_response = transactionData !== "" ? transactionData[0].payload.action.proposal_response_payload.extension.response : ""; 320 | return { 321 | txId: txId, 322 | results: execution_response 323 | }; 324 | } catch(e) { 325 | throw e; 326 | } 327 | } 328 | } -------------------------------------------------------------------------------- /blockchainNetwork/set-up/config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const SECRETS_DIR = process.env.SECRETSDIR || '/run/secrets'; 4 | 5 | function readConfig() { 6 | if(fs.existsSync(SECRETS_DIR)) { 7 | const data = JSON.parse(fs.readFileSync(path.resolve(SECRETS_DIR, 'config')).toString()); 8 | data.channelConfig = fs.readFileSync(path.resolve(SECRETS_DIR, 'channel')); 9 | return data; 10 | } 11 | } 12 | const config = readConfig(); 13 | export default config; -------------------------------------------------------------------------------- /blockchainNetwork/set-up/enroll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enrolls a user with the respective CA. 3 | */ 4 | var User = require('fabric-client/lib/User'); 5 | export default async function (client, enrollmentID, enrollmentSecret, ca, { 6 | mspId, 7 | adminUser, 8 | affiliationOrg, 9 | noOfAttempts 10 | }) { 11 | try { 12 | if(!enrollmentID && enrollmentID === "") { 13 | throw new Error(`Invalid User Id`); 14 | } 15 | var getUser = async function (client, userId, count) { 16 | try { 17 | return client.getUserContext(userId, true); 18 | } catch(e) { 19 | if(count > noOfAttempts) { 20 | count++; 21 | await new Promise(res => setTimeout(() => res(), 1000)); 22 | return getUser(client, userId, count); 23 | } else { 24 | return null; 25 | } 26 | } 27 | }; 28 | let user = await getUser(client, enrollmentID, 1); 29 | if(user && user.isEnrolled()) { 30 | return user; 31 | } 32 | try { 33 | if(!enrollmentSecret || enrollmentSecret === "") { 34 | //console.log('Initiate member ' + enrollmentID + " registration to " + affiliationOrg); 35 | enrollmentSecret = await ca.register({ 36 | enrollmentID: enrollmentID, 37 | affiliation: affiliationOrg, 38 | maxEnrollments: 1, 39 | role: 'client' 40 | }, adminUser); 41 | //console.log("Successfully registered user " + enrollmentID + " with secret " + enrollmentSecret); 42 | } 43 | } catch(e) { 44 | throw new Error(`Failed to register User. Error: ${e.message}`); 45 | } 46 | try { 47 | const enrollment = await ca.enroll({ 48 | enrollmentID, 49 | enrollmentSecret 50 | }); 51 | user = new User(enrollmentID, client); 52 | await user.setEnrollment(enrollment.key, enrollment.certificate, mspId); 53 | await client.setUserContext(user); 54 | return user; 55 | } catch(e) { 56 | throw new Error(`Failed to enroll and persist User. Error: ${e.message}`); 57 | } 58 | } catch(e) { 59 | throw new Error(`Could not get UserContext! Error: ${e.message}`); 60 | } 61 | } -------------------------------------------------------------------------------- /blockchainNetwork/set-up/invoke.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const TRANSACTION_TIMEOUT = 120000; 3 | export default async function (userId, clientObject, chaincodeId, chaincodeVersion, fcn, args) { 4 | var Transaction = require('fabric-client/lib/TransactionID.js'); 5 | //var user_from_store = await clientObject._client.getUserContext(userId, true); 6 | var getUser = async function (clientObject, userId, count) { 7 | try { 8 | var user = await clientObject._client.getUserContext(userId, true); 9 | return user; 10 | } catch(e) { 11 | if(count > 2) { 12 | count++; 13 | return getUser(clientObject, userId, count); 14 | } else { 15 | throw new Error('Failed to get user : ' + userId + ' from persistence. Error: ' + e.message); 16 | } 17 | } 18 | }; 19 | var user_from_store = await getUser(clientObject, userId, 1); 20 | if(!(user_from_store && user_from_store.isEnrolled())) { 21 | throw new Error('Failed to get user : ' + userId + ' from persistence'); 22 | } 23 | //console.log('Successfully loaded user : ' + userId + ' from persistence'); 24 | let proposalResponses, proposal; 25 | const txId = new Transaction(user_from_store); 26 | try { 27 | const request = { 28 | chaincodeId: chaincodeId, 29 | chaincodeVersion: chaincodeVersion, 30 | fcn: fcn, 31 | args: args, 32 | chainId: clientObject._channelName, 33 | txId: txId 34 | }; 35 | const results = await clientObject._channel.sendTransactionProposal(request); 36 | proposalResponses = results[0]; 37 | proposal = results[1]; 38 | const allGood = proposalResponses.every(pr => pr.response && pr.response.status == 200); 39 | if(!allGood) { 40 | throw new Error(`Proposal rejected by some (all) of the peers: ${proposalResponses}`); 41 | } 42 | } catch(e) { 43 | throw e; 44 | } 45 | try { 46 | const request = { 47 | proposalResponses, 48 | proposal 49 | }; 50 | var transaction_id_string = txId.getTransactionID(); //Get the transaction ID string to be used by the event processing 51 | var promises = []; 52 | var sendPromise = clientObject._channel.sendTransaction(request); 53 | promises.push(sendPromise); 54 | let event_hub = clientObject._eventHubs[0]; 55 | let txPromise = new Promise((resolve, reject) => { 56 | let handle = setTimeout(() => { 57 | event_hub.disconnect(); 58 | resolve({ 59 | event_status: 'TIMEOUT' 60 | }); 61 | }, TRANSACTION_TIMEOUT); 62 | var connectHub = async function (event_hub, count) { 63 | event_hub.connect(); 64 | event_hub.registerTxEvent(transaction_id_string, (tx, code) => { 65 | // this is the callback for transaction event status 66 | // first some clean up of event listener 67 | clearTimeout(handle); 68 | event_hub.unregisterTxEvent(transaction_id_string); 69 | event_hub.disconnect(); 70 | // now let the application know what happened 71 | resolve({ 72 | event_status: code, 73 | tx_id: transaction_id_string 74 | }); 75 | //return return_status; 76 | }, (err) => { 77 | count++; 78 | if(count > 2) { 79 | clearTimeout(handle); 80 | reject(new Error('There was a problem with the eventhub ::' + err)); 81 | } else { 82 | connectHub(event_hub, count); 83 | } 84 | }); 85 | } 86 | connectHub(event_hub, 1); 87 | }); 88 | promises.push(txPromise); 89 | var results = await Promise.all(promises); 90 | //console.log('Send transaction promise and event listener promise have completed'); 91 | // check the results in the order the promises were added to the promise all list 92 | if(!(results && results[0] && results[0].status === 'SUCCESS')) { 93 | //console.log('Successfully sent transaction to the orderer.'); 94 | throw new Error('Failed to order the transaction. Error code: ' + results.status); 95 | } 96 | if(results && results[1] && results[1].event_status === 'VALID') { 97 | console.log('Successfully committed the change to the ledger by the peer'); 98 | return results[1].tx_id; 99 | } else { 100 | throw new Error('Transaction failed to be committed to the ledger due to ::' + results[1].event_status); 101 | } 102 | } catch(e) { 103 | throw e; 104 | } 105 | } -------------------------------------------------------------------------------- /blockchainNetwork/set-up/query.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | export default async function (userId, clientObject, chaincodeId, chaincodeVersion, fcn, args) { 3 | try { 4 | //var user_from_store = await clientObject._client.getUserContext(userId, true); 5 | var getUser = async function (clientObject, userId, count) { 6 | try { 7 | return clientObject._client.getUserContext(userId, true); 8 | } catch(e) { 9 | if(count > 2) { 10 | count++; 11 | return getUser(clientObject, userId, count); 12 | } else { 13 | throw new Error('Failed to get user : ' + userId + ' from persistence. Error: ' + e.message); 14 | } 15 | } 16 | }; 17 | var user_from_store = await getUser(clientObject, userId, 1); 18 | if(!(user_from_store && user_from_store.isEnrolled())) { 19 | throw new Error('Failed to get user : ' + userId + ' from persistence'); 20 | } 21 | //console.log('Successfully loaded user : ' + userId + ' from persistence'); 22 | const request = { 23 | //targets : --- letting this default to the peers assigned to the channel 24 | chaincodeId: chaincodeId, 25 | chaincodeVersion: chaincodeVersion, 26 | fcn: fcn, 27 | args: args 28 | }; 29 | const query_responses = await clientObject._channel.queryByChaincode(request, user_from_store); 30 | //console.log("Query has completed, checking results"); 31 | // query_responses could have more than one results if there multiple peers were used as targets 32 | if(query_responses && query_responses.length == 1) { 33 | if(query_responses[0] instanceof Error) { 34 | throw new Error("Error from query = ", query_responses[0].message); 35 | } else { 36 | return query_responses.toString('utf8'); 37 | } 38 | } else { 39 | throw new Error("No payloads were returned from query"); 40 | } 41 | } catch(e) { 42 | throw e; 43 | } 44 | } -------------------------------------------------------------------------------- /blockchainNetwork/set-up/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import config from './config'; 3 | import { 4 | OrganizationClient 5 | } from './client'; 6 | import http from 'http'; 7 | import url from 'url'; 8 | // Setup clients per organization 9 | const clients = config.peers.map(obj => new OrganizationClient(config.channelName, config.orderer, obj.peer, obj.ca, obj.admin)); 10 | 11 | function getAdminOrgs() { 12 | return Promise.all(clients.map(client => client.createOrgAdmin())); 13 | } 14 | (async () => { 15 | try { 16 | await Promise.all(clients.map(client => client.login())); 17 | } catch(e) { 18 | console.log('Fatal error logging into blockchain organization clients!'); 19 | console.log(e); 20 | process.exit(-1); 21 | } 22 | // Setup event hubs 23 | clients.map(client => client.initEventHubs()); 24 | try { 25 | await getAdminOrgs(); 26 | if(!(await clients[0].checkChannelMembership())) { 27 | console.log('Default channel not found, attempting creation...'); 28 | const createChannelResponse = await clients[0].createChannel(config.channelConfig); 29 | if(createChannelResponse.status === 'SUCCESS') { 30 | console.log('Successfully created a new default channel.'); 31 | console.log('Joining peers to the default channel.'); 32 | await Promise.all(clients.map(client => client.joinChannel())); 33 | // Wait for 10s for the peers to join the newly created channel 34 | await new Promise(resolve => { 35 | setTimeout(resolve, 10000); 36 | }); 37 | } 38 | } 39 | } catch(e) { 40 | console.log('Fatal error bootstrapping the blockchain network!'); 41 | console.log(e); 42 | process.exit(-1); 43 | } 44 | // Initialize network 45 | try { 46 | await Promise.all(clients.map(client => client.initialize())); 47 | } catch(e) { 48 | console.log('Fatal error initializing blockchain organization clients!'); 49 | console.log(e); 50 | process.exit(-1); 51 | } 52 | // Install chaincode on all peers 53 | let installedOnClients; 54 | try { 55 | await getAdminOrgs(); 56 | installedOnClients = await Promise.all(clients.map(client => client.checkInstalled(config.chaincodeId, config.chaincodeVersion, config.chaincodePath))); 57 | } catch(e) { 58 | console.log('Fatal error getting installation status of the chaincode!'); 59 | console.log(e); 60 | process.exit(-1); 61 | } 62 | let check = installedOnClients.every(installed => installed); 63 | if(!(check)) { 64 | console.log('Chaincode is not installed, attempting installation...'); 65 | // Pull chaincode environment base image 66 | try { 67 | await getAdminOrgs(); 68 | const socketPath = process.env.DOCKER_SOCKET_PATH || (process.platform === 'win32' ? '//./pipe/docker_engine' : '/var/run/docker.sock'); 69 | const ccenvImage = process.env.DOCKER_CCENV_IMAGE || 'hyperledger/fabric-ccenv:x86_64-1.1.0'; 70 | const listOpts = { 71 | socketPath, 72 | method: 'GET', 73 | path: '/images/json' 74 | }; 75 | const pullOpts = { 76 | socketPath, 77 | method: 'POST', 78 | path: url.format({ 79 | pathname: '/images/create', 80 | query: { 81 | fromImage: ccenvImage 82 | } 83 | }) 84 | }; 85 | const images = await new Promise((resolve, reject) => { 86 | const req = http.request(listOpts, (response) => { 87 | let data = ''; 88 | response.setEncoding('utf-8'); 89 | response.on('data', chunk => { 90 | data += chunk; 91 | }); 92 | response.on('end', () => { 93 | resolve(JSON.parse(data)); 94 | }); 95 | }); 96 | req.on('error', reject); 97 | req.end(); 98 | }); 99 | const imageExists = images.some(i => i.RepoTags && i.RepoTags.some(tag => tag === ccenvImage)); 100 | if(!imageExists) { 101 | console.log('Base container image not present, pulling from Docker Hub...'); 102 | await new Promise((resolve, reject) => { 103 | const req = http.request(pullOpts, (response) => { 104 | response.on('data', () => {}); 105 | response.on('end', () => { 106 | resolve(); 107 | }); 108 | }); 109 | req.on('error', reject); 110 | req.end(); 111 | }); 112 | console.log('Base container image downloaded.'); 113 | } else { 114 | console.log('Base container image present.'); 115 | } 116 | } catch(e) { 117 | console.log('Fatal error pulling docker images.'); 118 | console.log(e); 119 | process.exit(-1); 120 | } 121 | try { 122 | await Promise.all(clients.map(client => client.install(config.chaincodeId, config.chaincodeVersion, config.chaincodePath))); 123 | await new Promise(resolve => { 124 | setTimeout(resolve, 10000); 125 | }); 126 | console.log('Successfully installed chaincode on the default channel.'); 127 | } catch(e) { 128 | console.log('Fatal error installing chaincode on the Peer!'); 129 | console.log(e); 130 | process.exit(-1); 131 | } 132 | } else { 133 | console.log('Chaincode already installed on the blockchain network.'); 134 | } 135 | // Instantiate chaincode on all peers 136 | // Instantiating the chaincode on a single peer should be enough (for now) 137 | try { 138 | await clients[0].instantiate(config.chaincodeId, config.chaincodeVersion, config.chaincodePath, '{"Args":[""]}'); 139 | console.log('Successfully instantiated chaincode on all peers.'); 140 | } catch(e) { 141 | console.log('Fatal error instantiating chaincode on some(all) peers!'); 142 | console.log(e); 143 | process.exit(-1); 144 | } 145 | 146 | })(); 147 | // Export organization clients 148 | /*export { 149 | shopClient, 150 | fitcoinClient 151 | };*/ -------------------------------------------------------------------------------- /blockchainNetwork/set-up/utils.js: -------------------------------------------------------------------------------- 1 | import { 2 | snakeToCamelCase, 3 | camelToSnakeCase 4 | } from 'json-style-converter'; 5 | module.exports.wrapError = function(message, innerError) { 6 | let error = new Error(message); 7 | error.inner = innerError; 8 | console.log(error.message); 9 | throw error; 10 | }; 11 | module.exports.marshalArgs = function(args) { 12 | if(!args) { 13 | return args; 14 | } 15 | if(typeof args === 'string') { 16 | return [args]; 17 | } 18 | let snakeArgs = camelToSnakeCase(args); 19 | if(Array.isArray(args)) { 20 | return snakeArgs.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg.toString()); 21 | } 22 | if(typeof args === 'object') { 23 | return [JSON.stringify(snakeArgs)]; 24 | } 25 | }; 26 | module.exports.unmarshalResult = function(result) { 27 | if(!Array.isArray(result)) { 28 | return result; 29 | } 30 | let buff = Buffer.concat(result); 31 | if(!Buffer.isBuffer(buff)) { 32 | return result; 33 | } 34 | let json = buff.toString('utf8'); 35 | if(!json) { 36 | return null; 37 | } 38 | let obj = JSON.parse(json); 39 | return snakeToCamelCase(obj); 40 | }; 41 | module.exports.unmarshalBlock = function(block) { 42 | const transactions = Array.isArray(block.data.data) ? block.data.data.map(({ 43 | payload: { 44 | header, 45 | data 46 | } 47 | }) => { 48 | const { 49 | channel_header 50 | } = header; 51 | const { 52 | type, 53 | timestamp, 54 | tx_id, 55 | channel_id 56 | } = channel_header; 57 | const { 58 | actions 59 | } = data; 60 | var execution_response = actions ? actions.map(obj => obj.payload.action.proposal_response_payload.extension.response) : ""; 61 | //console.log(execution_response); 62 | return { 63 | type, 64 | timestamp, 65 | tx_id, 66 | channel_id, 67 | execution_response 68 | }; 69 | }) : []; 70 | return { 71 | id: block.header.number.toString(), 72 | fingerprint: block.header.data_hash.slice(0, 20), 73 | transactions 74 | }; 75 | }; 76 | -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "6.11.3" 8 | } 9 | } 10 | ] 11 | ], 12 | "sourceMaps": "inline" 13 | } -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .vscode 4 | .idea 5 | .env 6 | .env.sample 7 | npm-debug.log 8 | .jshintignore 9 | .jshintrc 10 | .npmrc 11 | bin 12 | dist 13 | fitcoin-peer 14 | shop-peer 15 | -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/node:6.11.3 2 | 3 | ENV NODE_ENV production 4 | ENV PORT 3000 5 | ENV DOCKER_SOCKET_PATH /host/var/run/docker.sock 6 | ENV DOCKER_CCENV_IMAGE=hyperledger/fabric-ccenv:x86_64-1.1.0 7 | 8 | RUN mkdir /app 9 | COPY . /app 10 | WORKDIR /app 11 | RUN npm install 12 | 13 | EXPOSE 3000 14 | 15 | CMD ["node", "index.js"] 16 | -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/chaincode/src/bcfit/contract.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | "math/rand" 26 | "strconv" 27 | "strings" 28 | "time" 29 | 30 | "github.com/hyperledger/fabric/core/chaincode/shim" 31 | pb "github.com/hyperledger/fabric/protos/peer" 32 | ) 33 | 34 | // ============================================================================================================================ 35 | // Make Purchase - creates purchase Contract 36 | // Inputs - userID, sellerID, productID, quantity 37 | // ============================================================================================================================ 38 | func (t *SimpleChaincode) makePurchase(stub shim.ChaincodeStubInterface, args []string) pb.Response { 39 | if len(args) != 4 { 40 | return shim.Error("Incorrect number of arguments") 41 | } 42 | var err error 43 | 44 | //creates contract struct with properties, and get sellerID, userID, productID, quantity from args 45 | var contract Contract 46 | contract.Id = "c" + randomInts(6) 47 | contract.UserId = args[0] 48 | contract.SellerId = args[1] 49 | contract.ProductId = args[2] 50 | quantity, err := strconv.Atoi(args[3]) 51 | if err != nil { 52 | return shim.Error("4th argument 'quantity' must be a numeric string") 53 | } 54 | contract.Quantity = quantity 55 | 56 | //get seller 57 | sellerAsBytes, err := stub.GetState(contract.SellerId) 58 | if err != nil { 59 | return shim.Error("Failed to get seller") 60 | } 61 | var seller Seller 62 | json.Unmarshal(sellerAsBytes, &seller) 63 | if seller.Type != TYPE_SELLER { 64 | return shim.Error("Not seller type") 65 | } 66 | 67 | //find the product 68 | var product Product 69 | productFound := false 70 | for h := 0; h < len(seller.Products); h++ { 71 | if seller.Products[h].Id == contract.ProductId { 72 | productFound = true 73 | product = seller.Products[h] 74 | break 75 | } 76 | } 77 | 78 | //if product not found return error 79 | if productFound != true { 80 | return shim.Error("Product not found") 81 | } 82 | 83 | //calculates cost and assigns to contract 84 | contract.Cost = product.Price * contract.Quantity 85 | //gets product name 86 | contract.ProductName = product.Name 87 | //assign 'Pending' state 88 | contract.State = STATE_PENDING 89 | 90 | // get user's current state 91 | var user User 92 | userAsBytes, err := stub.GetState(contract.UserId) 93 | if err != nil { 94 | return shim.Error("Failed to get user") 95 | } 96 | json.Unmarshal(userAsBytes, &user) 97 | if user.Type != TYPE_USER { 98 | return shim.Error("Not user type") 99 | } 100 | 101 | //check if user has enough Fitcoinsbalance 102 | if user.FitcoinsBalance < contract.Cost { 103 | return shim.Error("Insufficient funds") 104 | } 105 | 106 | //store contract 107 | contractAsBytes, _ := json.Marshal(contract) 108 | err = stub.PutState(contract.Id, contractAsBytes) 109 | if err != nil { 110 | return shim.Error(err.Error()) 111 | } 112 | 113 | //append contractId 114 | user.ContractIds = append(user.ContractIds, contract.Id) 115 | 116 | //update user's state 117 | updatedUserAsBytes, _ := json.Marshal(user) 118 | err = stub.PutState(contract.UserId, updatedUserAsBytes) 119 | if err != nil { 120 | return shim.Error(err.Error()) 121 | } 122 | 123 | //return contract info 124 | fmt.Println("contractAsBytes") 125 | fmt.Println(contractAsBytes) 126 | return shim.Success(contractAsBytes) 127 | 128 | } 129 | 130 | // ============================================================================================================================ 131 | // Transact Purchase - update user account, update seller's account and product inventory, update contract state 132 | // Inputs - memberId, contractID, newState(complete or declined) 133 | // ============================================================================================================================ 134 | func (t *SimpleChaincode) transactPurchase(stub shim.ChaincodeStubInterface, args []string) pb.Response { 135 | if len(args) != 3 { 136 | return shim.Error("Incorrect number of arguments") 137 | } 138 | //get contractID args 139 | memberId := args[0] 140 | contractId := args[1] 141 | newState := args[2] 142 | 143 | // Get contract from the ledger 144 | contractAsBytes, err := stub.GetState(contractId) 145 | if err != nil { 146 | return shim.Error("Failed to get contract") 147 | } 148 | var contract Contract 149 | json.Unmarshal(contractAsBytes, &contract) 150 | 151 | //ensure call is called by authorized user 152 | if memberId != contract.SellerId && memberId != contract.UserId { 153 | return shim.Error("Member not authorized to update contract") 154 | } 155 | 156 | //if current contract state is pending, then execute transaction 157 | if contract.State == STATE_PENDING { 158 | if newState == STATE_COMPLETE && memberId == contract.SellerId { 159 | //get seller 160 | var member Seller 161 | memberAsBytes, err := stub.GetState(memberId) 162 | if err != nil { 163 | return shim.Error("Failed to get member") 164 | } 165 | json.Unmarshal(memberAsBytes, &member) 166 | 167 | //get contract user's current state 168 | var contractUser User 169 | contractUserAsBytes, err := stub.GetState(contract.UserId) 170 | if err != nil { 171 | return shim.Error("Failed to get contract owner") 172 | } 173 | json.Unmarshal(contractUserAsBytes, &contractUser) 174 | 175 | //update user's FitcoinsBalance 176 | if (contractUser.FitcoinsBalance - contract.Cost) >= 0 { 177 | contractUser.FitcoinsBalance = contractUser.FitcoinsBalance - contract.Cost 178 | } else { 179 | return shim.Error("Insufficient fitcoins") 180 | } 181 | 182 | //update seller's product count 183 | productFound := false 184 | for h := 0; h < len(member.Products); h++ { 185 | if member.Products[h].Id == contract.ProductId { 186 | productFound = true 187 | if member.Products[h].Count >= contract.Quantity { 188 | member.Products[h].Count = member.Products[h].Count - contract.Quantity 189 | } 190 | break 191 | } 192 | } 193 | //if product not found return error 194 | if productFound == true { 195 | //update seller's FitcoinsBalance 196 | member.FitcoinsBalance = member.FitcoinsBalance + contract.Cost 197 | //update user state 198 | updatedUserAsBytes, _ := json.Marshal(contractUser) 199 | err = stub.PutState(contract.UserId, updatedUserAsBytes) 200 | if err != nil { 201 | return shim.Error(err.Error()) 202 | } 203 | //update seller state 204 | updatedSellerAsBytes, _ := json.Marshal(member) 205 | err = stub.PutState(contract.SellerId, updatedSellerAsBytes) 206 | if err != nil { 207 | return shim.Error(err.Error()) 208 | } 209 | contract.State = STATE_COMPLETE 210 | 211 | } else { 212 | contract.State = STATE_DECLINED 213 | declinedContractAsBytes, _ := json.Marshal(contract) 214 | err = stub.PutState(contract.Id, declinedContractAsBytes) 215 | if err != nil { 216 | return shim.Error(err.Error()) 217 | } 218 | return shim.Error("Product not available for sale. Cancelling contract.") 219 | } 220 | } else if newState == STATE_DECLINED { 221 | contract.State = STATE_DECLINED 222 | } else { 223 | return shim.Error("Invalid new state") 224 | } 225 | 226 | // update contract state on ledger 227 | updatedContractAsBytes, _ := json.Marshal(contract) 228 | err = stub.PutState(contract.Id, updatedContractAsBytes) 229 | if err != nil { 230 | return shim.Error(err.Error()) 231 | } 232 | //return contract info 233 | return shim.Success(updatedContractAsBytes) 234 | } else { 235 | return shim.Error("Contract already Complete or Declined") 236 | } 237 | } 238 | 239 | // ============================================================================================================================ 240 | // Get all user contracts 241 | // Inputs - userID 242 | // ============================================================================================================================ 243 | func (t *SimpleChaincode) getAllUserContracts(stub shim.ChaincodeStubInterface, args []string) pb.Response { 244 | if len(args) != 1 { 245 | return shim.Error("Incorrect number of arguments") 246 | } 247 | var err error 248 | 249 | //get userID from args 250 | user_id := args[0] 251 | 252 | //get user 253 | userAsBytes, err := stub.GetState(user_id) 254 | if err != nil { 255 | return shim.Error("Failed to get user") 256 | } 257 | var user User 258 | json.Unmarshal(userAsBytes, &user) 259 | if user.Type != TYPE_USER { 260 | return shim.Error("Not user type") 261 | } 262 | 263 | //get user contracts 264 | var contracts []Contract 265 | for h := 0; h < len(user.ContractIds); h++ { 266 | //get contract from the ledger 267 | contractAsBytes, err := stub.GetState(user.ContractIds[h]) 268 | if err != nil { 269 | return shim.Error("Failed to get contract") 270 | } 271 | var contract Contract 272 | json.Unmarshal(contractAsBytes, &contract) 273 | contracts = append(contracts, contract) 274 | } 275 | //change to array of bytes 276 | contractsAsBytes, _ := json.Marshal(contracts) 277 | return shim.Success(contractsAsBytes) 278 | 279 | } 280 | 281 | // ============================================================================================================================ 282 | // Get all contracts 283 | // Inputs - (none) 284 | // ============================================================================================================================ 285 | func (t *SimpleChaincode) getAllContracts(stub shim.ChaincodeStubInterface, args []string) pb.Response { 286 | var err error 287 | var contracts []Contract 288 | 289 | // ---- Get All Contracts ---- // 290 | resultsIterator, err := stub.GetStateByRange("c0", "c9999999999999999999") 291 | if err != nil { 292 | return shim.Error(err.Error()) 293 | } 294 | defer resultsIterator.Close() 295 | 296 | for resultsIterator.HasNext() { 297 | aKeyValue, err := resultsIterator.Next() 298 | if err != nil { 299 | return shim.Error(err.Error()) 300 | } 301 | queryKeyAsStr := aKeyValue.Key 302 | queryValAsBytes := aKeyValue.Value 303 | fmt.Println("on contract id - ", queryKeyAsStr) 304 | var contract Contract 305 | json.Unmarshal(queryValAsBytes, &contract) 306 | contracts = append(contracts, contract) 307 | } 308 | 309 | //change to array of bytes 310 | contractsAsBytes, _ := json.Marshal(contracts) 311 | return shim.Success(contractsAsBytes) 312 | 313 | } 314 | 315 | //generate an array of random ints 316 | func randomArray(len int) []int { 317 | a := make([]int, len) 318 | for i := 0; i <= len-1; i++ { 319 | a[i] = rand.Intn(10) 320 | } 321 | return a 322 | } 323 | 324 | // Generate a random string of ints with length len 325 | func randomInts(len int) string { 326 | rand.Seed(time.Now().UnixNano()) 327 | intArray := randomArray(len) 328 | var stringInt []string 329 | for _, i := range intArray { 330 | stringInt = append(stringInt, strconv.Itoa(i)) 331 | } 332 | return strings.Join(stringInt, "") 333 | } 334 | -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/chaincode/src/bcfit/member.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "strconv" 25 | "strings" 26 | 27 | "github.com/hyperledger/fabric/core/chaincode/shim" 28 | pb "github.com/hyperledger/fabric/protos/peer" 29 | ) 30 | 31 | // ============================================================================================================================ 32 | // Create member 33 | // Inputs - id, type(user or seller) 34 | // ============================================================================================================================ 35 | func (t *SimpleChaincode) createMember(stub shim.ChaincodeStubInterface, args []string) pb.Response { 36 | var err error 37 | if len(args) != 2 { 38 | return shim.Error("Incorrect number of arguments") 39 | } 40 | 41 | //get id and type from args 42 | member_id := args[0] 43 | member_type := strings.ToLower(args[1]) 44 | 45 | //check if type is 'user' 46 | if member_type == TYPE_USER { 47 | 48 | //create user 49 | var user User 50 | user.Id = member_id 51 | user.Type = TYPE_USER 52 | user.FitcoinsBalance = 0 53 | user.StepsUsedForConversion = 0 54 | user.TotalSteps = 0 55 | 56 | //store user 57 | userAsBytes, _ := json.Marshal(user) 58 | err = stub.PutState(user.Id, userAsBytes) 59 | if err != nil { 60 | return shim.Error(err.Error()) 61 | } 62 | 63 | //return user info 64 | return shim.Success(userAsBytes) 65 | 66 | } else if member_type == TYPE_SELLER { 67 | //check if type is 'seller' 68 | 69 | //create seller 70 | var seller Seller 71 | seller.Id = member_id 72 | seller.Type = TYPE_SELLER 73 | seller.FitcoinsBalance = 0 74 | 75 | // store seller 76 | sellerAsBytes, _ := json.Marshal(seller) 77 | err = stub.PutState(seller.Id, sellerAsBytes) 78 | if err != nil { 79 | return shim.Error(err.Error()) 80 | } 81 | 82 | //get and update sellerIDs 83 | sellerIdsBytes, err := stub.GetState("sellerIds") 84 | if err != nil { 85 | return shim.Error("Unable to get users.") 86 | } 87 | var sellerIds []string 88 | // add sellerID to update sellers 89 | json.Unmarshal(sellerIdsBytes, &sellerIds) 90 | sellerIds = append(sellerIds, seller.Id) 91 | updatedSellerIdsBytes, _ := json.Marshal(sellerIds) 92 | err = stub.PutState("sellerIds", updatedSellerIdsBytes) 93 | 94 | //return seller info 95 | return shim.Success(sellerAsBytes) 96 | 97 | } 98 | 99 | return shim.Success(nil) 100 | 101 | } 102 | 103 | // ============================================================================================================================ 104 | // Generate Fitcoins for the user 105 | // Inputs - userId, transactionSteps 106 | // ============================================================================================================================ 107 | func (t *SimpleChaincode) generateFitcoins(stub shim.ChaincodeStubInterface, args []string) pb.Response { 108 | if len(args) != 2 { 109 | return shim.Error("Incorrect number of arguments") 110 | } 111 | var err error 112 | 113 | //get user_id and newSteps from args 114 | user_id := args[0] 115 | newTransactionSteps, err := strconv.Atoi(args[1]) 116 | if err != nil { 117 | return shim.Error(err.Error()) 118 | } 119 | 120 | //get user 121 | var user User 122 | userAsBytes, err := stub.GetState(user_id) 123 | if err != nil { 124 | return shim.Error("Failed to get user") 125 | } 126 | json.Unmarshal(userAsBytes, &user) 127 | if user.Type != TYPE_USER { 128 | return shim.Error("Not user type") 129 | } 130 | 131 | //update user account 132 | var newSteps = newTransactionSteps - user.StepsUsedForConversion 133 | if newSteps > STEPS_TO_FITCOIN { 134 | var newFitcoins = newSteps / STEPS_TO_FITCOIN 135 | var remainderSteps = newSteps % STEPS_TO_FITCOIN 136 | user.FitcoinsBalance = user.FitcoinsBalance + newFitcoins 137 | user.StepsUsedForConversion = newTransactionSteps - remainderSteps 138 | user.TotalSteps = newTransactionSteps 139 | 140 | //update users state 141 | updatedUserAsBytes, _ := json.Marshal(user) 142 | err = stub.PutState(user_id, updatedUserAsBytes) 143 | if err != nil { 144 | return shim.Error(err.Error()) 145 | } 146 | 147 | //return user info 148 | return shim.Success(updatedUserAsBytes) 149 | } 150 | 151 | return shim.Success(userAsBytes) 152 | 153 | } 154 | -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/chaincode/src/bcfit/product.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "strconv" 25 | 26 | "github.com/hyperledger/fabric/core/chaincode/shim" 27 | pb "github.com/hyperledger/fabric/protos/peer" 28 | ) 29 | 30 | // ============================================================================================================================ 31 | // Create product inventory for seller 32 | // Inputs - sellerId, productID, productName, productCount, productPrice 33 | // ============================================================================================================================ 34 | func (t *SimpleChaincode) createProduct(stub shim.ChaincodeStubInterface, args []string) pb.Response { 35 | 36 | return t.updateProduct(stub, args) 37 | } 38 | 39 | // ============================================================================================================================ 40 | // Update product inventory for seller 41 | // Inputs - sellerId, productID, newProductName, newProductCount, newProductPrice 42 | // ============================================================================================================================ 43 | func (t *SimpleChaincode) updateProduct(stub shim.ChaincodeStubInterface, args []string) pb.Response { 44 | if len(args) != 5 { 45 | return shim.Error("Incorrect number of arguments") 46 | } 47 | var err error 48 | 49 | //get sellerID from args 50 | seller_id := args[0] 51 | //get productID from args 52 | product_id := args[1] 53 | 54 | //get new product properties from args 55 | newProductName := args[2] 56 | newProductCount, err := strconv.Atoi(args[3]) 57 | if err != nil { 58 | return shim.Error("3rd argument 'productCount' must be a numeric string") 59 | } 60 | newProductPrice, err := strconv.Atoi(args[4]) 61 | if err != nil { 62 | return shim.Error("4th argument 'productPrice' must be a numeric string") 63 | } 64 | 65 | //get seller 66 | sellerAsBytes, err := stub.GetState(seller_id) 67 | if err != nil { 68 | return shim.Error("Failed to get seller") 69 | } 70 | var seller Seller 71 | json.Unmarshal(sellerAsBytes, &seller) 72 | if seller.Type != TYPE_SELLER { 73 | return shim.Error("Not seller type") 74 | } 75 | 76 | //find the product and update the properties 77 | productFound := false 78 | for h := 0; h < len(seller.Products); h++ { 79 | if seller.Products[h].Id == product_id { 80 | productFound = true 81 | seller.Products[h].Name = newProductName 82 | seller.Products[h].Count = newProductCount 83 | seller.Products[h].Price = newProductPrice 84 | break 85 | } 86 | } 87 | //if product not found return error 88 | if productFound != true { 89 | var product Product 90 | product.Id = product_id 91 | product.Name = newProductName 92 | product.Count = newProductCount 93 | product.Price = newProductPrice 94 | //append product 95 | seller.Products = append(seller.Products, product) 96 | } 97 | 98 | //update seller's state 99 | updatedSellerAsBytes, _ := json.Marshal(seller) 100 | err = stub.PutState(seller_id, updatedSellerAsBytes) 101 | if err != nil { 102 | return shim.Error(err.Error()) 103 | } 104 | 105 | //return seller info 106 | return shim.Success(updatedSellerAsBytes) 107 | 108 | } 109 | 110 | // ============================================================================================================================ 111 | // Get product inventory for seller 112 | // Inputs - sellerId, productID 113 | // ============================================================================================================================ 114 | func (t *SimpleChaincode) getProductByID(stub shim.ChaincodeStubInterface, args []string) pb.Response { 115 | if len(args) != 2 { 116 | return shim.Error("Incorrect number of arguments") 117 | } 118 | var err error 119 | 120 | //get sellerID, productID from args 121 | seller_id := args[0] 122 | product_id := args[1] 123 | 124 | //get seller 125 | sellerAsBytes, err := stub.GetState(seller_id) 126 | if err != nil { 127 | return shim.Error("Failed to get seller") 128 | } 129 | var seller Seller 130 | json.Unmarshal(sellerAsBytes, &seller) 131 | if seller.Type != TYPE_SELLER { 132 | return shim.Error("Not seller type") 133 | } 134 | 135 | //find the product 136 | var product Product 137 | productFound := false 138 | for h := 0; h < len(seller.Products); h++ { 139 | if seller.Products[h].Id == product_id { 140 | productFound = true 141 | product = seller.Products[h] 142 | break 143 | } 144 | } 145 | 146 | //if product not found return error 147 | if productFound != true { 148 | return shim.Error("Product not found") 149 | } 150 | 151 | //return product type 152 | productAsBytes, _ := json.Marshal(product) 153 | return shim.Success(productAsBytes) 154 | 155 | } 156 | 157 | // ============================================================================================================================ 158 | // Get all products for sale 159 | // Inputs - (none) 160 | // ============================================================================================================================ 161 | func (t *SimpleChaincode) getProductsForSale(stub shim.ChaincodeStubInterface, args []string) pb.Response { 162 | var err error 163 | 164 | //get sellers array 165 | sellerIdsBytes, err := stub.GetState("sellerIds") 166 | if err != nil { 167 | return shim.Error("Unable to get sellers.") 168 | } 169 | var sellerIds []string 170 | json.Unmarshal(sellerIdsBytes, &sellerIds) 171 | 172 | // create return object array 173 | type ReturnProductSale struct { 174 | SellerID string `json:"sellerid"` 175 | ProductId string `json:"productid"` 176 | Name string `json:"name"` 177 | Count int `json:"count"` 178 | Price int `json:"price"` 179 | } 180 | var returnProducts []ReturnProductSale 181 | 182 | //go through all sellerIDs 183 | for g := 0; g < len(sellerIds); g++ { 184 | 185 | //get seller 186 | sellerAsBytes, err := stub.GetState(sellerIds[g]) 187 | if err != nil { 188 | return shim.Error("Failed to get seller") 189 | } 190 | var seller Seller 191 | json.Unmarshal(sellerAsBytes, &seller) 192 | 193 | for h := 0; h < len(seller.Products); h++ { 194 | if seller.Products[h].Count > 0 { 195 | var returnProduct ReturnProductSale 196 | returnProduct.SellerID = seller.Id 197 | returnProduct.ProductId = seller.Products[h].Id 198 | returnProduct.Name = seller.Products[h].Name 199 | returnProduct.Count = seller.Products[h].Count 200 | returnProduct.Price = seller.Products[h].Price 201 | //append to array 202 | returnProducts = append(returnProducts, returnProduct) 203 | } 204 | } 205 | } 206 | 207 | //return products for sole 208 | returnProductsBytes, _ := json.Marshal(returnProducts) 209 | return shim.Success(returnProductsBytes) 210 | } 211 | -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/chaincode/src/bcfit/secret_map.go: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | 26 | "github.com/hyperledger/fabric/core/chaincode/shim" 27 | pb "github.com/hyperledger/fabric/protos/peer" 28 | ) 29 | 30 | //steps to fitcoin constant 31 | const STEPS_TO_FITCOIN = 100 32 | 33 | //contract state 34 | const STATE_COMPLETE = "complete" 35 | const STATE_PENDING = "pending" 36 | const STATE_DECLINED = "declined" 37 | 38 | //member type 39 | const TYPE_USER = "user" 40 | const TYPE_SELLER = "seller" 41 | 42 | // SimpleChaincode example simple Chaincode implementation 43 | type SimpleChaincode struct { 44 | } 45 | 46 | // Member object for participants 47 | type Member struct { 48 | Id string `json:"id"` 49 | Type string `json:"memberType"` 50 | FitcoinsBalance int `json:"fitcoinsBalance"` 51 | } 52 | 53 | // User 54 | type User struct { 55 | Member 56 | TotalSteps int `json:"totalSteps"` 57 | StepsUsedForConversion int `json:"stepsUsedForConversion"` 58 | ContractIds []string `json:"contractIds"` 59 | } 60 | 61 | // Seller 62 | type Seller struct { 63 | Member 64 | Products []Product `json:"products"` 65 | } 66 | 67 | // Product 68 | type Product struct { 69 | Id string `json:"id"` 70 | Name string `json:"name"` 71 | Count int `json:"count"` 72 | Price int `json:"price"` 73 | } 74 | 75 | // Contract 76 | type Contract struct { 77 | Id string `json:"id"` 78 | SellerId string `json:"sellerId"` 79 | UserId string `json:"userId"` 80 | ProductId string `json:"productId"` 81 | ProductName string `json:"productName"` 82 | Quantity int `json:"quantity"` 83 | Cost int `json:"cost"` 84 | State string `json:"state"` 85 | } 86 | 87 | // ============================================================================================================================ 88 | // Main 89 | // ============================================================================================================================ 90 | func main() { 91 | err := shim.Start(new(SimpleChaincode)) 92 | if err != nil { 93 | fmt.Printf("Error starting chaincode: %s", err) 94 | } 95 | } 96 | 97 | // ============================================================================================================================ 98 | // Init - initialize the chaincode 99 | // ============================================================================================================================ 100 | func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { 101 | 102 | //store sellerIds 103 | var sellerIds []string 104 | sellerIdsBytes, err := json.Marshal(sellerIds) 105 | if err != nil { 106 | return shim.Error("Error initializing sellers.") 107 | } 108 | err = stub.PutState("sellerIds", sellerIdsBytes) 109 | 110 | return shim.Success(nil) 111 | } 112 | 113 | // ============================================================================================================================ 114 | // Invoke - Our entry point for Invocations 115 | // ============================================================================================================================ 116 | func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 117 | function, args := stub.GetFunctionAndParameters() 118 | fmt.Println(" ") 119 | fmt.Println("starting invoke, for - " + function) 120 | 121 | //call functions 122 | if function == "createMember" { 123 | return t.createMember(stub, args) 124 | } else if function == "generateFitcoins" { 125 | return t.generateFitcoins(stub, args) 126 | } else if function == "getState" { 127 | return t.getState(stub, args) 128 | } else if function == "createProduct" { 129 | return t.createProduct(stub, args) 130 | } else if function == "updateProduct" { 131 | return t.updateProduct(stub, args) 132 | } else if function == "getProductByID" { 133 | return t.getProductByID(stub, args) 134 | } else if function == "getProductsForSale" { 135 | return t.getProductsForSale(stub, args) 136 | } else if function == "makePurchase" { 137 | return t.makePurchase(stub, args) 138 | } else if function == "transactPurchase" { 139 | return t.transactPurchase(stub, args) 140 | } else if function == "getAllUserContracts" { 141 | return t.getAllUserContracts(stub, args) 142 | } else if function == "getAllContracts" { 143 | return t.getAllContracts(stub, args) 144 | } 145 | 146 | return shim.Error("Function with the name " + function + " does not exist.") 147 | } 148 | 149 | // ============================================================================================================================ 150 | // Get state with userId, sellerID, contractID 151 | // Inputs - id 152 | // ============================================================================================================================ 153 | func (t *SimpleChaincode) getState(stub shim.ChaincodeStubInterface, args []string) pb.Response { 154 | if len(args) != 1 { 155 | return shim.Error("Incorrect number of arguments") 156 | } 157 | 158 | //get id 159 | id := args[0] 160 | 161 | // Get the state from the ledger 162 | dataAsBytes, err := stub.GetState(id) 163 | if err != nil { 164 | return shim.Error("Failed to get state") 165 | } 166 | 167 | //return user info 168 | return shim.Success(dataAsBytes) 169 | } 170 | -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./set-up/setup.js'); -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blockchain", 3 | "version": "1.0.0", 4 | "description": "Setup Blockchain for fitcoin network", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "babel-core": "^6.26.0", 13 | "babel-polyfill": "^6.26.0", 14 | "babel-preset-env": "^1.6.1", 15 | "events": "^1.1.1", 16 | "fabric-ca-client": "latest", 17 | "fabric-client": "latest", 18 | "fs": "0.0.1-security", 19 | "fs-extra": "^5.0.0", 20 | "grpc": "^1.8.0", 21 | "http": "0.0.0", 22 | "json-style-converter": "^1.0.3", 23 | "long": "^3.2.0", 24 | "nano": "^6.4.2", 25 | "path": "^0.12.7", 26 | "url": "^0.11.0", 27 | "util": "^0.10.3" 28 | } 29 | } -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/set-up/CouchDBKeyValueStore.js: -------------------------------------------------------------------------------- 1 | /*eslint no-unused-vars: 0*/ 2 | /* 3 | Copyright 2016 IBM All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the 'License'); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an 'AS IS' BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 'use strict'; 18 | var api = require('fabric-client/lib/api.js'); 19 | //var fs = require('fs-extra'); 20 | //var path = require('path'); 21 | var util = require('util'); 22 | var utils = require('fabric-client/lib/utils.js'); 23 | var nano = require('nano'); 24 | var logger = utils.getLogger('CouchDBKeyValueStore.js'); 25 | /** 26 | * This is a sample database implementation of the [KeyValueStore]{@link module:api.KeyValueStore} API. 27 | * It uses a local or remote CouchDB database instance to store the keys. 28 | * 29 | * @class 30 | * @extends module:api.KeyValueStore 31 | */ 32 | var CouchDBKeyValueStore = class extends api.KeyValueStore { 33 | /** 34 | * @typedef {Object} CouchDBOpts 35 | * @property {string} url The CouchDB instance url, in the form of http(s)://:@host:port 36 | * @property {string} name Optional. Identifies the name of the database to use. Default: member_db. 37 | */ 38 | /** 39 | * constructor 40 | * 41 | * @param {CouchDBOpts} options Settings used to connect to a CouchDB instance 42 | */ 43 | constructor(options) { 44 | logger.debug('constructor', { 45 | options: options 46 | }); 47 | if(!options || !options.url) { 48 | throw new Error('Must provide the CouchDB database url to store membership data.'); 49 | } 50 | // Create the keyValStore instance 51 | super(); 52 | var self = this; 53 | // url is the database instance url 54 | this._url = options.url; 55 | // Name of the database, optional 56 | if(!options.name) { 57 | this._name = 'member_db'; 58 | } else { 59 | this._name = options.name; 60 | } 61 | return new Promise(function (resolve, reject) { 62 | // Initialize the CouchDB database client 63 | var dbClient = nano(self._url); 64 | // Check if the database already exists. If not, create it. 65 | dbClient.db.get(self._name, function (err, body) { 66 | // Check for error 67 | if(err) { 68 | // Database doesn't exist 69 | if(err.error == 'not_found') { 70 | logger.debug('No %s found, creating %s', self._name, self._name); 71 | dbClient.db.create(self._name, function (err, body) { 72 | if(err) { 73 | return reject(new Error(util.format('Failed to create %s database due to error: %s', self._name, err.stack ? err.stack : err))); 74 | } 75 | logger.debug('Created %s database', self._name); 76 | // Specify it as the database to use 77 | self._database = dbClient.use(self._name); 78 | resolve(self); 79 | }); 80 | } else { 81 | // Other error 82 | return reject(new Error(util.format('Error creating %s database to store membership data: %s', self._name, err.stack ? err.stack : err))); 83 | } 84 | } else { 85 | // Database exists 86 | logger.debug('%s already exists', self._name); 87 | // Specify it as the database to use 88 | self._database = dbClient.use(self._name); 89 | resolve(self); 90 | } 91 | }); 92 | }); 93 | } 94 | getValue(name) { 95 | logger.debug('getValue', { 96 | key: name 97 | }); 98 | var self = this; 99 | return new Promise(function (resolve, reject) { 100 | self._database.get(name, function (err, body) { 101 | // Check for error on retrieving from database 102 | if(err) { 103 | if(err.error !== 'not_found') { 104 | logger.error('getValue: %s, ERROR: [%s.get] - ', name, self._name, err.error); 105 | return reject(err.error); 106 | } else { 107 | logger.debug('getValue: %s, Entry does not exist', name); 108 | return resolve(null); 109 | } 110 | } else { 111 | logger.debug('getValue: %s, Retrieved message from %s.', name, self._name); 112 | return resolve(body.member); 113 | } 114 | }); 115 | }); 116 | } 117 | setValue(name, value) { 118 | logger.debug('setValue', { 119 | key: name 120 | }); 121 | var self = this; 122 | return new Promise(function (resolve, reject) { 123 | // Attempt to retrieve from the database to see if the entry exists 124 | self._database.get(name, function (err, body) { 125 | // Check for error on retrieving from database 126 | if(err) { 127 | if(err.error !== 'not_found') { 128 | logger.error('setValue: %s, ERROR: [%s.get] - ', name, self._name, err.error); 129 | reject(err.error); 130 | } else { 131 | // Entry does not exist 132 | logger.debug('setValue: %s, Entry does not exist, insert it.', name); 133 | self._dbInsert({ 134 | _id: name, 135 | member: value 136 | }).then(function (status) { 137 | logger.debug('setValue add: ' + name + ', status: ' + status); 138 | resolve(value); 139 | //if(status == true) resolve(value); 140 | //else reject(new Error('Couch database insert add failed.')); 141 | }).catch(err => { 142 | reject(new Error('Couch database insert update failed - ' + err.message)); 143 | }); 144 | } 145 | } else { 146 | // Entry already exists and must be updated 147 | // Update the database entry using the latest rev number 148 | logger.debug('setValue: %s, Retrieved entry from %s. Latest rev number: %s', name, self._name, body._rev); 149 | self._dbInsert({ 150 | _id: name, 151 | _rev: body._rev, 152 | member: value 153 | }).then(function (status) { 154 | logger.debug('setValue update: ' + name + ', status: ' + status); 155 | resolve(value); 156 | //if(status == true) resolve(value); 157 | //else reject(new Error('Couch database insert add failed.')); 158 | }).catch(err => { 159 | reject(new Error('Couch database insert update failed - ' + err.message)); 160 | }); 161 | } 162 | }); 163 | }); 164 | } 165 | _dbInsert(options) { 166 | logger.debug('setValue, _dbInsert', { 167 | options: options 168 | }); 169 | var self = this; 170 | return new Promise(function (resolve, reject) { 171 | self._database.insert(options, function (err, body, header) { 172 | if(err) { 173 | logger.error('setValue, _dbInsert, ERROR: [%s.insert] - ', self._name, err.error); 174 | reject(new Error(err.error)); 175 | } else { 176 | logger.debug('setValue, _dbInsert, Inserted member into %s.', self._name); 177 | resolve(true); 178 | } 179 | }); 180 | }); 181 | } 182 | }; 183 | module.exports = CouchDBKeyValueStore; -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/set-up/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Long = require('long'); 3 | var resolve = require('path').resolve; 4 | var EventEmitter = require('events').EventEmitter; 5 | var hfc = require('fabric-client'); 6 | var CAClient = require('fabric-ca-client'); 7 | var CouchDBKeyValueStore = require('./CouchDBKeyValueStore.js'); 8 | var CKS = require('fabric-client/lib/impl/CryptoKeyStore.js'); 9 | import enrollUser from './enroll'; 10 | var utils = require('./utils.js'); 11 | process.env.GOPATH = resolve(__dirname, '../chaincode'); 12 | const JOIN_TIMEOUT = 120000, 13 | TRANSACTION_TIMEOUT = 120000; 14 | export class OrganizationClient extends EventEmitter { 15 | constructor(channelName, ordererConfig, peerConfig, caConfig, admin) { 16 | super(); 17 | this._channelName = channelName; 18 | this._ordererConfig = ordererConfig; 19 | this._peerConfig = peerConfig; 20 | this._caConfig = caConfig; 21 | this._admin = admin; 22 | this._peers = []; 23 | this._eventHubs = []; 24 | this._client = new hfc(); 25 | // Setup channel 26 | this._channel = this._client.newChannel(channelName); 27 | // Setup orderer and peers 28 | const orderer = this._client.newOrderer(ordererConfig.url, { 29 | pem: ordererConfig.pem, 30 | 'ssl-target-name-override': ordererConfig.hostname 31 | }); 32 | this._channel.addOrderer(orderer); 33 | const defaultPeer = this._client.newPeer(peerConfig.url, { 34 | pem: peerConfig.pem, 35 | 'ssl-target-name-override': peerConfig.hostname 36 | }); 37 | this._peers.push(defaultPeer); 38 | this._channel.addPeer(defaultPeer); 39 | this._adminUser = null; 40 | this._ca = null; 41 | } 42 | async login() { 43 | try { 44 | await this._client.setStateStore(await CKS(CouchDBKeyValueStore, { 45 | name: this._peerConfig.stateDBName, 46 | url: this._peerConfig.stateDBUrl 47 | })); 48 | var crypto_suite = hfc.newCryptoSuite(); 49 | var crypto_store = hfc.newCryptoKeyStore(CouchDBKeyValueStore, { 50 | name: this._peerConfig.userKeystoreDBName, 51 | url: this._peerConfig.userKeystoreDBUrl 52 | }); 53 | crypto_suite.setCryptoKeyStore(crypto_store); 54 | var client_crypto_suite = hfc.newCryptoSuite(); 55 | var client_crypto_store = hfc.newCryptoKeyStore(CouchDBKeyValueStore, { 56 | name: this._peerConfig.userKeystoreDBName, 57 | url: this._peerConfig.userKeystoreDBUrl 58 | }); 59 | client_crypto_suite.setCryptoKeyStore(client_crypto_store); 60 | this._client.setCryptoSuite(client_crypto_suite); 61 | this._ca = await new CAClient(this._caConfig.url, { 62 | trustedRoots: [], 63 | verify: false 64 | }, this._caConfig.caName, crypto_suite); 65 | console.log("CA registration complete "); 66 | this._adminUser = await enrollUser(this._client, "admin", "adminpw", this._ca, { 67 | mspId: this._caConfig.mspId, 68 | adminUser: null, 69 | affiliationOrg: this._peerConfig.org, 70 | noOfAttempts: 5 71 | }); 72 | //await this._client.setUserContext(this._adminUser); 73 | //await this.createOrgAdmin(); 74 | } catch(e) { 75 | console.log(`Failed to enroll user. Error: ${e.message}`); 76 | throw e; 77 | } 78 | } 79 | initEventHubs() { 80 | // Setup event hubs 81 | try { 82 | const defaultEventHub = this._client.newEventHub(); 83 | defaultEventHub.setPeerAddr(this._peerConfig.eventHubUrl, { 84 | pem: this._peerConfig.pem, 85 | 'ssl-target-name-override': this._peerConfig.hostname 86 | }); 87 | defaultEventHub.connect(); 88 | defaultEventHub.registerBlockEvent(block => { 89 | this.emit('block', utils.unmarshalBlock(block)); 90 | }); 91 | this._eventHubs.push(defaultEventHub); 92 | } catch(e) { 93 | console.log(`Failed to configure event hubs. Error ${e.message}`); 94 | throw e; 95 | } 96 | } 97 | async createOrgAdmin() { 98 | return this._client.createUser({ 99 | username: `Admin@${this._peerConfig.hostname}`, 100 | mspid: this._caConfig.mspId, 101 | cryptoContent: { 102 | privateKeyPEM: this._admin.key, 103 | signedCertPEM: this._admin.cert 104 | } 105 | }); 106 | } 107 | async initialize() { 108 | try { 109 | await this._channel.initialize(); 110 | } catch(e) { 111 | console.log(`Failed to initialize chain. Error: ${e.message}`); 112 | throw e; 113 | } 114 | } 115 | async createChannel(envelope) { 116 | const txId = this._client.newTransactionID(); 117 | const channelConfig = this._client.extractChannelConfig(envelope); 118 | const signature = this._client.signChannelConfig(channelConfig); 119 | const request = { 120 | name: this._channelName, 121 | orderer: this._channel.getOrderers()[0], 122 | config: channelConfig, 123 | signatures: [signature], 124 | txId 125 | }; 126 | const response = await this._client.createChannel(request); 127 | // Wait for 5sec to create channel 128 | //console.log("channel log "); 129 | //console.log(response); 130 | await new Promise(resolve => { 131 | setTimeout(resolve, 5000); 132 | }); 133 | return response; 134 | } 135 | async joinChannel() { 136 | try { 137 | const genesisBlock = await this._channel.getGenesisBlock({ 138 | txId: this._client.newTransactionID() 139 | }); 140 | const request = { 141 | targets: this._peers, 142 | txId: this._client.newTransactionID(), 143 | block: genesisBlock 144 | }; 145 | const joinedChannelPromises = this._eventHubs.map(eh => { 146 | eh.connect(); 147 | return new Promise((resolve, reject) => { 148 | let blockRegistration; 149 | const cb = block => { 150 | clearTimeout(responseTimeout); 151 | eh.unregisterBlockEvent(blockRegistration); 152 | if(block.data.data.length === 1) { 153 | const channelHeader = block.data.data[0].payload.header.channel_header; 154 | if(channelHeader.channel_id === this._channelName) { 155 | resolve(); 156 | } else { 157 | reject(new Error('Peer did not join an expected channel.')); 158 | } 159 | } 160 | }; 161 | blockRegistration = eh.registerBlockEvent(cb); 162 | const responseTimeout = setTimeout(() => { 163 | eh.unregisterBlockEvent(blockRegistration); 164 | reject(new Error('Peer did not respond in a timely fashion!')); 165 | }, JOIN_TIMEOUT); 166 | }); 167 | }); 168 | const completedPromise = joinedChannelPromises.concat([ 169 | this._channel.joinChannel(request) 170 | ]); 171 | await Promise.all(completedPromise); 172 | } catch(e) { 173 | console.log(`Error joining peer to channel. Error: ${e.message}`); 174 | throw e; 175 | } 176 | } 177 | async checkChannelMembership() { 178 | try { 179 | const { 180 | channels 181 | } = await this._client.queryChannels(this._peers[0]); 182 | if(!Array.isArray(channels)) { 183 | return false; 184 | } 185 | return channels.some(({ 186 | channel_id 187 | }) => channel_id === this._channelName); 188 | } catch(e) { 189 | return false; 190 | } 191 | } 192 | async checkInstalled(chaincodeId, chaincodeVersion, chaincodePath) { 193 | let { 194 | chaincodes 195 | } = await this._channel.queryInstantiatedChaincodes(); 196 | if(!Array.isArray(chaincodes)) { 197 | return false; 198 | } 199 | return chaincodes.some(cc => cc.name === chaincodeId && cc.path === chaincodePath && cc.version === chaincodeVersion); 200 | } 201 | async install(chaincodeId, chaincodeVersion, chaincodePath) { 202 | const request = { 203 | targets: this._peers, 204 | chaincodePath, 205 | chaincodeId, 206 | chaincodeVersion 207 | }; 208 | // Make install proposal to all peers 209 | let results; 210 | try { 211 | results = await this._client.installChaincode(request); 212 | } catch(e) { 213 | console.log(`Error sending install proposal to peer! Error: ${e.message}`); 214 | throw e; 215 | } 216 | const proposalResponses = results[0]; 217 | const allGood = proposalResponses.every(pr => pr.response && pr.response.status == 200); 218 | return allGood; 219 | } 220 | async instantiate(chaincodeId, chaincodeVersion, chaincodePath, ...args) { 221 | let proposalResponses, proposal; 222 | const txId = this._client.newTransactionID(); 223 | try { 224 | const request = { 225 | chaincodeType: 'golang', 226 | chaincodePath, 227 | chaincodeId, 228 | chaincodeVersion, 229 | fcn: 'init', 230 | args: utils.marshalArgs(args), 231 | txId 232 | }; 233 | const results = await this._channel.sendInstantiateProposal(request, 100000); 234 | proposalResponses = results[0]; 235 | proposal = results[1]; 236 | let allGood = proposalResponses.every(pr => pr.response && pr.response.status == 200); 237 | if(!allGood) { 238 | throw new Error(`Proposal rejected by some (all) of the peers: ${proposalResponses}`); 239 | } 240 | } catch(e) { 241 | throw e; 242 | } 243 | try { 244 | const request = { 245 | proposalResponses, 246 | proposal 247 | }; 248 | const deployId = txId.getTransactionID(); 249 | const transactionCompletePromises = this._eventHubs.map(eh => { 250 | eh.connect(); 251 | return new Promise((resolve, reject) => { 252 | // Set timeout for the transaction response from the current peer 253 | const responseTimeout = setTimeout(() => { 254 | eh.unregisterTxEvent(deployId); 255 | reject(new Error('Peer did not respond in a timely fashion!')); 256 | }, TRANSACTION_TIMEOUT); 257 | eh.registerTxEvent(deployId, (tx, code) => { 258 | clearTimeout(responseTimeout); 259 | eh.unregisterTxEvent(deployId); 260 | if(code != 'VALID') { 261 | reject(new Error(`Peer has rejected transaction with code: ${code}`)); 262 | } else { 263 | resolve(); 264 | } 265 | }); 266 | }); 267 | }); 268 | transactionCompletePromises.push(this._channel.sendTransaction(request)); 269 | await transactionCompletePromises; 270 | } catch(e) { 271 | throw e; 272 | } 273 | } 274 | async getBlocks(currentBlock, noOfLastBlocks) { 275 | if(currentBlock === 0) { 276 | return []; 277 | } 278 | if(!currentBlock) { 279 | currentBlock = -1; 280 | } 281 | if(!noOfLastBlocks) { 282 | noOfLastBlocks = 10; 283 | } 284 | currentBlock = typeof currentBlock !== 'number' ? Number(currentBlock) : currentBlock; 285 | noOfLastBlocks = typeof noOfLastBlocks !== 'number' ? Number(noOfLastBlocks) : noOfLastBlocks; 286 | var { 287 | height 288 | } = await this._channel.queryInfo(); 289 | if(currentBlock == -1) { 290 | currentBlock = height; 291 | } 292 | if(height.comp(currentBlock) >= 0) { 293 | height = Long.fromNumber(currentBlock, height.unsigned); 294 | } 295 | let blockCount; 296 | if(height.comp(noOfLastBlocks) > 0) { 297 | blockCount = Long.fromNumber(noOfLastBlocks, height.unsigned); 298 | } else { 299 | blockCount = height; 300 | } 301 | blockCount = blockCount.toNumber(); 302 | const queryBlock = this._channel.queryBlock.bind(this._channel); 303 | const blockPromises = {}; 304 | blockPromises[Symbol.iterator] = function* () { 305 | for(let i = 1; i <= blockCount; i++) { 306 | yield queryBlock(height.sub(i).toNumber()); 307 | } 308 | }; 309 | const blocks = await Promise.all([...blockPromises]); 310 | return blocks.map(utils.unmarshalBlock); 311 | } 312 | async registerAndEnroll(enrollmentID) { 313 | try { 314 | if(!enrollmentID && enrollmentID === "") { 315 | throw new Error(`Invalid User Id`); 316 | } 317 | let adminUser = await this._client.getUserContext('admin', true); 318 | //let adminUser = this._adminUser; 319 | if(!adminUser && !adminUser.isEnrolled()) { 320 | throw new Error(`Admin user not present to register user : ` + enrollmentID); 321 | } 322 | return enrollUser(this._client, enrollmentID, "", this._ca, { 323 | mspId: this._caConfig.mspId, 324 | adminUser: adminUser, 325 | affiliationOrg: this._peerConfig.org, 326 | noOfAttempts: 3 327 | }); 328 | } catch(e) { 329 | throw e; 330 | } 331 | } 332 | async getTransactionDetails(txId) { 333 | try { 334 | var transactionData = await this._channel.queryTransaction(txId); 335 | transactionData = transactionData ? transactionData.transactionEnvelope.payload.data.actions : ""; 336 | var execution_response = transactionData !== "" ? transactionData[0].payload.action.proposal_response_payload.extension.response : ""; 337 | return { 338 | txId: txId, 339 | results: execution_response 340 | }; 341 | } catch(e) { 342 | throw e; 343 | } 344 | } 345 | } -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/set-up/config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const SECRETS_DIR = process.env.SECRETSDIR || '/run/secrets'; 4 | 5 | function readConfig() { 6 | if(fs.existsSync(SECRETS_DIR)) { 7 | const data = JSON.parse(fs.readFileSync(path.resolve(SECRETS_DIR, 'config')).toString()); 8 | data.channelConfig = fs.readFileSync(path.resolve(SECRETS_DIR, 'channel')); 9 | return data; 10 | } 11 | } 12 | const config = readConfig(); 13 | export default config; -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/set-up/enroll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enrolls a user with the respective CA. 3 | */ 4 | var User = require('fabric-client/lib/User'); 5 | export default async function (client, enrollmentID, enrollmentSecret, ca, { 6 | mspId, 7 | adminUser, 8 | affiliationOrg, 9 | noOfAttempts 10 | }) { 11 | try { 12 | if(!enrollmentID && enrollmentID === "") { 13 | throw new Error(`Invalid User Id`); 14 | } 15 | var getUser = async function (client, userId, count) { 16 | try { 17 | return client.getUserContext(userId, true); 18 | } catch(e) { 19 | if(count > noOfAttempts) { 20 | count++; 21 | await new Promise(res => setTimeout(() => res(), 1000)); 22 | return getUser(client, userId, count); 23 | } else { 24 | return null; 25 | } 26 | } 27 | }; 28 | let user = await getUser(client, enrollmentID, 1); 29 | if(user && user.isEnrolled()) { 30 | return user; 31 | } 32 | try { 33 | if(!enrollmentSecret || enrollmentSecret === "") { 34 | //console.log('Initiate member ' + enrollmentID + " registration to " + affiliationOrg); 35 | enrollmentSecret = await ca.register({ 36 | enrollmentID: enrollmentID, 37 | affiliation: affiliationOrg, 38 | maxEnrollments: 1, 39 | role: 'client' 40 | }, adminUser); 41 | //console.log("Successfully registered user " + enrollmentID + " with secret " + enrollmentSecret); 42 | } 43 | } catch(e) { 44 | throw new Error(`Failed to register User. Error: ${e.message}`); 45 | } 46 | try { 47 | const enrollment = await ca.enroll({ 48 | enrollmentID, 49 | enrollmentSecret 50 | }); 51 | user = new User(enrollmentID, client); 52 | await user.setEnrollment(enrollment.key, enrollment.certificate, mspId); 53 | await client.setUserContext(user); 54 | return user; 55 | } catch(e) { 56 | throw new Error(`Failed to enroll and persist User. Error: ${e.message}`); 57 | } 58 | } catch(e) { 59 | throw new Error(`Could not get UserContext! Error: ${e.message}`); 60 | } 61 | } -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/set-up/invoke.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const TRANSACTION_TIMEOUT = 120000; 3 | export default async function (userId, clientObject, chaincodeId, chaincodeVersion, fcn, args) { 4 | var Transaction = require('fabric-client/lib/TransactionID.js'); 5 | //var user_from_store = await clientObject._client.getUserContext(userId, true); 6 | var getUser = async function (clientObject, userId, count) { 7 | try { 8 | var user = await clientObject._client.getUserContext(userId, true); 9 | return user; 10 | } catch(e) { 11 | if(count > 2) { 12 | count++; 13 | return getUser(clientObject, userId, count); 14 | } else { 15 | throw new Error('Failed to get user : ' + userId + ' from persistence. Error: ' + e.message); 16 | } 17 | } 18 | }; 19 | var user_from_store = await getUser(clientObject, userId, 1); 20 | if(!(user_from_store && user_from_store.isEnrolled())) { 21 | throw new Error('Failed to get user : ' + userId + ' from persistence'); 22 | } 23 | //console.log('Successfully loaded user : ' + userId + ' from persistence'); 24 | let proposalResponses, proposal; 25 | const txId = new Transaction(user_from_store); 26 | try { 27 | const request = { 28 | chaincodeId: chaincodeId, 29 | chaincodeVersion: chaincodeVersion, 30 | fcn: fcn, 31 | args: args, 32 | chainId: clientObject._channelName, 33 | txId: txId 34 | }; 35 | const results = await clientObject._channel.sendTransactionProposal(request); 36 | proposalResponses = results[0]; 37 | proposal = results[1]; 38 | const allGood = proposalResponses.every(pr => pr.response && pr.response.status == 200); 39 | if(!allGood) { 40 | throw new Error(`Proposal rejected by some (all) of the peers: ${proposalResponses}`); 41 | } 42 | } catch(e) { 43 | throw e; 44 | } 45 | try { 46 | const request = { 47 | proposalResponses, 48 | proposal 49 | }; 50 | var transaction_id_string = txId.getTransactionID(); //Get the transaction ID string to be used by the event processing 51 | var promises = []; 52 | var sendPromise = clientObject._channel.sendTransaction(request); 53 | promises.push(sendPromise); 54 | let event_hub = clientObject._eventHubs[0]; 55 | let txPromise = new Promise((resolve, reject) => { 56 | let handle = setTimeout(() => { 57 | event_hub.disconnect(); 58 | resolve({ 59 | event_status: 'TIMEOUT' 60 | }); 61 | }, TRANSACTION_TIMEOUT); 62 | var connectHub = async function (event_hub, count) { 63 | event_hub.connect(); 64 | event_hub.registerTxEvent(transaction_id_string, (tx, code) => { 65 | // this is the callback for transaction event status 66 | // first some clean up of event listener 67 | clearTimeout(handle); 68 | event_hub.unregisterTxEvent(transaction_id_string); 69 | event_hub.disconnect(); 70 | // now let the application know what happened 71 | resolve({ 72 | event_status: code, 73 | tx_id: transaction_id_string 74 | }); 75 | //return return_status; 76 | }, (err) => { 77 | //clearTimeout(handle); 78 | count++; 79 | if(count > 2) { 80 | clearTimeout(handle); 81 | reject(new Error('There was a problem with the eventhub ::' + err)); 82 | } else { 83 | connectHub(event_hub, count); 84 | } 85 | }); 86 | } 87 | connectHub(event_hub, 1); 88 | }); 89 | promises.push(txPromise); 90 | var results = await Promise.all(promises); 91 | //console.log('Send transaction promise and event listener promise have completed'); 92 | // check the results in the order the promises were added to the promise all list 93 | if(!(results && results[0] && results[0].status === 'SUCCESS')) { 94 | //console.log('Successfully sent transaction to the orderer.'); 95 | throw new Error('Failed to order the transaction. Error code: ' + results.status); 96 | } 97 | if(results && results[1] && results[1].event_status === 'VALID') { 98 | console.log('Successfully committed the change to the ledger by the peer'); 99 | return results[1].tx_id; 100 | } else { 101 | throw new Error('Transaction failed to be committed to the ledger due to ::' + results[1].event_status); 102 | } 103 | } catch(e) { 104 | throw e; 105 | } 106 | } -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/set-up/query.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | export default async function (userId, clientObject, chaincodeId, chaincodeVersion, fcn, args) { 3 | try { 4 | //var user_from_store = await clientObject._client.getUserContext(userId, true); 5 | var getUser = async function (clientObject, userId, count) { 6 | try { 7 | return clientObject._client.getUserContext(userId, true); 8 | } catch(e) { 9 | if(count > 2) { 10 | count++; 11 | return getUser(clientObject, userId, count); 12 | } else { 13 | throw new Error('Failed to get user : ' + userId + ' from persistence. Error: ' + e.message); 14 | } 15 | } 16 | }; 17 | var user_from_store = await getUser(clientObject, userId, 1); 18 | if(!(user_from_store && user_from_store.isEnrolled())) { 19 | throw new Error('Failed to get user : ' + userId + ' from persistence'); 20 | } 21 | //console.log('Successfully loaded user : ' + userId + ' from persistence'); 22 | const request = { 23 | //targets : --- letting this default to the peers assigned to the channel 24 | chaincodeId: chaincodeId, 25 | chaincodeVersion: chaincodeVersion, 26 | fcn: fcn, 27 | args: args 28 | }; 29 | const query_responses = await clientObject._channel.queryByChaincode(request, user_from_store); 30 | //console.log("Query has completed, checking results"); 31 | // query_responses could have more than one results if there multiple peers were used as targets 32 | if(query_responses && query_responses.length == 1) { 33 | if(query_responses[0] instanceof Error) { 34 | throw new Error("Error from query = ", query_responses[0].message); 35 | } else { 36 | return query_responses.toString('utf8'); 37 | } 38 | } else { 39 | throw new Error("No payloads were returned from query"); 40 | } 41 | } catch(e) { 42 | throw e; 43 | } 44 | } -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/set-up/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import config from './config'; 3 | import { 4 | OrganizationClient 5 | } from './client'; 6 | import http from 'http'; 7 | import url from 'url'; 8 | // Setup clients per organization 9 | const clients = config.peers.map(obj => new OrganizationClient(config.channelName, config.orderer, obj.peer, obj.ca, obj.admin)); 10 | 11 | function getAdminOrgs() { 12 | return Promise.all(clients.map(client => client.createOrgAdmin())); 13 | } 14 | (async () => { 15 | try { 16 | await Promise.all(clients.map(client => client.login())); 17 | } catch(e) { 18 | console.log('Fatal error logging into blockchain organization clients!'); 19 | console.log(e); 20 | process.exit(-1); 21 | } 22 | // Setup event hubs 23 | clients.map(client => client.initEventHubs()); 24 | try { 25 | await getAdminOrgs(); 26 | if(!(await clients[0].checkChannelMembership())) { 27 | console.log('Default channel not found, attempting creation...'); 28 | const createChannelResponse = await clients[0].createChannel(config.channelConfig); 29 | if(createChannelResponse.status === 'SUCCESS') { 30 | console.log('Successfully created a new default channel.'); 31 | console.log('Joining peers to the default channel.'); 32 | await Promise.all(clients.map(client => client.joinChannel())); 33 | // Wait for 10s for the peers to join the newly created channel 34 | await new Promise(resolve => { 35 | setTimeout(resolve, 10000); 36 | }); 37 | } 38 | } 39 | } catch(e) { 40 | console.log('Fatal error bootstrapping the blockchain network!'); 41 | console.log(e); 42 | process.exit(-1); 43 | } 44 | // Initialize network 45 | try { 46 | await Promise.all(clients.map(client => client.initialize())); 47 | } catch(e) { 48 | console.log('Fatal error initializing blockchain organization clients!'); 49 | console.log(e); 50 | process.exit(-1); 51 | } 52 | // Install chaincode on all peers 53 | let installedOnClients; 54 | try { 55 | await getAdminOrgs(); 56 | installedOnClients = await Promise.all(clients.map(client => client.checkInstalled(config.chaincodeId, config.chaincodeVersion, config.chaincodePath))); 57 | } catch(e) { 58 | console.log('Fatal error getting installation status of the chaincode!'); 59 | console.log(e); 60 | process.exit(-1); 61 | } 62 | let check = installedOnClients.every(installed => installed); 63 | if(!(check)) { 64 | console.log('Chaincode is not installed, attempting installation...'); 65 | // Pull chaincode environment base image 66 | try { 67 | await getAdminOrgs(); 68 | const socketPath = process.env.DOCKER_SOCKET_PATH || (process.platform === 'win32' ? '//./pipe/docker_engine' : '/var/run/docker.sock'); 69 | const ccenvImage = process.env.DOCKER_CCENV_IMAGE || 'hyperledger/fabric-ccenv:x86_64-1.1.0'; 70 | const listOpts = { 71 | socketPath, 72 | method: 'GET', 73 | path: '/images/json' 74 | }; 75 | const pullOpts = { 76 | socketPath, 77 | method: 'POST', 78 | path: url.format({ 79 | pathname: '/images/create', 80 | query: { 81 | fromImage: ccenvImage 82 | } 83 | }) 84 | }; 85 | const images = await new Promise((resolve, reject) => { 86 | const req = http.request(listOpts, (response) => { 87 | let data = ''; 88 | response.setEncoding('utf-8'); 89 | response.on('data', chunk => { 90 | data += chunk; 91 | }); 92 | response.on('end', () => { 93 | resolve(JSON.parse(data)); 94 | }); 95 | }); 96 | req.on('error', reject); 97 | req.end(); 98 | }); 99 | const imageExists = images.some(i => i.RepoTags && i.RepoTags.some(tag => tag === ccenvImage)); 100 | if(!imageExists) { 101 | console.log('Base container image not present, pulling from Docker Hub...'); 102 | await new Promise((resolve, reject) => { 103 | const req = http.request(pullOpts, (response) => { 104 | response.on('data', () => {}); 105 | response.on('end', () => { 106 | resolve(); 107 | }); 108 | }); 109 | req.on('error', reject); 110 | req.end(); 111 | }); 112 | console.log('Base container image downloaded.'); 113 | } else { 114 | console.log('Base container image present.'); 115 | } 116 | } catch(e) { 117 | console.log('Fatal error pulling docker images.'); 118 | console.log(e); 119 | process.exit(-1); 120 | } 121 | try { 122 | await Promise.all(clients.map(client => client.install(config.chaincodeId, config.chaincodeVersion, config.chaincodePath))); 123 | await new Promise(resolve => { 124 | setTimeout(resolve, 10000); 125 | }); 126 | console.log('Successfully installed chaincode on the default channel.'); 127 | } catch(e) { 128 | console.log('Fatal error installing chaincode on the Peer!'); 129 | console.log(e); 130 | process.exit(-1); 131 | } 132 | } else { 133 | console.log('Chaincode already installed on the blockchain network.'); 134 | } 135 | // Instantiate chaincode on all peers 136 | // Instantiating the chaincode on a single peer should be enough (for now) 137 | try { 138 | await clients[0].instantiate(config.chaincodeId, config.chaincodeVersion, config.chaincodePath, '{"Args":[""]}'); 139 | console.log('Successfully instantiated chaincode on all peers.'); 140 | } catch(e) { 141 | console.log('Fatal error instantiating chaincode on some(all) peers!'); 142 | console.log(e); 143 | process.exit(-1); 144 | } 145 | 146 | })(); 147 | // Export organization clients 148 | /*export { 149 | shopClient, 150 | fitcoinClient 151 | };*/ 152 | -------------------------------------------------------------------------------- /blockchainNetworkWithCouchDB/set-up/utils.js: -------------------------------------------------------------------------------- 1 | import { 2 | snakeToCamelCase, 3 | camelToSnakeCase 4 | } from 'json-style-converter'; 5 | module.exports.wrapError = function(message, innerError) { 6 | let error = new Error(message); 7 | error.inner = innerError; 8 | console.log(error.message); 9 | throw error; 10 | }; 11 | module.exports.marshalArgs = function(args) { 12 | if(!args) { 13 | return args; 14 | } 15 | if(typeof args === 'string') { 16 | return [args]; 17 | } 18 | let snakeArgs = camelToSnakeCase(args); 19 | if(Array.isArray(args)) { 20 | return snakeArgs.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg.toString()); 21 | } 22 | if(typeof args === 'object') { 23 | return [JSON.stringify(snakeArgs)]; 24 | } 25 | }; 26 | module.exports.unmarshalResult = function(result) { 27 | if(!Array.isArray(result)) { 28 | return result; 29 | } 30 | let buff = Buffer.concat(result); 31 | if(!Buffer.isBuffer(buff)) { 32 | return result; 33 | } 34 | let json = buff.toString('utf8'); 35 | if(!json) { 36 | return null; 37 | } 38 | let obj = JSON.parse(json); 39 | return snakeToCamelCase(obj); 40 | }; 41 | module.exports.unmarshalBlock = function(block) { 42 | const transactions = Array.isArray(block.data.data) ? block.data.data.map(({ 43 | payload: { 44 | header, 45 | data 46 | } 47 | }) => { 48 | const { 49 | channel_header 50 | } = header; 51 | const { 52 | type, 53 | timestamp, 54 | tx_id, 55 | channel_id 56 | } = channel_header; 57 | const { 58 | actions 59 | } = data; 60 | var execution_response = actions ? actions.map(obj => obj.payload.action.proposal_response_payload.extension.response) : ""; 61 | //console.log(execution_response); 62 | return { 63 | type, 64 | timestamp, 65 | tx_id, 66 | channel_id, 67 | execution_response 68 | }; 69 | }) : []; 70 | return { 71 | id: block.header.number.toString(), 72 | fingerprint: block.header.data_hash.slice(0, 20), 73 | transactions 74 | }; 75 | }; 76 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export FABRIC_CFG_PATH=$PWD 3 | sh ./clean.sh 4 | sh ./generate-certs.sh 5 | sh ./docker-images.sh 6 | -------------------------------------------------------------------------------- /ccfunctions.md: -------------------------------------------------------------------------------- 1 | ## Chaincode Functions 2 | 3 | This document goes through all the functions in the chaincode. All chaincode function calls are through an input json. The json consists of the following attributes: 4 | 5 | * type - enroll, invoke or query 6 | * userID - id to call the function 7 | * fcn - function name 8 | * args - array of string 9 | 10 | 11 | ### Create user and seller 12 | 13 | #### Enroll member call 14 | ``` 15 | var input = { 16 | type: enroll, 17 | params: {} 18 | } 19 | ``` 20 | - return memberID 21 | 22 | #### Create user 23 | ``` 24 | var input = { 25 | type: invoke, 26 | params: { 27 | userId: memberID, 28 | fcn: createMember 29 | args: memberID, user 30 | } 31 | } 32 | ``` 33 | - memberID - the id created for user 34 | - user - "user" string must be second arg 35 | 36 | #### Create seller 37 | ``` 38 | var input = { 39 | type: invoke, 40 | params: { 41 | userId: memberID 42 | fcn: createMember 43 | args: memberID, seller 44 | } 45 | } 46 | ``` 47 | - memberID - the id created for seller 48 | - user - "seller" string must be second arg 49 | 50 | ### User invoke calls 51 | 52 | The invoke calls from user's iOS app which update the blockchain state. 53 | 54 | #### Generate fitcoins 55 | ``` 56 | input = { 57 | type: invoke, 58 | params: { 59 | userId: userId 60 | fcn: generateFitcoins 61 | args: userId, totalSteps 62 | } 63 | } 64 | ``` 65 | - userID - the user ID returned from enroll 66 | - totalSteps - the total steps walked by user 67 | 68 | #### Make purchase 69 | ``` 70 | input = { 71 | type: invoke, 72 | params: { 73 | userId: userId, 74 | fcn: makePurchase 75 | args: userId, sellerId, productId, quantity 76 | } 77 | } 78 | ``` 79 | 80 | - sellerID - the seller's ID 81 | - userID 82 | - productID - the id of product with seller, picked by user through interface 83 | - quantity - picked by user through interface 84 | 85 | 86 | ### Seller invoke calls 87 | 88 | The invoke calls from seller dashboard which update the blockchain state. 89 | 90 | #### Create product inventory 91 | ``` 92 | var input = { 93 | type: invoke, 94 | params: { 95 | userId: sellerID 96 | fcn: createProduct 97 | args: sellerID, productID, productName, productCount productPrice 98 | } 99 | } 100 | ``` 101 | - sellerID - the seller's ID returned from enroll 102 | - productID - product property: the id of product with seller 103 | - productName - product property: the name of product 104 | - productCount - product property: the count of product 105 | - productPrice - product price: the price of product 106 | 107 | #### Update product inventory 108 | ``` 109 | var input = { 110 | type: invoke, 111 | params: { 112 | userId: sellerID 113 | fcn: updateProduct 114 | args: sellerID, productID, productName, productCount productPrice 115 | } 116 | } 117 | ``` 118 | - sellerID - the seller's ID returned from enroll 119 | - productID - product property: the id of product with seller 120 | - productName - product property: the name of product 121 | - productCount - product property: the count of product 122 | - productPrice - product price: the price of product 123 | 124 | ### User or Seller invoke calls 125 | 126 | User or seller can call transact purchase. Only seller can complete the transaction while both seller and user can decline the transaction 127 | 128 | #### Transact purchase 129 | ``` 130 | var input = { 131 | type: invoke, 132 | params: { 133 | userId: memberID 134 | fcn: transactPurchase 135 | args: memberID, contractID, newState(complete or declined) 136 | } 137 | } 138 | ``` 139 | 140 | - memberID - the id of user or seller calling the function 141 | - contractID - the contract ID generated when user perform 'makePurchase' 142 | - newState - must be "declined" or "complete". Only the sellerID on the contract can make the "complete" call 143 | 144 | 145 | ### Query calls 146 | 147 | The calls that read data from blockchain state database. 148 | 149 | #### Get State 150 | Gets state with userId, sellerID or contractID as args 151 | ``` 152 | var input = { 153 | type: query, 154 | params: { 155 | userId: userID 156 | fcn: getState 157 | args: id userId, sellerID or contractID 158 | } 159 | } 160 | ``` 161 | - id - must be a userId, sellerID or contractID 162 | 163 | #### Get products for sale 164 | Gets array of products available with sellerID 165 | ``` 166 | var input = { 167 | type: query, 168 | params: { 169 | userId: userID 170 | fcn: getProductsForSale 171 | args: (none) 172 | } 173 | } 174 | ``` 175 | 176 | #### Get all user's contracts 177 | Get user's contracts, for all the purchases made 178 | ``` 179 | var input = { 180 | type: query, 181 | params: { 182 | userId: userID, 183 | fcn: getAllContracts 184 | args: userID 185 | } 186 | } 187 | ``` 188 | - userID - the user's ID 189 | 190 | #### Get all contracts 191 | Gets all contracts 192 | ``` 193 | var input = { 194 | type: query, 195 | params: { 196 | userId: userID, 197 | fcn: getAllContracts 198 | args: (none) 199 | } 200 | } 201 | ``` 202 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker rm -f $(docker ps -aq) 4 | images=(blockchain-setup fitcoin-ca shop-ca orderer-peer fitcoin-peer shop-peer blockchain-setup-couchdb) 5 | for i in "${images[@]}" 6 | do 7 | echo Removing image : $i 8 | docker rmi -f $i 9 | done 10 | 11 | #docker rmi -f $(docker images | grep none) 12 | images=( dev-shop-peer dev-fitcoin-peer) 13 | for i in "${images[@]}" 14 | do 15 | echo Removing image : $i 16 | docker rmi -f $(docker images | grep $i ) 17 | done 18 | 19 | docker rmi $(docker images -f "dangling=true" -q) 20 | -------------------------------------------------------------------------------- /configtx.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | Profiles: 3 | TwoOrgsGenesis: 4 | Orderer: 5 | <<: *OrdererDefaults 6 | Organizations: 7 | - *OrdererOrg 8 | Consortiums: 9 | FitCoinConsortium: 10 | Organizations: 11 | - *ShopOrg 12 | - *FitCoinOrg 13 | TwoOrgsChannel: 14 | Consortium: FitCoinConsortium 15 | Application: 16 | <<: *ApplicationDefaults 17 | Organizations: 18 | - *ShopOrg 19 | - *FitCoinOrg 20 | 21 | Organizations: 22 | - &OrdererOrg 23 | Name: OrdererOrg 24 | ID: OrdererMSP 25 | MSPDir: cli/peers/ordererOrganizations/orderer-org/msp 26 | - &ShopOrg 27 | Name: ShopOrgMSP 28 | ID: ShopOrgMSP 29 | MSPDir: cli/peers/peerOrganizations/shop-org/msp 30 | AnchorPeers: 31 | - Host: shop-peer 32 | Port: 7051 33 | - &FitCoinOrg 34 | Name: FitCoinOrgMSP 35 | ID: FitCoinOrgMSP 36 | MSPDir: cli/peers/peerOrganizations/fitcoin-org/msp 37 | AnchorPeers: 38 | - Host: fitcoin-peer 39 | Port: 7051 40 | 41 | Orderer: &OrdererDefaults 42 | OrdererType: solo 43 | Addresses: 44 | - orderer0:7050 45 | BatchTimeout: 2s 46 | BatchSize: 47 | MaxMessageCount: 10 48 | AbsoluteMaxBytes: 99 MB 49 | PreferredMaxBytes: 512 KB 50 | Organizations: 51 | 52 | Application: &ApplicationDefaults 53 | Organizations: 54 | -------------------------------------------------------------------------------- /configtxgen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/FabricNodeSDK-Starter/cab2b2dc35f463c91330c04093aea2fe14e64e04/configtxgen -------------------------------------------------------------------------------- /configuration/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .vscode 4 | .idea 5 | .env 6 | .env.sample 7 | npm-debug.log 8 | .jshintignore 9 | .jshintrc 10 | .npmrc 11 | bin 12 | dist 13 | certs 14 | channel.tx 15 | -------------------------------------------------------------------------------- /configuration/.gitignore: -------------------------------------------------------------------------------- 1 | certs 2 | set-up 3 | chaincode 4 | node_modules 5 | config.json 6 | -------------------------------------------------------------------------------- /configuration/config.js: -------------------------------------------------------------------------------- 1 | //var readFileSync = require('fs').readFileSync; 2 | var resolve = require('path').resolve; 3 | var fs = require("fs"); 4 | const basePath = resolve(__dirname, './certs'); 5 | const readCryptoFile = filename => fs.readFileSync(resolve(basePath, filename)).toString(); 6 | const config = { 7 | channelName: 'mychannel', 8 | channelConfig: fs.readFileSync(resolve(__dirname, 'channel.tx')), 9 | chaincodeId: 'bcfit', 10 | chaincodeVersion: '1', 11 | chaincodePath: 'bcfit', 12 | orderer: { 13 | hostname: 'orderer0', 14 | url: 'grpcs://orderer0:7050', 15 | pem: readCryptoFile('ordererOrg.pem') 16 | }, 17 | peers: [{ 18 | peer: { 19 | hostname: 'shop-peer', 20 | url: 'grpcs://shop-peer:7051', 21 | eventHubUrl: 'grpcs://shop-peer:7053', 22 | pem: readCryptoFile('shopOrg.pem'), 23 | userKeystoreDBName: 'seller_db', 24 | userKeystoreDBUrl: 'http://ca-datastore:5984', 25 | stateDBName: 'member_db', 26 | stateDBUrl: 'http://shop-statedb:5984', 27 | org: 'org.ShopOrg', 28 | userType: 'seller' 29 | }, 30 | ca: { 31 | hostname: 'shop-ca', 32 | url: 'https://shop-ca:7054', 33 | mspId: 'ShopOrgMSP', 34 | caName: 'shop-org' 35 | }, 36 | admin: { 37 | key: readCryptoFile('Admin@shop-org-key.pem'), 38 | cert: readCryptoFile('Admin@shop-org-cert.pem') 39 | } 40 | }, { 41 | peer: { 42 | hostname: 'fitcoin-peer', 43 | url: 'grpcs://fitcoin-peer:7051', 44 | pem: readCryptoFile('fitcoinOrg.pem'), 45 | userKeystoreDBName: 'user_db', 46 | userKeystoreDBUrl: 'http://ca-datastore:5984', 47 | stateDBName: 'member_db', 48 | stateDBUrl: 'http://fitcoin-statedb:5984', 49 | eventHubUrl: 'grpcs://fitcoin-peer:7053', 50 | org: 'org.FitCoinOrg', 51 | userType: 'user' 52 | }, 53 | ca: { 54 | hostname: 'fitcoin-ca', 55 | url: 'https://fitcoin-ca:7054', 56 | mspId: 'FitCoinOrgMSP', 57 | caName: 'fitcoin-org' 58 | }, 59 | admin: { 60 | key: readCryptoFile('Admin@fitcoin-org-key.pem'), 61 | cert: readCryptoFile('Admin@fitcoin-org-cert.pem') 62 | } 63 | }] 64 | }; 65 | if(process.env.LOCALCONFIG) { 66 | config.orderer.url = 'grpcs://localhost:7050'; 67 | config.peers[0].peer.url = 'grpcs://localhost:7051'; 68 | config.peers[0].peer.eventHubUrl = 'grpcs://localhost:7053'; 69 | config.peers[0].ca.url = 'https://localhost:7054'; 70 | config.peers[0].peer.userKeystoreDBUrl = 'http://localhost:5984'; 71 | config.peers[0].peer.stateDBUrl = 'http://localhost:9984'; 72 | config.peers[1].peer.url = 'grpcs://localhost:8051'; 73 | config.peers[1].peer.eventHubUrl = 'grpcs://localhost:8053'; 74 | config.peers[1].ca.url = 'https://localhost:8054'; 75 | config.peers[1].peer.userKeystoreDBUrl = 'http://localhost:5984'; 76 | config.peers[1].peer.stateDBUrl = 'http://localhost:8984'; 77 | } 78 | //export default config; 79 | fs.writeFile("./config.json", JSON.stringify(config), (err) => { 80 | if(err) { 81 | console.error(err); 82 | return; 83 | } 84 | console.log("File has been created"); 85 | }); -------------------------------------------------------------------------------- /configuration/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "configsetup", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "fs": { 8 | "version": "0.0.1-security", 9 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", 10 | "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" 11 | }, 12 | "path": { 13 | "version": "0.12.7", 14 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 15 | "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", 16 | "requires": { 17 | "process": "0.11.10", 18 | "util": "0.10.3" 19 | } 20 | }, 21 | "process": { 22 | "version": "0.11.10", 23 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 24 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" 25 | }, 26 | "util": { 27 | "version": "0.10.3", 28 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 29 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 30 | "requires": { 31 | "inherits": "2.0.1" 32 | }, 33 | "dependencies": { 34 | "inherits": { 35 | "version": "2.0.1", 36 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 37 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /configuration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "configsetup", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "fs": "0.0.1-security", 13 | "path": "^0.12.7" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crypto-config.yaml: -------------------------------------------------------------------------------- 1 | OrdererOrgs: 2 | - Name: OrdererOrg 3 | Domain: orderer-org 4 | CommonName: orderer-org 5 | Specs: 6 | - Hostname: orderer0 7 | CommonName: "{{.Hostname}}" 8 | PeerOrgs: 9 | - Name: ShopOrg 10 | Domain: shop-org 11 | Specs: 12 | - Hostname: shop-peer 13 | CommonName: "{{.Hostname}}" 14 | Template: 15 | Count: 2 16 | Users: 17 | Count: 1 18 | - Name: FitCoinOrg 19 | Domain: fitcoin-org 20 | Specs: 21 | - Hostname: fitcoin-peer 22 | CommonName: "{{.Hostname}}" 23 | Template: 24 | Count: 2 25 | Users: 26 | Count: 1 27 | -------------------------------------------------------------------------------- /cryptogen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/FabricNodeSDK-Starter/cab2b2dc35f463c91330c04093aea2fe14e64e04/cryptogen -------------------------------------------------------------------------------- /docker-compose-couchdb.yaml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | 3 | services: 4 | ca-datastore: 5 | container_name: ca-datastore 6 | image: ishangulhane/fabric-couchdb 7 | environment: 8 | - COUCHDB_USER= 9 | - COUCHDB_PASSWORD= 10 | ports: 11 | - 5984:5984 12 | logging: 13 | driver: "json-file" 14 | options: 15 | max-size: "1m" 16 | max-file: "10" 17 | 18 | fitcoin-statedb: 19 | container_name: fitcoin-statedb 20 | image: ishangulhane/fabric-couchdb 21 | environment: 22 | - COUCHDB_USER= 23 | - COUCHDB_PASSWORD= 24 | ports: 25 | - 8984:5984 26 | logging: 27 | driver: "json-file" 28 | options: 29 | max-size: "1m" 30 | max-file: "10" 31 | 32 | shop-statedb: 33 | container_name: shop-statedb 34 | image: ishangulhane/fabric-couchdb 35 | environment: 36 | - COUCHDB_USER= 37 | - COUCHDB_PASSWORD= 38 | ports: 39 | - 9984:5984 40 | logging: 41 | driver: "json-file" 42 | options: 43 | max-size: "1m" 44 | max-file: "10" 45 | 46 | shop-ca: 47 | container_name: shop-ca 48 | image: shop-ca 49 | environment: 50 | - FABRIC_CA_HOME=/ca 51 | command: fabric-ca-server start 52 | ports: 53 | - 7054:7054 54 | logging: 55 | driver: "json-file" 56 | options: 57 | max-size: "1m" 58 | max-file: "10" 59 | 60 | fitcoin-ca: 61 | container_name: fitcoin-ca 62 | image: fitcoin-ca 63 | environment: 64 | - FABRIC_CA_HOME=/ca 65 | command: fabric-ca-server start 66 | ports: 67 | - 8054:7054 68 | logging: 69 | driver: "json-file" 70 | options: 71 | max-size: "1m" 72 | max-file: "10" 73 | 74 | orderer0: 75 | container_name: orderer0 76 | image: orderer-peer 77 | environment: 78 | - ORDERER_GENERAL_LOGLEVEL=debug 79 | - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 80 | - ORDERER_GENERAL_GENESISMETHOD=file 81 | - ORDERER_GENERAL_GENESISFILE=/orderer/crypto/genesis.block 82 | - ORDERER_GENERAL_LOCALMSPID=OrdererMSP 83 | - ORDERER_GENERAL_LOCALMSPDIR=/orderer/crypto/msp 84 | - ORDERER_GENERAL_TLS_ENABLED=true 85 | - ORDERER_GENERAL_TLS_PRIVATEKEY=/orderer/crypto/tls/server.key 86 | - ORDERER_GENERAL_TLS_CERTIFICATE=/orderer/crypto/tls/server.crt 87 | - ORDERER_GENERAL_TLS_ROOTCAS=[/orderer/crypto/tls/ca.crt] 88 | working_dir: /orderer 89 | command: orderer 90 | ports: 91 | - 7050:7050 92 | logging: 93 | driver: "json-file" 94 | options: 95 | max-size: "1m" 96 | max-file: "10" 97 | 98 | shop-peer: 99 | container_name: shop-peer 100 | image: shop-peer 101 | environment: 102 | - CORE_PEER_ID=shop-peer 103 | - CORE_PEER_ADDRESS=shop-peer:7051 104 | - CORE_PEER_GOSSIP_EXTERNALENDPOINT=shop-peer:7051 105 | - CORE_PEER_LOCALMSPID=ShopOrgMSP 106 | - CORE_LEDGER_STATE_STATEDATABASE=CouchDB 107 | - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=shop-statedb:5984 108 | - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME= 109 | - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD= 110 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 111 | - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fitcoin_default 112 | - CORE_LOGGING_LEVEL=DEBUG 113 | - CORE_PEER_TLS_ENABLED=true 114 | - CORE_PEER_ENDORSER_ENABLED=true 115 | - CORE_PEER_GOSSIP_USELEADERELECTION=true 116 | - CORE_PEER_GOSSIP_ORGLEADER=false 117 | - CORE_PEER_PROFILE_ENABLED=true 118 | - CORE_PEER_MSPCONFIGPATH=/peer/crypto/msp 119 | - CORE_PEER_TLS_CERT_FILE=/peer/crypto/tls/server.crt 120 | - CORE_PEER_TLS_KEY_FILE=/peer/crypto/tls/server.key 121 | - CORE_PEER_TLS_ROOTCERT_FILE=/peer/crypto/tls/ca.crt 122 | working_dir: /peer 123 | command: peer node start 124 | volumes: 125 | - /var/run/:/host/var/run/ 126 | ports: 127 | - 7051:7051 128 | - 7053:7053 129 | depends_on: 130 | - orderer0 131 | - shop-ca 132 | - ca-datastore 133 | - shop-statedb 134 | logging: 135 | driver: "json-file" 136 | options: 137 | max-size: "1m" 138 | max-file: "10" 139 | 140 | fitcoin-peer: 141 | container_name: fitcoin-peer 142 | image: fitcoin-peer 143 | environment: 144 | - CORE_PEER_ID=fitcoin-peer 145 | - CORE_PEER_ADDRESS=fitcoin-peer:7051 146 | - CORE_PEER_GOSSIP_EXTERNALENDPOINT=fitcoin-peer:7051 147 | - CORE_PEER_LOCALMSPID=FitCoinOrgMSP 148 | - CORE_LEDGER_STATE_STATEDATABASE=CouchDB 149 | - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=fitcoin-statedb:5984 150 | - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME= 151 | - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD= 152 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 153 | - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fitcoin_default 154 | - CORE_LOGGING_LEVEL=DEBUG 155 | - CORE_PEER_TLS_ENABLED=true 156 | - CORE_PEER_ENDORSER_ENABLED=true 157 | - CORE_PEER_GOSSIP_USELEADERELECTION=true 158 | - CORE_PEER_GOSSIP_ORGLEADER=false 159 | - CORE_PEER_PROFILE_ENABLED=true 160 | - CORE_PEER_MSPCONFIGPATH=/peer/crypto/msp 161 | - CORE_PEER_TLS_CERT_FILE=/peer/crypto/tls/server.crt 162 | - CORE_PEER_TLS_KEY_FILE=/peer/crypto/tls/server.key 163 | - CORE_PEER_TLS_ROOTCERT_FILE=/peer/crypto/tls/ca.crt 164 | working_dir: /peer 165 | command: peer node start 166 | volumes: 167 | - /var/run/:/host/var/run/ 168 | ports: 169 | - 8051:7051 170 | - 8053:7053 171 | depends_on: 172 | - orderer0 173 | - fitcoin-ca 174 | - shop-peer 175 | - ca-datastore 176 | - fitcoin-statedb 177 | logging: 178 | driver: "json-file" 179 | options: 180 | max-size: "1m" 181 | max-file: "10" 182 | 183 | blockchain-setup : 184 | container_name: blockchain-setup 185 | image: blockchain-setup-couchdb 186 | environment: 187 | - DOCKER_SOCKET_PATH=/host/var/run/docker.sock 188 | - DOCKER_CCENV_IMAGE=hyperledger/fabric-ccenv:x86_64-1.1.0 189 | - SECRETSDIR=/run/secrets 190 | volumes: 191 | - /var/run/:/host/var/run/ 192 | depends_on: 193 | - orderer0 194 | - shop-ca 195 | - shop-peer 196 | - fitcoin-ca 197 | - fitcoin-peer 198 | 199 | secrets: 200 | - config 201 | - channel 202 | 203 | secrets: 204 | config: 205 | file: ./configuration/config.json 206 | channel: 207 | file: ./configuration/channel.tx 208 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | 3 | services: 4 | shop-ca: 5 | container_name: shop-ca 6 | image: shop-ca 7 | environment: 8 | - FABRIC_CA_HOME=/ca 9 | command: fabric-ca-server start 10 | ports: 11 | - 7054:7054 12 | logging: 13 | driver: "json-file" 14 | options: 15 | max-size: "1m" 16 | max-file: "10" 17 | 18 | fitcoin-ca: 19 | container_name: fitcoin-ca 20 | image: fitcoin-ca 21 | environment: 22 | - FABRIC_CA_HOME=/ca 23 | command: fabric-ca-server start 24 | ports: 25 | - 8054:7054 26 | logging: 27 | driver: "json-file" 28 | options: 29 | max-size: "1m" 30 | max-file: "10" 31 | 32 | orderer0: 33 | container_name: orderer0 34 | image: orderer-peer 35 | environment: 36 | - ORDERER_GENERAL_LOGLEVEL=debug 37 | - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 38 | - ORDERER_GENERAL_GENESISMETHOD=file 39 | - ORDERER_GENERAL_GENESISFILE=/orderer/crypto/genesis.block 40 | - ORDERER_GENERAL_LOCALMSPID=OrdererMSP 41 | - ORDERER_GENERAL_LOCALMSPDIR=/orderer/crypto/msp 42 | - ORDERER_GENERAL_TLS_ENABLED=true 43 | - ORDERER_GENERAL_TLS_PRIVATEKEY=/orderer/crypto/tls/server.key 44 | - ORDERER_GENERAL_TLS_CERTIFICATE=/orderer/crypto/tls/server.crt 45 | - ORDERER_GENERAL_TLS_ROOTCAS=[/orderer/crypto/tls/ca.crt] 46 | working_dir: /orderer 47 | command: orderer 48 | ports: 49 | - 7050:7050 50 | logging: 51 | driver: "json-file" 52 | options: 53 | max-size: "1m" 54 | max-file: "10" 55 | 56 | shop-peer: 57 | container_name: shop-peer 58 | image: shop-peer 59 | environment: 60 | - CORE_PEER_ID=shop-peer 61 | - CORE_PEER_ADDRESS=shop-peer:7051 62 | - CORE_PEER_GOSSIP_EXTERNALENDPOINT=shop-peer:7051 63 | - CORE_PEER_LOCALMSPID=ShopOrgMSP 64 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 65 | - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fitcoin_default 66 | - CORE_LOGGING_LEVEL=DEBUG 67 | - CORE_PEER_TLS_ENABLED=true 68 | - CORE_PEER_ENDORSER_ENABLED=true 69 | - CORE_PEER_GOSSIP_USELEADERELECTION=true 70 | - CORE_PEER_GOSSIP_ORGLEADER=false 71 | - CORE_PEER_PROFILE_ENABLED=true 72 | - CORE_PEER_MSPCONFIGPATH=/peer/crypto/msp 73 | - CORE_PEER_TLS_CERT_FILE=/peer/crypto/tls/server.crt 74 | - CORE_PEER_TLS_KEY_FILE=/peer/crypto/tls/server.key 75 | - CORE_PEER_TLS_ROOTCERT_FILE=/peer/crypto/tls/ca.crt 76 | working_dir: /peer 77 | command: peer node start 78 | volumes: 79 | - /var/run/:/host/var/run/ 80 | ports: 81 | - 7051:7051 82 | - 7053:7053 83 | depends_on: 84 | - orderer0 85 | - shop-ca 86 | logging: 87 | driver: "json-file" 88 | options: 89 | max-size: "1m" 90 | max-file: "10" 91 | 92 | fitcoin-peer: 93 | container_name: fitcoin-peer 94 | image: fitcoin-peer 95 | environment: 96 | - CORE_PEER_ID=fitcoin-peer 97 | - CORE_PEER_ADDRESS=fitcoin-peer:7051 98 | - CORE_PEER_GOSSIP_EXTERNALENDPOINT=fitcoin-peer:7051 99 | - CORE_PEER_LOCALMSPID=FitCoinOrgMSP 100 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 101 | - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fitcoin_default 102 | - CORE_LOGGING_LEVEL=DEBUG 103 | - CORE_PEER_TLS_ENABLED=true 104 | - CORE_PEER_ENDORSER_ENABLED=true 105 | - CORE_PEER_GOSSIP_USELEADERELECTION=true 106 | - CORE_PEER_GOSSIP_ORGLEADER=false 107 | - CORE_PEER_PROFILE_ENABLED=true 108 | - CORE_PEER_MSPCONFIGPATH=/peer/crypto/msp 109 | - CORE_PEER_TLS_CERT_FILE=/peer/crypto/tls/server.crt 110 | - CORE_PEER_TLS_KEY_FILE=/peer/crypto/tls/server.key 111 | - CORE_PEER_TLS_ROOTCERT_FILE=/peer/crypto/tls/ca.crt 112 | working_dir: /peer 113 | command: peer node start 114 | volumes: 115 | - /var/run/:/host/var/run/ 116 | ports: 117 | - 8051:7051 118 | - 8053:7053 119 | depends_on: 120 | - orderer0 121 | - fitcoin-ca 122 | - shop-peer 123 | logging: 124 | driver: "json-file" 125 | options: 126 | max-size: "1m" 127 | max-file: "10" 128 | 129 | blockchain-setup : 130 | container_name: blockchain-setup 131 | image: blockchain-setup 132 | environment: 133 | - DOCKER_SOCKET_PATH=/host/var/run/docker.sock 134 | - DOCKER_CCENV_IMAGE=hyperledger/fabric-ccenv:x86_64-1.1.0 135 | - SECRETSDIR=/run/secrets 136 | volumes: 137 | - /var/run/:/host/var/run/ 138 | depends_on: 139 | - orderer0 140 | - shop-ca 141 | - shop-peer 142 | - fitcoin-ca 143 | - fitcoin-peer 144 | secrets: 145 | - config 146 | - channel 147 | 148 | secrets: 149 | config: 150 | file: ./configuration/config.json 151 | channel: 152 | file: ./configuration/channel.tx 153 | -------------------------------------------------------------------------------- /docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | dockerFabricPull() { 5 | local FABRIC_TAG=$1 6 | #for IMAGES in peer orderer ccenv couchdb; do 7 | for IMAGES in peer orderer ccenv; do 8 | echo "==> FABRIC IMAGE: $IMAGES" 9 | echo 10 | docker pull hyperledger/fabric-$IMAGES:$FABRIC_TAG 11 | docker tag hyperledger/fabric-$IMAGES:$FABRIC_TAG hyperledger/fabric-$IMAGES 12 | done 13 | } 14 | 15 | dockerCaPull() { 16 | local CA_TAG=$1 17 | echo "==> FABRIC CA IMAGE" 18 | echo 19 | docker pull hyperledger/fabric-ca:$CA_TAG 20 | docker tag hyperledger/fabric-ca:$CA_TAG hyperledger/fabric-ca 21 | } 22 | 23 | dockerCouchDBPull() { 24 | echo "==> Couchdb IMAGE" 25 | echo 26 | docker pull ishangulhane/fabric-couchdb 27 | docker tag ishangulhane/fabric-couchdb ishangulhane/fabric-couchdb 28 | } 29 | 30 | BUILD= 31 | DOWNLOAD= 32 | if [ $# -eq 0 ]; then 33 | BUILD=true 34 | PUSH=true 35 | DOWNLOAD=true 36 | else 37 | for arg in "$@" 38 | do 39 | if [ $arg == "build" ]; then 40 | BUILD=true 41 | fi 42 | if [ $arg == "download" ]; then 43 | DOWNLOAD=true 44 | fi 45 | done 46 | fi 47 | # 1.1.0 48 | if [ $DOWNLOAD ]; then 49 | : ${CA_TAG:="x86_64-1.1.0"} 50 | : ${FABRIC_TAG:="x86_64-1.1.0"} 51 | 52 | 53 | echo "===> Pulling fabric Images" 54 | dockerFabricPull ${FABRIC_TAG} 55 | 56 | echo "===> Pulling fabric ca Image" 57 | dockerCaPull ${CA_TAG} 58 | 59 | echo "===> Pulling fabric couchdb Image" 60 | dockerCouchDBPull 61 | 62 | echo 63 | echo "===> List out hyperledger docker images" 64 | docker images | grep hyperledger* 65 | fi 66 | 67 | if [ $BUILD ]; 68 | then 69 | echo '############################################################' 70 | echo '# BUILDING CONTAINER IMAGES #' 71 | echo '############################################################' 72 | docker build -t orderer-peer:latest orderer/ 73 | docker build -t shop-peer:latest shopPeer/ 74 | docker build -t fitcoin-peer:latest fitcoinPeer/ 75 | docker build -t shop-ca:latest shopCertificateAuthority/ 76 | docker build -t fitcoin-ca:latest fitcoinCertificateAuthority/ 77 | docker build -t blockchain-setup:latest blockchainNetwork/ 78 | docker build -t blockchain-setup-couchdb:latest blockchainNetworkWithCouchDB/ 79 | fi 80 | -------------------------------------------------------------------------------- /fitcoinCertificateAuthority/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/hyperledger/fabric-ca:x86_64-1.1.0 2 | 3 | RUN mkdir /ca 4 | COPY fabric-ca-server-config.yaml /ca 5 | COPY tls /ca/tls 6 | COPY ca /ca/ca 7 | -------------------------------------------------------------------------------- /fitcoinCertificateAuthority/fabric-ca-server-config.yaml: -------------------------------------------------------------------------------- 1 | # Server's listening port (default: 7054) 2 | port: 7054 3 | 4 | # Enables debug logging (default: false) 5 | debug: false 6 | 7 | ############################################################################# 8 | # TLS section for the server's listening port 9 | ############################################################################# 10 | tls: 11 | enabled: true 12 | certfile: /ca/tls/cert.pem 13 | keyfile: /ca/tls/key.pem 14 | clientauth: 15 | type: noclientcert 16 | certfiles: 17 | 18 | ############################################################################# 19 | # The CA section contains information related to the Certificate Authority 20 | # including the name of the CA, which should be unique for all members 21 | # of a blockchain network. It also includes the key and certificate files 22 | # used when issuing enrollment certificates (ECerts) and transaction 23 | # certificates (TCerts). 24 | ############################################################################# 25 | ca: 26 | name: fitcoin-org 27 | keyfile: ./ca/key.pem 28 | certfile: ./ca/cert.pem 29 | chainfile: ./ca/cert.pem 30 | 31 | ############################################################################# 32 | # The registry section controls how the fabric-ca-server does two things: 33 | # 1) authenticates enrollment requests which contain a username and password 34 | # (also known as an enrollment ID and secret). 35 | # 2) once authenticated, retrieves the identity's attribute names and 36 | # values which the fabric-ca-server optionally puts into TCerts 37 | # which it issues for transacting on the Hyperledger Fabric blockchain. 38 | # These attributes are useful for making access control decisions in 39 | # chaincode. 40 | ############################################################################# 41 | registry: 42 | # Maximum number of times a password/secret can be reused for enrollment 43 | # (default: -1, which means there is no limit) 44 | maxenrollments: -1 45 | 46 | # Contains identity information which is used when LDAP is disabled 47 | identities: 48 | - name: admin 49 | pass: adminpw 50 | type: client 51 | affiliation: "FitCoinOrg" 52 | maxenrollments: -1 53 | attrs: 54 | hf.Registrar.Roles: "client,user,peer,validator,auditor" 55 | hf.Registrar.DelegateRoles: "client,user,validator,auditor" 56 | hf.Revoker: true 57 | hf.IntermediateCA: true 58 | 59 | 60 | ############################################################################# 61 | # Database section 62 | # Supported types are: "sqlite3", "postgres", and "mysql". 63 | ############################################################################# 64 | db: 65 | type: sqlite3 66 | datasource: fabric-ca-server.db 67 | tls: 68 | enabled: false 69 | certfiles: 70 | - db-server-cert.pem 71 | client: 72 | certfile: db-client-cert.pem 73 | keyfile: db-client-key.pem 74 | 75 | 76 | #db: 77 | # type: sqlite3 78 | # datasource: fabric-ca-server.db 79 | # tls: 80 | # enabled: false 81 | 82 | 83 | ############################################################################ 84 | # LDAP section 85 | # If LDAP is enabled, the fabric-ca-server calls LDAP to: 86 | # 1) authenticate enrollment ID and secret (i.e. username and password) 87 | # for enrollment requests; 88 | # 2) To retrieve identity attributes 89 | ############################################################################# 90 | ldap: 91 | enabled: false 92 | 93 | ############################################################################# 94 | # Affiliation section 95 | ############################################################################# 96 | affiliations: 97 | org: 98 | - FitCoinOrg 99 | 100 | 101 | ############################################################################# 102 | # Signing section 103 | # 104 | # The "default" subsection is used to sign enrollment certificates; 105 | # the default expiration ("expiry" field) is "8760h", which is 1 year in hours. 106 | # 107 | ############################################################################# 108 | signing: 109 | default: 110 | usage: 111 | - digital signature 112 | expiry: 8760h 113 | profiles: 114 | ca: 115 | usage: 116 | - cert sign 117 | expiry: 43800h 118 | caconstraint: 119 | isca: true 120 | maxpathlen: 1 121 | 122 | csr: 123 | cn: fabric-ca-server 124 | names: 125 | - C: US 126 | ST: "California" 127 | L: 128 | O: "IBM" 129 | OU: "Cloud" 130 | hosts: 131 | - shop-ca 132 | - localhost 133 | ca: 134 | expiry: 131400h 135 | pathlength: 10 136 | 137 | bccsp: 138 | default: SW 139 | sw: 140 | hash: SHA2 141 | security: 256 142 | filekeystore: 143 | keystore: msp/keystore 144 | -------------------------------------------------------------------------------- /fitcoinPeer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/hyperledger/fabric-peer:x86_64-1.1.0 2 | 3 | RUN mkdir /peer 4 | COPY crypto /peer/crypto 5 | -------------------------------------------------------------------------------- /generate-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo 5 | echo "#################################################################" 6 | echo "####### Generating cryptographic material ##########" 7 | echo "#################################################################" 8 | PROJPATH=$(pwd) 9 | CLIPATH=$PROJPATH/cli/peers 10 | ORDERERS=$CLIPATH/ordererOrganizations 11 | PEERS=$CLIPATH/peerOrganizations 12 | 13 | rm -rf $CLIPATH 14 | $PROJPATH/cryptogen generate --config=$PROJPATH/crypto-config.yaml --output=$CLIPATH 15 | 16 | sh generate-cfgtx.sh 17 | 18 | rm -rf $PROJPATH/{orderer,shopPeer,fitcoinPeer}/crypto 19 | mkdir $PROJPATH/{orderer,shopPeer,fitcoinPeer}/crypto 20 | cp -r $ORDERERS/orderer-org/orderers/orderer0/{msp,tls} $PROJPATH/orderer/crypto 21 | cp -r $PEERS/shop-org/peers/shop-peer/{msp,tls} $PROJPATH/shopPeer/crypto 22 | cp -r $PEERS/fitcoin-org/peers/fitcoin-peer/{msp,tls} $PROJPATH/fitcoinPeer/crypto 23 | cp $CLIPATH/genesis.block $PROJPATH/orderer/crypto/ 24 | 25 | SHOPCAPATH=$PROJPATH/shopCertificateAuthority 26 | FITCOINCAPATH=$PROJPATH/fitcoinCertificateAuthority 27 | 28 | rm -rf {$SHOPCAPATH,$FITCOINCAPATH}/{ca,tls} 29 | mkdir -p {$SHOPCAPATH,$FITCOINCAPATH}/{ca,tls} 30 | 31 | cp $PEERS/shop-org/ca/* $SHOPCAPATH/ca 32 | cp $PEERS/shop-org/tlsca/* $SHOPCAPATH/tls 33 | mv $SHOPCAPATH/ca/*_sk $SHOPCAPATH/ca/key.pem 34 | mv $SHOPCAPATH/ca/*-cert.pem $SHOPCAPATH/ca/cert.pem 35 | mv $SHOPCAPATH/tls/*_sk $SHOPCAPATH/tls/key.pem 36 | mv $SHOPCAPATH/tls/*-cert.pem $SHOPCAPATH/tls/cert.pem 37 | 38 | cp $PEERS/fitcoin-org/ca/* $FITCOINCAPATH/ca 39 | cp $PEERS/fitcoin-org/tlsca/* $FITCOINCAPATH/tls 40 | mv $FITCOINCAPATH/ca/*_sk $FITCOINCAPATH/ca/key.pem 41 | mv $FITCOINCAPATH/ca/*-cert.pem $FITCOINCAPATH/ca/cert.pem 42 | mv $FITCOINCAPATH/tls/*_sk $FITCOINCAPATH/tls/key.pem 43 | mv $FITCOINCAPATH/tls/*-cert.pem $FITCOINCAPATH/tls/cert.pem 44 | 45 | WEBCERTS=$PROJPATH/configuration/certs 46 | rm -rf $WEBCERTS 47 | mkdir -p $WEBCERTS 48 | cp $PROJPATH/orderer/crypto/tls/ca.crt $WEBCERTS/ordererOrg.pem 49 | cp $PROJPATH/shopPeer/crypto/tls/ca.crt $WEBCERTS/shopOrg.pem 50 | cp $PROJPATH/fitcoinPeer/crypto/tls/ca.crt $WEBCERTS/fitcoinOrg.pem 51 | cp $PEERS/shop-org/users/Admin@shop-org/msp/keystore/* $WEBCERTS/Admin@shop-org-key.pem 52 | cp $PEERS/shop-org/users/Admin@shop-org/msp/signcerts/* $WEBCERTS/ 53 | cp $PEERS/fitcoin-org/users/Admin@fitcoin-org/msp/keystore/* $WEBCERTS/Admin@fitcoin-org-key.pem 54 | cp $PEERS/fitcoin-org/users/Admin@fitcoin-org/msp/signcerts/* $WEBCERTS/ 55 | 56 | WEBCERTS=$PROJPATH/blockchainNetwork 57 | 58 | BACKEND=$PROJPATH/test 59 | rm -rf $BACKEND/set-up 60 | mkdir -p $BACKEND/set-up 61 | cp -r $WEBCERTS/set-up/* $BACKEND/set-up/ 62 | 63 | rm -rf $CLIPATH 64 | 65 | cd configuration 66 | npm install 67 | node config.js 68 | cd .. 69 | -------------------------------------------------------------------------------- /generate-cfgtx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CHANNEL_NAME="mychannel" 4 | PROJPATH=$(pwd) 5 | CLIPATH=$PROJPATH/cli/peers 6 | 7 | echo 8 | echo "##########################################################" 9 | echo "######### Generating Orderer Genesis block ##############" 10 | echo "##########################################################" 11 | $PROJPATH/configtxgen -profile TwoOrgsGenesis -outputBlock $CLIPATH/genesis.block 12 | 13 | echo 14 | echo "#################################################################" 15 | echo "### Generating channel configuration transaction 'channel.tx' ###" 16 | echo "#################################################################" 17 | $PROJPATH/configtxgen -profile TwoOrgsChannel -outputCreateChannelTx $CLIPATH/channel.tx -channelID $CHANNEL_NAME 18 | cp $CLIPATH/channel.tx $PROJPATH/configuration/channel.tx 19 | 20 | echo 21 | echo "#################################################################" 22 | echo "####### Generating anchor peer update for ShopOrg ##########" 23 | echo "#################################################################" 24 | $PROJPATH/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate $CLIPATH/ShopOrgMSPAnchors.tx -channelID $CHANNEL_NAME -asOrg ShopOrgMSP 25 | 26 | echo 27 | echo "##################################################################" 28 | echo "####### Generating anchor peer update for RepairShopOrg ##########" 29 | echo "##################################################################" 30 | $PROJPATH/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate $CLIPATH/FitCoinOrgMSPAnchors.tx -channelID $CHANNEL_NAME -asOrg FitCoinOrgMSP 31 | -------------------------------------------------------------------------------- /images/Pattern1-Build-a-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/FabricNodeSDK-Starter/cab2b2dc35f463c91330c04093aea2fe14e64e04/images/Pattern1-Build-a-network.png -------------------------------------------------------------------------------- /orderer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/hyperledger/fabric-orderer:x86_64-1.1.0 2 | 3 | RUN mkdir /orderer 4 | COPY crypto /orderer/crypto 5 | -------------------------------------------------------------------------------- /shopCertificateAuthority/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/hyperledger/fabric-ca:x86_64-1.1.0 2 | 3 | RUN mkdir /ca 4 | COPY fabric-ca-server-config.yaml /ca 5 | COPY tls /ca/tls 6 | COPY ca /ca/ca 7 | -------------------------------------------------------------------------------- /shopCertificateAuthority/fabric-ca-server-config.yaml: -------------------------------------------------------------------------------- 1 | # Server's listening port (default: 7054) 2 | port: 7054 3 | 4 | # Enables debug logging (default: false) 5 | debug: false 6 | 7 | ############################################################################# 8 | # TLS section for the server's listening port 9 | ############################################################################# 10 | tls: 11 | enabled: true 12 | certfile: /ca/tls/cert.pem 13 | keyfile: /ca/tls/key.pem 14 | clientauth: 15 | type: noclientcert 16 | certfiles: 17 | 18 | ############################################################################# 19 | # The CA section contains information related to the Certificate Authority 20 | # including the name of the CA, which should be unique for all members 21 | # of a blockchain network. It also includes the key and certificate files 22 | # used when issuing enrollment certificates (ECerts) and transaction 23 | # certificates (TCerts). 24 | ############################################################################# 25 | ca: 26 | name: shop-org 27 | keyfile: ./ca/key.pem 28 | certfile: ./ca/cert.pem 29 | chainfile: ./ca/cert.pem 30 | 31 | ############################################################################# 32 | # The registry section controls how the fabric-ca-server does two things: 33 | # 1) authenticates enrollment requests which contain a username and password 34 | # (also known as an enrollment ID and secret). 35 | # 2) once authenticated, retrieves the identity's attribute names and 36 | # values which the fabric-ca-server optionally puts into TCerts 37 | # which it issues for transacting on the Hyperledger Fabric blockchain. 38 | # These attributes are useful for making access control decisions in 39 | # chaincode. 40 | ############################################################################# 41 | registry: 42 | # Maximum number of times a password/secret can be reused for enrollment 43 | # (default: -1, which means there is no limit) 44 | maxenrollments: -1 45 | 46 | # Contains identity information which is used when LDAP is disabled 47 | identities: 48 | - name: admin 49 | pass: adminpw 50 | type: client 51 | affiliation: "ShopOrg" 52 | maxenrollments: -1 53 | attrs: 54 | hf.Registrar.Roles: "client,user,peer,validator,auditor" 55 | hf.Registrar.DelegateRoles: "client,user,validator,auditor" 56 | hf.Revoker: true 57 | hf.IntermediateCA: true 58 | 59 | 60 | ############################################################################# 61 | # Database section 62 | # Supported types are: "sqlite3", "postgres", and "mysql". 63 | ############################################################################# 64 | db: 65 | type: sqlite3 66 | datasource: fabric-ca-server.db 67 | tls: 68 | enabled: false 69 | certfiles: 70 | - db-server-cert.pem 71 | client: 72 | certfile: db-client-cert.pem 73 | keyfile: db-client-key.pem 74 | 75 | 76 | #db: 77 | # type: sqlite3 78 | # datasource: fabric-ca-server.db 79 | # tls: 80 | # enabled: false 81 | 82 | 83 | ############################################################################ 84 | # LDAP section 85 | # If LDAP is enabled, the fabric-ca-server calls LDAP to: 86 | # 1) authenticate enrollment ID and secret (i.e. username and password) 87 | # for enrollment requests; 88 | # 2) To retrieve identity attributes 89 | ############################################################################# 90 | ldap: 91 | enabled: false 92 | 93 | ############################################################################# 94 | # Affiliation section 95 | ############################################################################# 96 | affiliations: 97 | org: 98 | - ShopOrg 99 | 100 | 101 | ############################################################################# 102 | # Signing section 103 | # 104 | # The "default" subsection is used to sign enrollment certificates; 105 | # the default expiration ("expiry" field) is "8760h", which is 1 year in hours. 106 | # 107 | ############################################################################# 108 | signing: 109 | default: 110 | usage: 111 | - digital signature 112 | expiry: 8760h 113 | profiles: 114 | ca: 115 | usage: 116 | - cert sign 117 | expiry: 43800h 118 | caconstraint: 119 | isca: true 120 | maxpathlen: 1 121 | 122 | csr: 123 | cn: fabric-ca-server 124 | names: 125 | - C: US 126 | ST: "California" 127 | L: 128 | O: "IBM" 129 | OU: "Cloud" 130 | hosts: 131 | - shop-ca 132 | - localhost 133 | ca: 134 | expiry: 131400h 135 | pathlength: 10 136 | 137 | bccsp: 138 | default: SW 139 | sw: 140 | hash: SHA2 141 | security: 256 142 | filekeystore: 143 | keystore: msp/keystore 144 | -------------------------------------------------------------------------------- /shopPeer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/hyperledger/fabric-peer:x86_64-1.1.0 2 | 3 | RUN mkdir /peer 4 | COPY crypto /peer/crypto 5 | -------------------------------------------------------------------------------- /test/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "6.11.3" 8 | } 9 | } 10 | ] 11 | ], 12 | "sourceMaps": "inline" 13 | } -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | certs 2 | set-up 3 | chaincode 4 | node_modules 5 | config.json 6 | -------------------------------------------------------------------------------- /test/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Long = require('long'); 3 | var resolve = require('path').resolve; 4 | var EventEmitter = require('events').EventEmitter; 5 | var hfc = require('fabric-client'); 6 | var CAClient = require('fabric-ca-client'); 7 | var CouchDBKeyValueStore = require('./set-up/CouchDBKeyValueStore.js'); 8 | var CKS = require('fabric-client/lib/impl/CryptoKeyStore.js'); 9 | import enrollUser from './set-up/enroll'; 10 | var utils = require('./set-up/utils.js'); 11 | process.env.GOPATH = resolve(__dirname, '../chaincode'); 12 | const JOIN_TIMEOUT = 120000, 13 | TRANSACTION_TIMEOUT = 120000; 14 | export class OrganizationClient extends EventEmitter { 15 | constructor(channelName, ordererConfig, peerConfig, caConfig, admin) { 16 | super(); 17 | this._channelName = channelName; 18 | this._ordererConfig = ordererConfig; 19 | this._peerConfig = peerConfig; 20 | this._caConfig = caConfig; 21 | this._admin = admin; 22 | this._peers = []; 23 | this._eventHubs = []; 24 | this._client = new hfc(); 25 | // Setup channel 26 | this._channel = this._client.newChannel(channelName); 27 | // Setup orderer and peers 28 | const orderer = this._client.newOrderer(ordererConfig.url, { 29 | pem: ordererConfig.pem, 30 | 'ssl-target-name-override': ordererConfig.hostname 31 | }); 32 | this._channel.addOrderer(orderer); 33 | const defaultPeer = this._client.newPeer(peerConfig.url, { 34 | pem: peerConfig.pem, 35 | 'ssl-target-name-override': peerConfig.hostname 36 | }); 37 | this._peers.push(defaultPeer); 38 | this._channel.addPeer(defaultPeer); 39 | this._adminUser = null; 40 | this._ca = null; 41 | } 42 | async login() { 43 | try { 44 | await this._client.setStateStore(await CKS(CouchDBKeyValueStore, { 45 | name: this._peerConfig.stateDBName, 46 | url: this._peerConfig.stateDBUrl 47 | })); 48 | var crypto_suite = hfc.newCryptoSuite(); 49 | var crypto_store = hfc.newCryptoKeyStore(CouchDBKeyValueStore, { 50 | name: this._peerConfig.userKeystoreDBName, 51 | url: this._peerConfig.userKeystoreDBUrl 52 | }); 53 | crypto_suite.setCryptoKeyStore(crypto_store); 54 | var client_crypto_suite = hfc.newCryptoSuite(); 55 | var client_crypto_store = hfc.newCryptoKeyStore(CouchDBKeyValueStore, { 56 | name: this._peerConfig.userKeystoreDBName, 57 | url: this._peerConfig.userKeystoreDBUrl 58 | }); 59 | client_crypto_suite.setCryptoKeyStore(client_crypto_store); 60 | this._client.setCryptoSuite(client_crypto_suite); 61 | this._ca = await new CAClient(this._caConfig.url, { 62 | trustedRoots: [], 63 | verify: false 64 | }, this._caConfig.caName, crypto_suite); 65 | console.log("CA registration complete "); 66 | this._adminUser = await enrollUser(this._client, "admin", "adminpw", this._ca, { 67 | mspId: this._caConfig.mspId, 68 | adminUser: null, 69 | affiliationOrg: this._peerConfig.org, 70 | noOfAttempts: 5 71 | }); 72 | //await this._client.setUserContext(this._adminUser); 73 | //await this.createOrgAdmin(); 74 | } catch(e) { 75 | console.log(`Failed to enroll user. Error: ${e.message}`); 76 | throw e; 77 | } 78 | } 79 | initEventHubs() { 80 | // Setup event hubs 81 | try { 82 | const defaultEventHub = this._client.newEventHub(); 83 | defaultEventHub.setPeerAddr(this._peerConfig.eventHubUrl, { 84 | pem: this._peerConfig.pem, 85 | 'ssl-target-name-override': this._peerConfig.hostname 86 | }); 87 | defaultEventHub.connect(); 88 | defaultEventHub.registerBlockEvent(block => { 89 | this.emit('block', utils.unmarshalBlock(block)); 90 | }); 91 | this._eventHubs.push(defaultEventHub); 92 | } catch(e) { 93 | console.log(`Failed to configure event hubs. Error ${e.message}`); 94 | throw e; 95 | } 96 | } 97 | async createOrgAdmin() { 98 | return this._client.createUser({ 99 | username: `Admin@${this._peerConfig.hostname}`, 100 | mspid: this._caConfig.mspId, 101 | cryptoContent: { 102 | privateKeyPEM: this._admin.key, 103 | signedCertPEM: this._admin.cert 104 | } 105 | }); 106 | } 107 | async initialize() { 108 | try { 109 | await this._channel.initialize(); 110 | } catch(e) { 111 | console.log(`Failed to initialize chain. Error: ${e.message}`); 112 | throw e; 113 | } 114 | } 115 | async createChannel(envelope) { 116 | const txId = this._client.newTransactionID(); 117 | const channelConfig = this._client.extractChannelConfig(envelope); 118 | const signature = this._client.signChannelConfig(channelConfig); 119 | const request = { 120 | name: this._channelName, 121 | orderer: this._channel.getOrderers()[0], 122 | config: channelConfig, 123 | signatures: [signature], 124 | txId 125 | }; 126 | const response = await this._client.createChannel(request); 127 | // Wait for 5sec to create channel 128 | //console.log("channel log "); 129 | //console.log(response); 130 | await new Promise(resolve => { 131 | setTimeout(resolve, 5000); 132 | }); 133 | return response; 134 | } 135 | async joinChannel() { 136 | try { 137 | const genesisBlock = await this._channel.getGenesisBlock({ 138 | txId: this._client.newTransactionID() 139 | }); 140 | const request = { 141 | targets: this._peers, 142 | txId: this._client.newTransactionID(), 143 | block: genesisBlock 144 | }; 145 | const joinedChannelPromises = this._eventHubs.map(eh => { 146 | eh.connect(); 147 | return new Promise((resolve, reject) => { 148 | let blockRegistration; 149 | const cb = block => { 150 | clearTimeout(responseTimeout); 151 | eh.unregisterBlockEvent(blockRegistration); 152 | if(block.data.data.length === 1) { 153 | const channelHeader = block.data.data[0].payload.header.channel_header; 154 | if(channelHeader.channel_id === this._channelName) { 155 | resolve(); 156 | } else { 157 | reject(new Error('Peer did not join an expected channel.')); 158 | } 159 | } 160 | }; 161 | blockRegistration = eh.registerBlockEvent(cb); 162 | const responseTimeout = setTimeout(() => { 163 | eh.unregisterBlockEvent(blockRegistration); 164 | reject(new Error('Peer did not respond in a timely fashion!')); 165 | }, JOIN_TIMEOUT); 166 | }); 167 | }); 168 | const completedPromise = joinedChannelPromises.concat([ 169 | this._channel.joinChannel(request) 170 | ]); 171 | await Promise.all(completedPromise); 172 | } catch(e) { 173 | console.log(`Error joining peer to channel. Error: ${e.message}`); 174 | throw e; 175 | } 176 | } 177 | async checkChannelMembership() { 178 | try { 179 | const { 180 | channels 181 | } = await this._client.queryChannels(this._peers[0]); 182 | if(!Array.isArray(channels)) { 183 | return false; 184 | } 185 | return channels.some(({ 186 | channel_id 187 | }) => channel_id === this._channelName); 188 | } catch(e) { 189 | return false; 190 | } 191 | } 192 | async checkInstalled(chaincodeId, chaincodeVersion, chaincodePath) { 193 | let { 194 | chaincodes 195 | } = await this._channel.queryInstantiatedChaincodes(); 196 | if(!Array.isArray(chaincodes)) { 197 | return false; 198 | } 199 | return chaincodes.some(cc => cc.name === chaincodeId && cc.path === chaincodePath && cc.version === chaincodeVersion); 200 | } 201 | async install(chaincodeId, chaincodeVersion, chaincodePath) { 202 | const request = { 203 | targets: this._peers, 204 | chaincodePath, 205 | chaincodeId, 206 | chaincodeVersion 207 | }; 208 | // Make install proposal to all peers 209 | let results; 210 | try { 211 | results = await this._client.installChaincode(request); 212 | } catch(e) { 213 | console.log(`Error sending install proposal to peer! Error: ${e.message}`); 214 | throw e; 215 | } 216 | const proposalResponses = results[0]; 217 | const allGood = proposalResponses.every(pr => pr.response && pr.response.status == 200); 218 | return allGood; 219 | } 220 | async instantiate(chaincodeId, chaincodeVersion, chaincodePath, ...args) { 221 | let proposalResponses, proposal; 222 | const txId = this._client.newTransactionID(); 223 | try { 224 | const request = { 225 | chaincodeType: 'golang', 226 | chaincodePath, 227 | chaincodeId, 228 | chaincodeVersion, 229 | fcn: 'init', 230 | args: utils.marshalArgs(args), 231 | txId 232 | }; 233 | const results = await this._channel.sendInstantiateProposal(request, 100000); 234 | proposalResponses = results[0]; 235 | proposal = results[1]; 236 | let allGood = proposalResponses.every(pr => pr.response && pr.response.status == 200); 237 | if(!allGood) { 238 | throw new Error(`Proposal rejected by some (all) of the peers: ${proposalResponses}`); 239 | } 240 | } catch(e) { 241 | throw e; 242 | } 243 | try { 244 | const request = { 245 | proposalResponses, 246 | proposal 247 | }; 248 | const deployId = txId.getTransactionID(); 249 | const transactionCompletePromises = this._eventHubs.map(eh => { 250 | eh.connect(); 251 | return new Promise((resolve, reject) => { 252 | // Set timeout for the transaction response from the current peer 253 | const responseTimeout = setTimeout(() => { 254 | eh.unregisterTxEvent(deployId); 255 | reject(new Error('Peer did not respond in a timely fashion!')); 256 | }, TRANSACTION_TIMEOUT); 257 | eh.registerTxEvent(deployId, (tx, code) => { 258 | clearTimeout(responseTimeout); 259 | eh.unregisterTxEvent(deployId); 260 | if(code != 'VALID') { 261 | reject(new Error(`Peer has rejected transaction with code: ${code}`)); 262 | } else { 263 | resolve(); 264 | } 265 | }); 266 | }); 267 | }); 268 | transactionCompletePromises.push(this._channel.sendTransaction(request)); 269 | await transactionCompletePromises; 270 | } catch(e) { 271 | throw e; 272 | } 273 | } 274 | async getBlocks(currentBlock, noOfLastBlocks) { 275 | if(currentBlock === 0) { 276 | return []; 277 | } 278 | if(!currentBlock) { 279 | currentBlock = -1; 280 | } 281 | if(!noOfLastBlocks) { 282 | noOfLastBlocks = 10; 283 | } 284 | currentBlock = typeof currentBlock !== 'number' ? Number(currentBlock) : currentBlock; 285 | noOfLastBlocks = typeof noOfLastBlocks !== 'number' ? Number(noOfLastBlocks) : noOfLastBlocks; 286 | var { 287 | height 288 | } = await this._channel.queryInfo(); 289 | if(currentBlock == -1) { 290 | currentBlock = height; 291 | } 292 | if(height.comp(currentBlock) >= 0) { 293 | height = Long.fromNumber(currentBlock, height.unsigned); 294 | } 295 | let blockCount; 296 | if(height.comp(noOfLastBlocks) > 0) { 297 | blockCount = Long.fromNumber(noOfLastBlocks, height.unsigned); 298 | } else { 299 | blockCount = height; 300 | } 301 | blockCount = blockCount.toNumber(); 302 | const queryBlock = this._channel.queryBlock.bind(this._channel); 303 | const blockPromises = {}; 304 | blockPromises[Symbol.iterator] = function* () { 305 | for(let i = 1; i <= blockCount; i++) { 306 | yield queryBlock(height.sub(i).toNumber()); 307 | } 308 | }; 309 | const blocks = await Promise.all([...blockPromises]); 310 | return blocks.map(utils.unmarshalBlock); 311 | } 312 | async registerAndEnroll(enrollmentID) { 313 | try { 314 | if(!enrollmentID && enrollmentID === "") { 315 | throw new Error(`Invalid User Id`); 316 | } 317 | //let adminUser = await this._client.getUserContext('admin', true); 318 | let adminUser = this._adminUser; 319 | if(!adminUser && !adminUser.isEnrolled()) { 320 | throw new Error(`Admin user not present to register user : ` + enrollmentID); 321 | } 322 | return enrollUser(this._client, enrollmentID, "", this._ca, { 323 | mspId: this._caConfig.mspId, 324 | adminUser: adminUser, 325 | affiliationOrg: this._peerConfig.org, 326 | noOfAttempts: 3 327 | }); 328 | } catch(e) { 329 | throw e; 330 | } 331 | } 332 | async getTransactionDetails(txId) { 333 | try { 334 | var transactionData = await this._channel.queryTransaction(txId); 335 | transactionData = transactionData ? transactionData.transactionEnvelope.payload.data.actions : ""; 336 | var execution_response = transactionData !== "" ? transactionData[0].payload.action.proposal_response_payload.extension.response : ""; 337 | return { 338 | txId: txId, 339 | results: execution_response 340 | }; 341 | } catch(e) { 342 | throw e; 343 | } 344 | } 345 | } -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const SECRETS_DIR = '../configuration'; 4 | 5 | function readConfig() { 6 | if(fs.existsSync(SECRETS_DIR)) { 7 | const data = JSON.parse(fs.readFileSync(path.resolve(SECRETS_DIR, 'config.json')).toString()); 8 | data.channelConfig = fs.readFileSync(path.resolve(SECRETS_DIR, 'channel.tx')); 9 | return data; 10 | } 11 | } 12 | const config = readConfig(); 13 | export default config; -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./testNetwork.js'); -------------------------------------------------------------------------------- /test/indexCouchDB.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('./testNetworkCouchDB.js'); -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blockchain", 3 | "version": "1.0.0", 4 | "description": "Setup Blockchain for fitcoin network", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "babel-core": "^6.26.0", 13 | "babel-polyfill": "^6.26.0", 14 | "babel-preset-env": "^1.6.1", 15 | "events": "^1.1.1", 16 | "fabric-ca-client": "latest", 17 | "fabric-client": "latest", 18 | "fs": "0.0.1-security", 19 | "fs-extra": "^5.0.0", 20 | "grpc": "^1.8.0", 21 | "http": "0.0.0", 22 | "json-style-converter": "^1.0.3", 23 | "long": "^3.2.0", 24 | "nano": "^6.4.2", 25 | "path": "^0.12.7", 26 | "url": "^0.11.0", 27 | "util": "^0.10.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/testNetwork.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import config from './config.js'; 3 | import { 4 | OrganizationClient 5 | } from './set-up/client'; 6 | import invokeFunc from './set-up/invoke'; 7 | import queryFunc from './set-up/query'; 8 | const shopClient = new OrganizationClient(config.channelName, config.orderer, config.peers[0].peer, config.peers[0].ca, config.peers[0].admin); 9 | (async () => { 10 | try { 11 | await Promise.all([shopClient.login()]); 12 | } catch(e) { 13 | console.log('Fatal error logging into blockchain organization clients!'); 14 | console.log(e); 15 | process.exit(-1); 16 | } 17 | await Promise.all([shopClient.initEventHubs()]); 18 | var results = await invokeFunc("admin", shopClient, config.chaincodeId, config.chaincodeVersion, "createMember", ["userA", "user"]); 19 | console.log("Invoke Results:"); 20 | console.log(results); 21 | results = await queryFunc("admin", shopClient, config.chaincodeId, config.chaincodeVersion, "getState", ["userA"]) 22 | console.log("Query Results:"); 23 | console.log(results); 24 | })(); -------------------------------------------------------------------------------- /test/testNetworkCouchDB.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import config from './config.js'; 3 | import { 4 | OrganizationClient 5 | } from './client'; 6 | import invokeFunc from './set-up/invoke'; 7 | import queryFunc from './set-up/query'; 8 | const shopClient = new OrganizationClient(config.channelName, config.orderer, config.peers[0].peer, config.peers[0].ca, config.peers[0].admin); 9 | (async () => { 10 | try { 11 | await Promise.all([shopClient.login()]); 12 | } catch(e) { 13 | console.log('Fatal error logging into blockchain organization clients!'); 14 | console.log(e); 15 | process.exit(-1); 16 | } 17 | await Promise.all([shopClient.initEventHubs()]); 18 | var results = await invokeFunc("admin", shopClient, config.chaincodeId, config.chaincodeVersion, "createMember", ["userA", "user"]); 19 | console.log("Invoke Results:"); 20 | console.log(results); 21 | results = await queryFunc("admin", shopClient, config.chaincodeId, config.chaincodeVersion, "getState", ["userA"]) 22 | console.log("Query Results:"); 23 | console.log(results); 24 | })(); --------------------------------------------------------------------------------