├── index.js ├── images ├── offer.png ├── IdentityA.png ├── admintab.png ├── closebid.png ├── newowner.png ├── selectid.png ├── ComposerMenu.png ├── IDRegistry.png ├── addproduct.png ├── addproduct1.png ├── addtowallet.png ├── idtowallet.png ├── soldlisting.png ├── transactions.png ├── IssueIDScreen.png ├── generateNewId.png ├── productoffers.png ├── select-member.png ├── ComposerPlayground.jpg ├── WalletTab-Selection.png ├── arch-smart-contract.png └── GettingStartedWComposer-arch-diagram.png ├── CONTRIBUTING.md ├── .gitignore ├── package.json ├── .eslintrc.yml ├── models └── product.cto ├── MAINTAINERS.md ├── permissions.acl ├── lib └── logic.js ├── README-cn.md ├── README-ko.md ├── LICENSE ├── test └── productAuction.js └── README.md /index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/offer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/offer.png -------------------------------------------------------------------------------- /images/IdentityA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/IdentityA.png -------------------------------------------------------------------------------- /images/admintab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/admintab.png -------------------------------------------------------------------------------- /images/closebid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/closebid.png -------------------------------------------------------------------------------- /images/newowner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/newowner.png -------------------------------------------------------------------------------- /images/selectid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/selectid.png -------------------------------------------------------------------------------- /images/ComposerMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/ComposerMenu.png -------------------------------------------------------------------------------- /images/IDRegistry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/IDRegistry.png -------------------------------------------------------------------------------- /images/addproduct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/addproduct.png -------------------------------------------------------------------------------- /images/addproduct1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/addproduct1.png -------------------------------------------------------------------------------- /images/addtowallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/addtowallet.png -------------------------------------------------------------------------------- /images/idtowallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/idtowallet.png -------------------------------------------------------------------------------- /images/soldlisting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/soldlisting.png -------------------------------------------------------------------------------- /images/transactions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/transactions.png -------------------------------------------------------------------------------- /images/IssueIDScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/IssueIDScreen.png -------------------------------------------------------------------------------- /images/generateNewId.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/generateNewId.png -------------------------------------------------------------------------------- /images/productoffers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/productoffers.png -------------------------------------------------------------------------------- /images/select-member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/select-member.png -------------------------------------------------------------------------------- /images/ComposerPlayground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/ComposerPlayground.jpg -------------------------------------------------------------------------------- /images/WalletTab-Selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/WalletTab-Selection.png -------------------------------------------------------------------------------- /images/arch-smart-contract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/arch-smart-contract.png -------------------------------------------------------------------------------- /images/GettingStartedWComposer-arch-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/BlockchainSmartContractTrading-CompositeJourney/HEAD/images/GettingStartedWComposer-arch-diagram.png -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This is an open source project, and we appreciate your help! 4 | 5 | We use the GitHub issue tracker to discuss new features and non-trivial bugs. 6 | 7 | In addition to the issue tracker, [#journeys on 8 | Slack](https://dwopen.slack.com) is the best way to get into contact with the 9 | project's maintainers. 10 | 11 | To contribute code, documentation, or tests, please submit a pull request to 12 | the GitHub repository. Generally, we expect two maintainers to review your pull 13 | request before it is approved for merging. For more details, see the 14 | [MAINTAINERS](MAINTAINERS.md) page. 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # JSDoc 40 | out 41 | 42 | # Mac files. 43 | **/.DS_Store 44 | 45 | *.swp 46 | 47 | # Build generated files should be ignored by git, but not by npm. 48 | dist 49 | 50 | node_modules 51 | 52 | composer-logs 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product-auction", 3 | "version": "0.0.1", 4 | "description": "Sample product auction network", 5 | "scripts": { 6 | "prepublish": "mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/product-auction.bna", 7 | "test": "nyc mocha -t 0 test/*.js" 8 | }, 9 | "author": "Admin", 10 | "email": "admin@example.org", 11 | "license": "Apache-2.0", 12 | "devDependencies": { 13 | "browserfs": "latest", 14 | "chai": "latest", 15 | "chai-as-promised": "latest", 16 | "composer-cli": "^0.20.5", 17 | "composer-common": "^0.20.5", 18 | "composer-admin": "^0.20.5", 19 | "composer-client": "^0.20.5", 20 | "composer-connector-embedded": "^0.20.5", 21 | "eslint": "latest", 22 | "istanbul": "latest", 23 | "mkdirp": "latest", 24 | "mocha": "latest", 25 | "nyc": "latest" 26 | }, 27 | "dependencies": { 28 | "cryptiles": "^4.1.3", 29 | "lodash": "^4.17.11" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | mocha: true 4 | extends: 'eslint:recommended' 5 | parserOptions: 6 | sourceType: 7 | - script 8 | globals: 9 | getFactory: true 10 | getSerializer: true 11 | getAssetRegistry: true 12 | getParticipantRegistry: true 13 | getCurrentParticipant: true 14 | post: true 15 | emit: true 16 | rules: 17 | indent: 18 | - error 19 | - 4 20 | linebreak-style: 21 | - error 22 | - unix 23 | quotes: 24 | - error 25 | - single 26 | semi: 27 | - error 28 | - always 29 | no-unused-vars: 30 | - 0 31 | - args: none 32 | no-console: off 33 | curly: error 34 | eqeqeq: error 35 | no-throw-literal: error 36 | strict: error 37 | dot-notation: error 38 | no-tabs: error 39 | no-trailing-spaces: error 40 | no-useless-call: error 41 | no-with: error 42 | operator-linebreak: error 43 | require-jsdoc: 44 | - error 45 | - require: 46 | ClassDeclaration: true 47 | MethodDefinition: true 48 | FunctionDeclaration: true 49 | yoda: error 50 | no-confusing-arrow: 2 51 | no-constant-condition: 2 52 | -------------------------------------------------------------------------------- /models/product.cto: -------------------------------------------------------------------------------- 1 | /** 2 | /** 3 | * Defines a data model for a product auction 4 | */ 5 | namespace org.acme.product.auction 6 | 7 | enum ListingState { 8 | o FOR_SALE 9 | o RESERVE_NOT_MET 10 | o SOLD 11 | } 12 | 13 | asset Product identified by productId { 14 | o String productId 15 | o String description 16 | --> User owner 17 | } 18 | 19 | asset ProductListing identified by listingId { 20 | o String listingId 21 | o Double reservePrice 22 | o ListingState state 23 | o Offer[] offers optional 24 | --> Product product 25 | } 26 | 27 | abstract participant User identified by email { 28 | o String email 29 | o Double balance 30 | o Product[] products 31 | } 32 | 33 | /** 34 | * A Seller participant 35 | */ 36 | participant Seller extends User { 37 | o String organisation 38 | } 39 | 40 | /** 41 | * A Member participant 42 | */ 43 | participant Member extends User { 44 | o String firstName 45 | o String lastName 46 | } 47 | 48 | /** 49 | * Transactions performed on network 50 | */ 51 | 52 | transaction Offer { 53 | o Double bidPrice 54 | --> ProductListing listing 55 | --> User member 56 | } 57 | 58 | transaction CloseBidding { 59 | --> ProductListing listing 60 | } 61 | 62 | transaction StartBidding { 63 | o String listingId 64 | o Double reservePrice 65 | --> Product product 66 | } 67 | 68 | transaction AddProduct { 69 | o String productId 70 | o String description 71 | --> Seller owner 72 | } 73 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers Guide 2 | 3 | This guide is intended for maintainers - anybody with commit access to one or 4 | more Code Pattern repositories. 5 | 6 | ## Methodology 7 | 8 | This repository does not have a traditional release management cycle, but 9 | should instead be maintained as a useful, working, and polished reference at 10 | all times. While all work can therefore be focused on the master branch, the 11 | quality of this branch should never be compromised. 12 | 13 | The remainder of this document details how to merge pull requests to the 14 | repositories. 15 | 16 | ## Merge approval 17 | 18 | The project maintainers use LGTM (Looks Good To Me) in comments on the pull 19 | request to indicate acceptance prior to merging. A change requires LGTMs from 20 | two project maintainers. If the code is written by a maintainer, the change 21 | only requires one additional LGTM. 22 | 23 | ## Reviewing Pull Requests 24 | 25 | We recommend reviewing pull requests directly within GitHub. This allows a 26 | public commentary on changes, providing transparency for all users. When 27 | providing feedback be civil, courteous, and kind. Disagreement is fine, so long 28 | as the discourse is carried out politely. If we see a record of uncivil or 29 | abusive comments, we will revoke your commit privileges and invite you to leave 30 | the project. 31 | 32 | During your review, consider the following points: 33 | 34 | ### Does the change have positive impact? 35 | 36 | Some proposed changes may not represent a positive impact to the project. Ask 37 | whether or not the change will make understanding the code easier, or if it 38 | could simply be a personal preference on the part of the author (see 39 | [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding)). 40 | 41 | Pull requests that do not have a clear positive impact should be closed without 42 | merging. 43 | 44 | ### Do the changes make sense? 45 | 46 | If you do not understand what the changes are or what they accomplish, ask the 47 | author for clarification. Ask the author to add comments and/or clarify test 48 | case names to make the intentions clear. 49 | 50 | At times, such clarification will reveal that the author may not be using the 51 | code correctly, or is unaware of features that accommodate their needs. If you 52 | feel this is the case, work up a code sample that would address the pull 53 | request for them, and feel free to close the pull request once they confirm. 54 | 55 | ### Does the change introduce a new feature? 56 | 57 | For any given pull request, ask yourself "is this a new feature?" If so, does 58 | the pull request (or associated issue) contain narrative indicating the need 59 | for the feature? If not, ask them to provide that information. 60 | 61 | Are new unit tests in place that test all new behaviors introduced? If not, do 62 | not merge the feature until they are! Is documentation in place for the new 63 | feature? (See the documentation guidelines). If not do not merge the feature 64 | until it is! Is the feature necessary for general use cases? Try and keep the 65 | scope of any given component narrow. If a proposed feature does not fit that 66 | scope, recommend to the user that they maintain the feature on their own, and 67 | close the request. You may also recommend that they see if the feature gains 68 | traction among other users, and suggest they re-submit when they can show such 69 | support. 70 | -------------------------------------------------------------------------------- /permissions.acl: -------------------------------------------------------------------------------- 1 | /** 2 | * Access Control List for the bidding network. 3 | */ 4 | 5 | rule MembersCanViewALLData { 6 | description: "Allow all participants read access to all resources" 7 | participant(m): "org.acme.product.auction.User" 8 | operation: READ 9 | resource(v): "org.acme.product.auction.*" 10 | condition: (v.getIdentifier() == m.getIdentifier()) 11 | action: ALLOW 12 | } 13 | 14 | rule SellerCanViewMemberData { 15 | description: "Allow all participants read access to all resources" 16 | participant: "org.acme.product.auction.Seller" 17 | operation: READ 18 | resource: "org.acme.product.auction.Member" 19 | action: ALLOW 20 | } 21 | 22 | rule SellerCanUpdateData { 23 | description: "Allow all seller access to all resources" 24 | participant(m): "org.acme.product.auction.Seller" 25 | operation: ALL 26 | resource(v): "org.acme.product.auction.Seller" 27 | condition: (v.getIdentifier() == m.getIdentifier()) 28 | action: ALLOW 29 | } 30 | 31 | rule ProductView { 32 | description: "Allow Users all access to their products" 33 | participant: "org.acme.product.auction.User" 34 | operation: READ 35 | resource: "org.acme.product.auction.Product" 36 | action: ALLOW 37 | } 38 | 39 | rule ProductAccess { 40 | description: "Allow Users all access to their products" 41 | participant(m): "org.acme.product.auction.User" 42 | operation: ALL 43 | resource(v): "org.acme.product.auction.Product" 44 | condition: (v.owner.getIdentifier() == m.getIdentifier()) 45 | action: ALLOW 46 | } 47 | 48 | rule ProductListingView { 49 | description: "Allow the owner of a product total access to their listing" 50 | participant: "org.acme.product.auction.User" 51 | operation: READ 52 | resource: "org.acme.product.auction.ProductListing" 53 | action: ALLOW 54 | } 55 | 56 | rule ProductListingOwner { 57 | description: "Allow the owner of a product total access to their listing" 58 | participant(m): "org.acme.product.auction.User" 59 | operation: ALL 60 | resource(v): "org.acme.product.auction.ProductListing" 61 | condition: (v.product.owner.getIdentifier() == m.getIdentifier()) 62 | action: ALLOW 63 | } 64 | 65 | rule AddProduct{ 66 | description: "Allow Sellers to add new product" 67 | participant(m): "org.acme.product.auction.Seller" 68 | operation: CREATE 69 | resource(v): "org.acme.product.auction.AddProduct" 70 | condition: (v.owner.getIdentifier() == m.getIdentifier()) 71 | action: ALLOW 72 | } 73 | 74 | rule MakeOffer{ 75 | description: "Allow members to bid for the product" 76 | participant(m): "org.acme.product.auction.User" 77 | operation: CREATE 78 | resource(v): "org.acme.product.auction.Offer" 79 | condition: (v.member.getIdentifier() == m.getIdentifier()) 80 | action: ALLOW 81 | } 82 | 83 | rule MembersCanUpdateProductListing { 84 | description: "Allow the members to update their bid for the listing" 85 | participant: "org.acme.product.auction.User" 86 | operation: UPDATE 87 | resource: "org.acme.product.auction.ProductListing" 88 | action: ALLOW 89 | } 90 | 91 | rule StartBiddingProcessTransaction { 92 | description: "Allow owner of product to start the bidding" 93 | participant(m): "org.acme.product.auction.User" 94 | operation: CREATE 95 | resource(v): "org.acme.product.auction.StartBidding" 96 | condition: (v.product.owner.getIdentifier() == m.getIdentifier()) 97 | action: ALLOW 98 | } 99 | 100 | rule CloseBiddingProcessTransaction { 101 | description: "Allow owner of product to close the bidding" 102 | participant(m): "org.acme.product.auction.User" 103 | operation: CREATE 104 | resource(v): "org.acme.product.auction.CloseBidding" 105 | condition: (v.listing.product.owner.getIdentifier() == m.getIdentifier()) 106 | action: ALLOW 107 | } 108 | 109 | rule CloseBiddingProcessTransactionUpdate { 110 | description: "Allow members to bid for the product" 111 | participant(m): "org.acme.product.auction.User" 112 | operation: UPDATE 113 | resource(v): "org.acme.product.auction.User" 114 | transaction(tx): "org.acme.product.auction.CloseBidding" 115 | condition: (tx.listing.product.owner.getIdentifier() == m.getIdentifier()) 116 | action: ALLOW 117 | } 118 | 119 | rule SystemACL { 120 | description: "System ACL to permit all access" 121 | participant: "org.hyperledger.composer.system.Participant" 122 | operation: ALL 123 | resource: "org.hyperledger.composer.system.**" 124 | action: ALLOW 125 | } 126 | 127 | rule NetworkAdminUser { 128 | description: "Grant business network administrators full access to user resources" 129 | participant: "org.hyperledger.composer.system.NetworkAdmin" 130 | operation: ALL 131 | resource: "**" 132 | action: ALLOW 133 | } 134 | 135 | rule NetworkAdminSystem { 136 | description: "Grant business network administrators full access to system resources" 137 | participant: "org.hyperledger.composer.system.NetworkAdmin" 138 | operation: ALL 139 | resource: "org.hyperledger.composer.system.**" 140 | action: ALLOW 141 | } 142 | -------------------------------------------------------------------------------- /lib/logic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Write your transction processor functions here 4 | */ 5 | /* 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * 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, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | var NS = 'org.acme.product.auction'; 19 | /** 20 | * Close the bidding for product listing and choose the 21 | * highest bid that is over the asking price 22 | * @param {org.acme.product.auction.CloseBidding} closeBidding - the closeBidding transaction 23 | * @transaction 24 | */ 25 | function closeBidding(closeBidding) { 26 | var listing = closeBidding.listing; 27 | if(listing.state !== 'FOR_SALE') { 28 | throw new Error('Listing is not FOR SALE'); 29 | } 30 | // by default we mark the listing as RESERVE_NOT_MET 31 | listing.state = 'RESERVE_NOT_MET'; 32 | var oldOwner = listing.product.owner.email; 33 | var highestOffer = null; 34 | var buyer = null; 35 | var seller = listing.product.owner; 36 | if(listing.offers && listing.offers.length > 0) { 37 | // sort the bids by bidPrice 38 | listing.offers.sort(function(a, b) { 39 | return(b.bidPrice - a.bidPrice); 40 | }); 41 | highestOffer = listing.offers[0]; 42 | if(highestOffer.bidPrice >= listing.reservePrice) { 43 | buyer = highestOffer.member; 44 | //seller = listing.owner; 45 | // update the balance of the seller 46 | seller.balance += highestOffer.bidPrice; 47 | // update the balance of the buyer 48 | buyer.balance -= highestOffer.bidPrice; 49 | // transfer the product to the buyer 50 | listing.product.owner = buyer; 51 | // Clear the offers 52 | //listing.offers = null; 53 | // mark the listing as SOLD 54 | listing.state = 'SOLD'; 55 | } 56 | } 57 | listing.product.owner.products.push(listing.product); 58 | return getParticipantRegistry(NS + '.Seller').then(function(sellerRegistry) { 59 | // update seller 60 | return sellerRegistry.update(seller); 61 | }).then(function() { 62 | if(listing.state === 'SOLD') { 63 | return getParticipantRegistry(NS + '.Member').then(function(memberRegistry) { 64 | return memberRegistry.update(buyer); 65 | }); 66 | } 67 | }).then(function() { 68 | return getAssetRegistry(NS + '.Product'); 69 | }).then(function(productRegistry) { 70 | // remove the listing 71 | return productRegistry.update(listing.product); 72 | }).then(function() { 73 | return getAssetRegistry(NS + '.ProductListing'); 74 | }).then(function(productListingRegistry) { 75 | // remove the listing 76 | return productListingRegistry.update(listing); 77 | }); 78 | } 79 | /** 80 | * Make an Offer for a ProductListing 81 | * @param {org.acme.product.auction.Offer} offer - the offer 82 | * @transaction 83 | */ 84 | function makeOffer(offer) { 85 | var listing = offer.listing; 86 | if(listing.state !== 'FOR_SALE') { 87 | throw new Error('Listing is not FOR SALE'); 88 | } 89 | if(offer.bidPrice < listing.reservePrice) { 90 | throw new Error('Bid amount less than reserve amount!!'); 91 | } 92 | if(offer.member.balance < offer.bidPrice) { 93 | throw new Error('Insufficient fund for bid. Please verify the placed bid!!'); 94 | } 95 | return getAssetRegistry(NS + '.ProductListing').then(function(productListingRegistry) { 96 | // save the product listing 97 | listing.offers.push(offer); 98 | return productListingRegistry.update(listing); 99 | }); 100 | } 101 | /** 102 | * Create a new listing 103 | * @param {org.acme.product.auction.StartBidding} publishListing - the listing transaction 104 | * @transaction 105 | */ 106 | function publishListing(listing) { 107 | listing.product.owner.products = listing.product.owner.products.filter(function(object) { 108 | return object.getIdentifier() !== listing.product.getIdentifier(); 109 | }); 110 | var productListing = null; 111 | var factory = getFactory(); 112 | return getAssetRegistry(NS + '.ProductListing').then(function(registry) { 113 | // Create the bond asset. 114 | productListing = factory.newResource(NS, 'ProductListing', listing.listingId); 115 | productListing.reservePrice = listing.reservePrice; 116 | productListing.state = 'FOR_SALE'; 117 | productListing.product = listing.product; 118 | productListing.offers = []; 119 | // Add the bond asset to the registry. 120 | return registry.add(productListing); 121 | }).then(function() { 122 | return getParticipantRegistry(NS + '.Seller'); 123 | }).then(function(sellerRegistry) { 124 | // save the buyer 125 | return sellerRegistry.update(listing.product.owner); 126 | }); 127 | } 128 | /** 129 | * Add new Product 130 | * @param {org.acme.product.auction.AddProduct} addProduct - new product addition 131 | * @transaction 132 | */ 133 | function addProduct(newproduct) { 134 | var product = getFactory().newResource(NS, 'Product', newproduct.productId); 135 | product.description = newproduct.description; 136 | product.owner = newproduct.owner; 137 | if(!product.owner.products) { 138 | product.owner.products = []; 139 | } 140 | product.owner.products.push(product); 141 | return getAssetRegistry(NS + '.Product').then(function(registry) { 142 | return registry.add(product); 143 | }).then(function() { 144 | return getParticipantRegistry(NS + '.Seller'); 145 | }).then(function(sellerRegistry) { 146 | return sellerRegistry.update(newproduct.owner); 147 | }); 148 | } -------------------------------------------------------------------------------- /README-cn.md: -------------------------------------------------------------------------------- 1 | # Hyperledger Composer - 产品拍卖网络 2 | *阅读本文的其他语言版本:[English](README.md)。* 3 | 4 | 欢迎学习 Hyperledger Composer Composite 学习之旅的第 2 部分。这是 [Composer 网络设置学习之旅的延续](https://github.com/IBM/BlockchainNetwork-CompositeJourney#build-your-first-hyperledger-network)。本次学习之旅将介绍使用 Composer 定义智能合约的更多复杂细节。您将学习如何向区块链应用程序添加多个参与者和访问控制。为此,您将创建一个交互的、分布式的产品拍卖演示网络。您将列出待售的资产(设置一个保留价格),观察满足保留价格的资产在拍卖结束时被自动转移给最高出价者。另外,根据 `permissions.acl` 文件中的访问控制规则 (ACL),每个参与者都有不同的访问权限级别。访问控制列表 (ACL) 是针对共享和隐私的设置,由 Fabric Composer 运行时自动执行。 5 | 6 | 7 | 这个业务网络定义了以下内容: 8 | 9 | **参与者:** 10 | `Member` `Seller` 11 | 12 | **资产:** 13 | `Product` `ProductListing` 14 | 15 | **事务:** 16 | `AddProduct` `StartBidding` `Offer` `CloseBidding` 17 | 18 | 在提交 `AddProduct` 事务时调用 `addProduct` 函数。该逻辑允许销售者创建一个产品资产并更新它的注册表。 19 | 20 | 在产品所有者提交 `StartBidding` 事务时调用 `publishListing` 函数。该逻辑允许销售者以包含其产品和保留价格的产品清单形式创建一个智能合约。 21 | 22 | 在提交 `Offer` 事务时调用 `makeOffer` 函数。该逻辑简单地检查针对该出价的清单是否仍待售,然后将该出价添加到清单中,然后更新 `ProductListing` 资产注册表中的出价。 23 | 24 | 在提交 `CloseBidding` 事务供处理时调用 `closeBidding` 函数。该逻辑检查该清单是否仍待售,按投标价格对出价排序,然后如果满足保留价格,则将与该清单相关的产品的所有权转移给最高投标者。资金从购买者的账户转移到销售者的账户,然后在各自的注册表中更新所有修改的资产。 25 | 26 | `models` 目录中的 `product.cto` 文件为产品拍卖演示定义一个数据模型,该模型包含资产、参与者和事务的定义。`lib` 目录中的 `logic.js` 文件实现 `product.cto` 文件中定义的事务。 回想一下,`.cto` 文件从资产、参与者和事务方面定义了您的业务网络的结构。 27 | 28 | ACL 规则包含在 `permissions.acl` 文件中,用于确定允许哪个用户/角色创建、读取、更新或删除业务网络的领域模型中的元素。默认 `System` 用户拥有所有权限。网络成员拥有所有资源的读取权限,销售者可以创建产品,开始和结束其产品的投标。网络成员可以对该产品清单进行投标。参与者只能访问允许的资源和事务。 29 | 30 | ## 包含的组件 31 | * Hyperledger Fabric 32 | * Hyperledger Composer 33 | * Docker 34 | 35 | ## 应用程序工作流图 36 | ![应用程序工作流](images/GettingStartedWComposer-arch-diagram.png) 37 | 38 | 创建多个事务并添加 ACL 39 | * 添加更多参与者 40 | * 添加访问控制列表 41 | * 查询和调用链代码 42 | 43 | ## 步骤 44 | 1. [生成 Business Network Archive (BNA)](#1-generate-the-business-network-archive-bna) 45 | 2. [使用 Composer Playground 部署 Business Network Archive](#2-deploy-the-business-network-archive-using-composer-playground) 46 | 3. [将 Business Network Archive 部署到在本地运行的 Hyperledger Composer 上](#3-deploy-the-business-network-archive-on-hyperledger-composer-running-locally) 47 | 48 | ## 1.生成 Business Network Archive (BNA) 49 | 50 | 要检查文件的结构是否有效,现在可以为您的业务网络定义生成一个 Business Network Archive (BNA) 文件。BNA 文件是可部署的单元 —— 一个可部署到 Composer 运行时上执行的文件。 51 | 52 | 使用以下命令生成网络归档文件: 53 | ```bash 54 | npm install 55 | ``` 56 | 您会看到以下输出: 57 | ```bash 58 | > mkdirp ./dist && composer archive create --sourceType dir --sourceName .-a ./dist/product-auction.bna 59 | 60 | Creating Business Network Archive 61 | 62 | Looking for package.json of Business Network Definition 63 | Input directory: /Users/ishan/Documents/git-demo/BlockchainBalanceTransfer-CompositeJourney 64 | 65 | Found: 66 | Description: Sample product auction network 67 | Name: product-auction 68 | Identifier: product-auction@0.0.1 69 | 70 | Written Business Network Definition Archive file to 71 | Output file: ./dist/product-auction.bna 72 | 73 | Command succeeded 74 | ``` 75 | `composer archive create` 命令在 `dist` 文件夹中创建了一个名为 `product-auction.bna` 的文件。 76 | 77 | 可以针对一个嵌入式运行时来测试业务网络定义,该运行时在一个 Node.js 进程中将“区块链”的状态存储在内存中。 78 | 从您的项目工作目录,打开 test/productAuction.js 文件并运行以下命令: 79 | ``` 80 | npm test 81 | ``` 82 | 您应该看到以下输出: 83 | ``` 84 | > product-auction@0.0.1 test /Users/ishan/Documents/git-demo/BlockchainBalanceTransfer-CompositeJourney 85 | > mocha --recursive 86 | 87 | ProductAuction - AddProduct Test 88 | #BiddingProcess 89 | ✓ Add the product to seller list (119ms) 90 | ✓ Authorized owner should start the bidding (90ms) 91 | ✓ Members bid for the product (127ms) 92 | ✓ Close bid for the product (53ms) 93 | 94 | 95 | 4 passing (2s) 96 | ``` 97 | 98 | ## 2.使用 Composer Playground 部署 Business Network Archive 99 | 100 | 打开 [Composer Playground](http://composer-playground.mybluemix.net/),其中已在默认情况下导入基本样本网络。 101 | 如果以前使用过 Playground,一定要在浏览器控制台中运行 `localStorage.clear()` 来清除浏览器本地存储。现在导入 `product-auction.bna` 文件并单击 deploy 按钮。 102 | 103 | 104 | 要测试 **Test** 选项卡中的这个业务网络定义,请执行以下步骤: 105 | 106 | 107 | 在 `Seller` 参与者注册表中,创建一个新参与者。确保单击了最左侧的 `Seller` 选项卡。 108 | 109 | ``` 110 | { 111 | "$class": "org.acme.product.auction.Seller", 112 | "organisation": "ACME", 113 | "email": "auction@acme.org", 114 | "balance": 100, 115 | "products": [] 116 | } 117 | ``` 118 | 119 | 在 `Member` 参与者注册表中,创建两个参与者。再次单击最左侧的 `Member` 选项卡。 120 | 121 | ``` 122 | { 123 | "$class": "org.acme.product.auction.Member", 124 | "firstName": "Amy", 125 | "lastName": "Williams", 126 | "email": "memberA@acme.org", 127 | "balance": 1000, 128 | "products": [] 129 | } 130 | ``` 131 | 132 | ``` 133 | { 134 | "$class": "org.acme.product.auction.Member", 135 | "firstName": "Billy", 136 | "lastName": "Thompson", 137 | "email": "memberB@acme.org", 138 | "balance": 1000, 139 | "products": [] 140 | } 141 | ``` 142 | 143 | 现在我们已准备好添加**访问控制**。为此,单击 `admin` 选项卡向参与者发放**新 ID**,并将这些 ID 添加到钱包中。 144 | 请按照以下图中所示的操作说明进行操作: 145 | 146 | * 单击 Option 2 下的 +add to my Wallet,实际添加到您的钱包。 147 | 148 | ![Admin 选项卡](images/admintab.png) 149 | 150 | ![生成新 ID](images/generateNewId.png) 151 | 152 | ![添加到钱包](images/addtowallet.png) 153 | 154 | ![将 ID 添加到钱包](images/idstowallet.png) 155 | 156 | 从 `Wallet tab` 选项卡中选择 `seller id`。现在单击 `test tab` 来执行 `AddProduct` 和 `StartBidding` 事务。 157 | 158 | ![选择 ID](images/selectid.png) 159 | 160 | 现在单击 `Submit Transaction` 按钮,从下拉列表中选择 `AddProduct` 事务为销售者创建一个产品。 161 | ![addproduct](images/addproduct.png) 162 | 163 | ``` 164 | { 165 | "$class": "org.acme.product.auction.AddProduct", 166 | "description": "Sample Product", 167 | "owner": "resource:org.acme.product.auction.Seller#auction@acme.org" 168 | } 169 | ``` 170 | 您可以检查产品和销售者注册表来验证事务。 171 | 172 | 要为上述产品创建一个产品清单,可以从产品注册表复制 `ProductID`。然后提交 `StartBidding` 事务。记得将 `` 替换为您刚复制的产品 ID。 173 | ``` 174 | { 175 | "$class": "org.acme.product.auction.StartBidding", 176 | "reservePrice": 50, 177 | "product": "resource:org.acme.product.auction.Product#" 178 | } 179 | ``` 180 | 181 | 您刚列出了用于拍卖的 `Sample Product`,它的保留价格为 50! 182 | 已在 `ProductListing` 注册表中为具有 `FOR_SALE` 状态的产品创建了一个清单。 183 | 184 | 现在成员参与者可以提交 `Offer` 事务来竞标一个产品清单。 185 | 186 | 对于每个 `member id`,从 `Wallet tab` 中选择用户 ID。要提交一个 `Offer` 事务,可以选择 `test tab` 并单击 `Submit Transaction` 按钮。 187 | > `ListingID` 是从 `ProductListing` 注册表复制的清单的 ID。 188 | 189 | ``` 190 | { 191 | "$class": "org.acme.product.auction.Offer", 192 | "bidPrice": 50, 193 | "listing": "resource:org.acme.product.auction.ProductListing#", 194 | "member": "resource:org.acme.product.auction.Member#memberA@acme.org" 195 | } 196 | ``` 197 | 198 | ``` 199 | { 200 | "$class": "org.acme.product.auction.Offer", 201 | "bidPrice": 100, 202 | "listing": "resource:org.acme.product.auction.ProductListing#", 203 | "member": "resource:org.acme.product.auction.Member#memberB@acme.org" 204 | } 205 | ``` 206 | 207 | 可以检查 `ProductListing` 注册表,查看该产品的所有投标。 208 | 209 | ![产品出价](images/productoffers.png) 210 | 211 | 现在再次从 `Wallet tab` 选项卡中选择 `seller id`。单击 `test tab`,为该清单提交一个 `CloseBidding` 事务来结束拍卖。 212 | 213 | ``` 214 | { 215 | "$class": "org.acme.product.auction.CloseBidding", 216 | "listing": "resource:org.acme.product.auction.ProductListing#" 217 | } 218 | ``` 219 | 220 | 这表明 `ListingID` 的拍卖现在已结束,并会触发上面介绍的 `closeBidding` 函数。 221 | 222 | 要检查产品是否售出,您需要单击 `ProductListing` 资产注册表并检查该产品的所有者。最高的投标是所有者 `memberB@acme.org` 投的,所以 `memberB@acme.org` 应是该产品的所有者。 223 | 224 | 可以检查具有 `` 的 ProductListing 的状态是否为 `SOLD`。 225 | 226 | ![产品清单已售](images/soldlisting.png) 227 | 228 | 单击 `Member` 资产注册表,以验证购买者和销售者余额已更新。该产品已添加到购买者 `memberB@acme.org` 的产品列表。 229 | 230 | ![产品的新所有者](images/newowner.png) 231 | 232 | 您可以选择 `All transactions` 选项卡来查看所有事务的历史记录。 233 | 234 | ![事务历史记录](images/transactions.png) 235 | 236 | > 也可以使用默认的 `System user` 来执行所有操作,因为我们在 `permissions.acl` 中有一条规则,用于为 `System user` 授予所有访问权。 237 | 238 | ## 3.将 Business Network Archive 部署到在本地运行的 Hyperledger Composer 上 239 | 240 | 请按照[操作说明](https://github.com/IBM/BlockchainNetwork-CompositeJourney#2-starting-hyperledger-fabric) 启动本地 Fabric。 241 | 现在将目录更改为包含 `product-auction.bna` 文件的 `dist` 文件夹并键入: 242 | ``` 243 | cd dist 244 | composer runtime install --card PeerAdmin@hlfv1 --businessNetworkName product-auction 245 | composer network start --card PeerAdmin@hlfv1 --networkAdmin admin --networkAdminEnrollSecret adminpw --archiveFile product-auction.bna --file networkadmin.card 246 | composer card import --file networkadmin.card 247 | ``` 248 | 249 | 可以键入以下命令来验证网络已部署: 250 | ``` 251 | composer network ping --card admin@product-auction 252 | ``` 253 | 254 | 您会看到以下输出: 255 | ``` 256 | The connection to the network was successfully tested: product-auction 257 | version: 0.16.0 258 | participant: org.hyperledger.composer.system.NetworkAdmin#admin 259 | 260 | Command succeeded 261 | ``` 262 | 263 | 要创建 REST API,需要启动 `composer-rest-server`,告诉它如何连接到我们已部署的业务网络。 264 | 现在启动该服务器,方法是将目录更改为 product-auction 文件夹并键入以下内容: 265 | ```bash 266 | cd .. 267 | composer-rest-server 268 | ``` 269 | 270 | 回答启动时提出的问题。这些信息使得 composer-rest-server 能连接到 Hyperledger Fabric,并配置如何生成 REST API。 271 | * 输入 `admin@product-auction` 作为卡名称。 272 | * 在询问是否在生成的 API 中使用名称空间时,选择 `never use namespaces`。 273 | * 在询问是否安全生成的 API 时,选择 `No`。 274 | * 在询问是否启用事件发布时,选择 `Yes`。 275 | * 在询问是否启用 TLS 安全性时,选择 `No`。 276 | 277 | **测试 REST API** 278 | 279 | 如果 composer-rest-server 成功启动,您会看到以下两行输出: 280 | ``` 281 | Web server listening at: http://localhost:3000 282 | Browse your REST API at http://localhost:3000/explorer 283 | ``` 284 | 285 | 打开 Web 浏览器并导航到 http://localhost:3000/explorer 286 | 287 | 您会看到 LoopBack API Explorer,可以检查和测试已生成的 REST API。按照上面的 Composer 部分给出的说明来测试业务网络定义。 288 | 289 | ## 准备执行第 3 步! 290 | 恭喜您 - 您已经完成这个综合学习之旅的第 2 步 - 请继续执行[第 3 步](https://github.com/IBM/BlockchainEvents-CompositeJourney). 291 | 292 | ## 附加资源 293 | * [Hyperledger Fabric 文档](http://hyperledger-fabric.readthedocs.io/en/latest/) 294 | * [Hyperledger Composer 文档](https://hyperledger.github.io/composer/introduction/introduction.html) 295 | 296 | ## 许可 297 | [Apache 2.0](LICENSE) 298 | -------------------------------------------------------------------------------- /README-ko.md: -------------------------------------------------------------------------------- 1 | # 하이퍼레저 컴포저 - 상품 경매 네트워크 2 | 3 | *다른 언어로 보기: [English](README.md).* 4 | 5 | 하이퍼레저 컴포저(Hyperledger Composer) Composite Journey의 Part 2에 오신 것을 환영합니다. 이번 세션은 [하이퍼레저 컴포저 네트워크 설정하기](https://github.com/IBM/BlockchainNetwork-CompositeJourney#build-your-first-hyperledger-network) 시리즈 중 하나입니다. 이 과정은 스마트 계약을 정의하기 위해 하이퍼레저 컴포저를 사용하는 좀 더 복잡한 내용을 다룹니다. 이 네트워크에 여러 참가자를 추가하고, 블록체인 애플리케이션에 대한 액세스 제어권한을 추가하는 방법을 배우게 됩니다. 이 기능들을 적용하기 위해 - 대화형의 분산된 제품 경매 데모 네트워크를 만들 것입니다. 판매할 자산(예비 가격 설정)을 리스트에 넣으면, 경매 종료 후 예비 가격을 설정한 자산이 자동으로 최고 입찰자에게 이전됩니다. 또한 각 참가자는 permissions.acl 파일의 액세스 제어 규칙에 따라 개별적인 액세스 권한을 갖습니다. 이 ACL(Access Control List) 파일은 패브릭 컴포저 런타임에 의해 자동 적용되는 공유 및 개인 정보 보호를 위한 설정입니다. 6 | 7 | 이 비즈니스 네트워크는 다음을 정의합니다: 8 | 9 | **참가자:** 10 | `Member` `Seller` 11 | 12 | **자산:** 13 | `Product` `ProductListing` 14 | 15 | **거래:** 16 | `AddProduct` `StartBidding` `Offer` `CloseBidding` 17 | 18 | `addProduct` 함수는  `AddProduct` 트랜잭션이 제출될 때 호출됩니다. 이 로직을 따라 판매자는 제품 자산을 작성하고 레지스트리를 업데이트할 수 있습니다. 19 | 20 | `publishListing` 함수는 제품의 소유자가 `StartBidding` 트랜잭션을 제출할 때 호출됩니다. 데모 화면상에서 판매자는 본인이 판매할 제품과 판매 시작가를 입력하여 스마트 계약을 생성할 수 있습니다. 21 | 22 | `Offer` 트렌잭션이 제출되면 `makeOffer` 함수가 호출됩니다. 이 로직은 오퍼 리스트가 아직 판매 중인지를 단순히 확인한 다음, 해당 오퍼를 목록에 추가한 후  `ProductListing` 자산 레지스트리의 오퍼를 업데이트합니다. 23 | `closeBidding` 트랜잭션이 처리를 위해 제출되면 `closeBidding` 함수가 호출됩니다. 이 로직은 해당 리스트가 아직 판매중인지 확인하고 입찰 가격으로 오퍼를 정렬한 다음 준비금이 맞으면, 리스트와 연결된 제품의 소유권을 최고 입찰자에게 이전합니다. 구매자의 계좌에서 판매자의 계좌로 돈이 전송된 후 수정된 모든 자산이 각각의 레지스트리에서 업데이트됩니다. 24 | 25 | `models` 디렉토리에있는 `product.cto` 파일은 자산, 참여자 및 트랜잭션에 대한 정의로 구성된 제품 경매 데모에 대한 데이터 모델을 정의합니다. `lib` 디렉토리에 있는 `logic.js` 파일은 `product.cto` 파일에 정의된 트랜잭션을 구현합니다. `.cto` 파일은 자산, 참여자 및 트랜잭션 측면에서 비즈니스 네트워크의 구조를 정의합니다. 26 | 27 | `permissions.acl` 파일에 위치한 ACL 규칙으로 비즈니스 네트워크의 도메인 모델의 한 요소를 작성, 읽기, 업데이트 또는 삭제할 수 있는 사용자/역할을 결정합니다. 기본 `System` 사용자에게는 모든 권한이 있습니다. 네트워크 구성원은 모든 리소스에 대한 읽기 권한을 가지며 판매자는 제품을 만들고 제품에 대한 입찰을 시작하고 종료할 수 있습니다. 네트워크 회원은 제품 리스트에 대한 입찰을 할 수 있습니다. 참여자는 허용된 자원 및 트랜잭션에만 액세스할 수 있습니다. 28 | 29 | ## 구성 요소 30 | * 하이퍼레저 패브릭 31 | * 하이퍼레저 컴포저 32 | * 도커 33 | 34 | ## 애플리케이션 워크플로우 도표 35 | ![애플리케이션 워크플로우](images/GettingStartedWComposer-arch-diagram.png) 36 | 37 | 여러 참가자 생성 및 ACL 추가 38 | * 추가적인 참가자 추가 39 | * Access Control Lists(엑세스 제어 리스트) 추가 40 | * 체인코드 쿼리 및 호출 41 | 42 | ## Steps 43 | 1. [비즈니스 네트워크 아카이브 (BNA) 생성하기](#1-비즈니스-네트워크-아카이브-bna-생성하기) 44 | 2. [컴포저 플레이그라운드를 사용하여 비즈니스 네트워크 아카이브 배포하기](#2-컴포저-플레이그라운드를-사용하여-비즈니스-네트워크-아카이브-배포하기) 45 | 3. [로컬에 있는 하이퍼레저 컴포저에 비즈니스 네트워크 아카이브 (BNA) 배포하기](#3-로컬에-있는-하이퍼레저-컴포저에-비즈니스-네트워크-아카이브-bna-배포하기) 46 | 47 | ## 1. 비즈니스 네트워크 아카이브 (BNA) 생성하기 48 | 49 | 파일 구조가 유효한지 확인하려면 비즈니스 네트워크 정의에 대한 BNA (Business Network Archive) 파일을 생성합니다. BNA 파일은 배포 가능한 유닛으로, 실행을 위해 하이퍼레저 컴포저 런타임에 배포할 수 있습니다. 50 | 51 | 다음 명령을 사용하여 네트워크 아카이브를 생성합니다: 52 | ```bash 53 | npm install 54 | ``` 55 | 다음과 같은 결과가 나옵니다: 56 | ```bash 57 | > mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/product-auction.bna 58 | 59 | Creating Business Network Archive 60 | 61 | Looking for package.json of Business Network Definition 62 | Input directory: /Users/ishan/Documents/git-demo/BlockchainBalanceTransfer-CompositeJourney 63 | 64 | Found: 65 | Description: Sample product auction network 66 | Name: product-auction 67 | Identifier: product-auction@0.0.1 68 | 69 | Written Business Network Definition Archive file to 70 | Output file: ./dist/product-auction.bna 71 | 72 | Command succeeded 73 | ``` 74 | `composer archive create` 명령을 사용하면 `dist`폴더 안에 `product-auction.bna` 파일이 생성됩니다. 75 | 76 | Node.js 프로세스에서 '블록체인' 인메모리 상태를 저장하는 임베디드 런타임을 통해, 생성한 비즈니스 네트워크를 테스트할 수 있습니다. 프로젝트 작업 디렉토리에서 test/productAuction.js 파일을 열고 다음 명령을 실행하십시오: 77 | ``` 78 | npm test 79 | ``` 80 | 다음과 같은 결과가 나옵니다 : 81 | ``` 82 | > product-auction@0.0.1 test /Users/ishan/Documents/git-demo/BlockchainBalanceTransfer-CompositeJourney 83 | > mocha --recursive 84 | 85 | ProductAuction - AddProduct Test 86 | #BiddingProcess 87 | ✓ Add the product to seller list (119ms) 88 | ✓ Authorized owner should start the bidding (90ms) 89 | ✓ Members bid for the product (127ms) 90 | ✓ Close bid for the product (53ms) 91 | 92 | 93 | 4 passing (2s) 94 | ``` 95 | 96 | ## 2. 컴포저 플레이그라운드를 사용하여 비즈니스 네트워크 아카이브 배포하기 97 | 98 | [컴포저 플레이그라운드](http://composer-playground.mybluemix.net/)를 열어서, 기본 샘플 네트워크를 가져옵니다. 99 | 이전에 플레이그라운드를 사용한 적이 있다면, 웹브라우저 콘솔에서 `localStorage.clear()` 을 사용해 웹브라우저 로컬 저장소를 지우십시오. 이제 `product-auction.bna` 파일을 가져와서 deploy 버튼을 클릭합니다. 100 | 101 | 102 | **테스트** 탭에서 이 비즈니스 네트워크 정의를 테스트하려면: 103 | 104 | 105 | `Seller` 참여자 레지스트리에서 새 참여자를 작성하십시오. 맨 왼쪽에 있는 `Seller` 탭을 클릭하십시오. 106 | 107 | ``` 108 | { 109 | "$class": "org.acme.product.auction.Seller", 110 | "organisation": "ACME", 111 | "email": "auction@acme.org", 112 | "balance": 100, 113 | "products": [] 114 | } 115 | ``` 116 | 117 | `Member` 참여자 레지스트리에서 두 명의 참가자를 만듭니다. 다시 왼쪽 끝에있는`Member` 탭을 클릭하십시오. 118 | 119 | ``` 120 | { 121 | "$class": "org.acme.product.auction.Member", 122 | "firstName": "Amy", 123 | "lastName": "Williams", 124 | "email": "memberA@acme.org", 125 | "balance": 1000, 126 | "products": [] 127 | } 128 | ``` 129 | 130 | ``` 131 | { 132 | "$class": "org.acme.product.auction.Member", 133 | "firstName": "Billy", 134 | "lastName": "Thompson", 135 | "email": "memberB@acme.org", 136 | "balance": 1000, 137 | "products": [] 138 | } 139 | ``` 140 | 141 | 이제 **Access Control**을 추가할 준비가 되었습니다. 먼저 `admin` 탭을 클릭하여 참가자에게 **새로운 ID**를 발급하고, 생성한 ID를 월렛에 추가하십시오. 아래 이미지와 같이 지침을 따르십시오: 142 | 143 | *실제로 월렛에 추가하려면 옵션 2에서 +add to my Wallet을 클릭하십시오. 144 | 145 | ![Admin Tab](images/admintab.png) 146 | 147 | ![Generate New Id](images/generateNewId.png) 148 | 149 | ![Add to Wallet](images/addtowallet.png) 150 | 151 | ![Ids to Wallet](images/idstowallet.png) 152 | 153 | `Wallet tab`에서 `seller id`를 선택하십시오. `test tab`을 클릭하여 `AddProduct` 및 `StartBidding` 트랜잭션을 수행합니다. 154 | 155 | ![Select Id](images/selectid.png) 156 | 157 | 이제 `Submit Transaction` 버튼을 클릭하여 드롭다운 박스 중에 `AddProduct` 트랜젝션을 선택하여 판매자용 상품을 생성합니다. 158 | ![addproduct](images/addproduct.png) 159 | 160 | ``` 161 | { 162 | "$class": "org.acme.product.auction.AddProduct", 163 | "description": "Sample Product", 164 | "owner": "resource:org.acme.product.auction.Seller#auction@acme.org" 165 | } 166 | ``` 167 | 제품 및 판매자 레지스트리를 확인하여 트랜잭션을 확인할 수 있습니다. 168 | 169 | 위의 제품에 대한 제품 목록을 작성하려면 제품 레지스트리에서 `ProductID`를 복사하십시오. 그런 다음 `StartBidding` 트랜잭션을 제출합니다. ``부분을 방금 복사한 제품 ID로 변경하여 제출하셔야 합니다. 170 | 171 | ``` 172 | { 173 | "$class": "org.acme.product.auction.StartBidding", 174 | "reservePrice": 50, 175 | "product": "resource:org.acme.product.auction.Product#" 176 | } 177 | ``` 178 | 179 | 귀하는 방금 `Sample Product`를 예비가격 50에 경매에 등록했습니다! 180 | 상태가 `FOR_SALE`인 제품들은 `ProductListing` 레지스트리에 목록이 생성됩니다. 181 | 182 | 이제 회원 참여자는 제품 리스트에 입찰하기 위해 `Offer` 트랜젝션을 제출할 수 있습니다. 183 | 184 | 각 `member id`에 대해 `Wallet tab`에서 사용자 ID를 선택합니다. `Offer` 트랜젝션을 제출하려면 `test tab` 선택하여 `Submit Transaction` 버튼을 클릭하십시오. 185 | > `ListingID`는 `ProductListing` 레지스트리에서 복사한 리스트의 ID입니다. 186 | 187 | ``` 188 | { 189 | "$class": "org.acme.product.auction.Offer", 190 | "bidPrice": 50, 191 | "listing": "resource:org.acme.product.auction.ProductListing#", 192 | "member": "resource:org.acme.product.auction.Member#memberA@acme.org" 193 | } 194 | ``` 195 | 196 | ``` 197 | { 198 | "$class": "org.acme.product.auction.Offer", 199 | "bidPrice": 100, 200 | "listing": "resource:org.acme.product.auction.ProductListing#", 201 | "member": "resource:org.acme.product.auction.Member#memberB@acme.org" 202 | } 203 | ``` 204 | 205 | `ProductListing` 레지스트리를 확인하면 제품의 모든 입찰 내역을 볼 수 있습니다. 206 | 207 | ![Product Offers](images/productoffers.png) 208 | 209 | 이제 다시 `Wallet tab`탭에서 `seller id`를 선택합니다. `test tab`을 클릭하여 목록에 대한 `CloseBidding` 트랜잭션을 제출하여 경매를 종료하십시오. 210 | 211 | ``` 212 | { 213 | "$class": "org.acme.product.auction.CloseBidding", 214 | "listing": "resource:org.acme.product.auction.ProductListing#" 215 | } 216 | ``` 217 | 218 | 이는 단순히 `ListingID` 대한 경매가 종료되어 앞서 설명한 `closeBidding`함수를 트리거합니다. 219 | 220 | 제품 판매 여부를 확인하려면 `ProductListing` 자산 레지스트리를 클릭하고 제품의 소유자를 확인해야 합니다. 최고 입찰액은 소유자인 `memberB@acme.org`가 제출했으므로 `memberB@acme.org`가 제품의 소유자여야 합니다. 221 | 222 | ``로 ProductListing의 상태가 `SOLD`인지 확인할 수 있습니다. 223 | 224 | ![Product Listing Sold](images/soldlisting.png) 225 | 226 | 구매자와 판매자의 업데이트된 자산 목록을 확인하려면 `Member` 자산 레지스트리를 클릭하십시오. 제품이 구매자인 `memberB@acme.org`의 제품 리스트에 추가됩니다. 227 | 228 | ![New Owner of Product](images/newowner.png) 229 | 230 | `All transactions` 탭을 선택하여 모든 트랜젝션의 내역을 볼 수 있습니다. 231 | 232 | ![Transaction History](images/transactions.png) 233 | 234 | > 또한 모든 `System user`에게 액세스를 허용하려면 `permissions.acl`에 정의된 엑세스 권한 리스트 중 기본 설정인 `System user`를 사용하여 모든 작업을 수행할 수 있습니다. 235 | 236 | ## 3. 로컬에 있는 하이퍼레저 컴포저에 비즈니스 네트워크 아카이브 (BNA) 배포하기 237 | 238 | [하이퍼레저 패브릭 시작하기](https://github.com/IBM/BlockchainNetwork-CompositeJourney/blob/master/README-ko.md#2-%ED%95%98%EC%9D%B4%ED%8D%BC%EB%A0%88%EC%A0%80-%ED%8C%A8%EB%B8%8C%EB%A6%AD-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0)가이드를 따라 로컬 패브릭을 시작하십시오. 이제 디렉토리를 `product-auction.bna`파일이 들어있는 `dist`폴더로 변경하고 다음을 입력하십시오: 239 | ``` 240 | cd dist 241 | composer runtime install --card PeerAdmin@hlfv1 --businessNetworkName product-auction 242 | composer network start --card PeerAdmin@hlfv1 --networkAdmin admin --networkAdminEnrollSecret adminpw --archiveFile product-auction.bna --file networkadmin.card 243 | composer card import --file networkadmin.card 244 | ``` 245 | 246 | 다음을 입력하여 네트워크 배포 여부를 확인할 수 있습니다: 247 | ``` 248 | composer network ping --card admin@product-auction 249 | ``` 250 | 251 | 다음과 같은 결과를 확인할 수 있습니다: 252 | ``` 253 | The connection to the network was successfully tested: product-auction 254 | version: 0.16.0 255 | participant: org.hyperledger.composer.system.NetworkAdmin#admin 256 | 257 | Command succeeded 258 | ``` 259 | 260 | REST API를 만들려면 Rest API Server인 `composer-rest-server`를 시작하여 배포된 비즈니스 네트워크의 접속 정보를 설정합니다. 261 | 이제 디렉토리를 제품 경매 폴더로 변경하고 다음을 입력하여 서버를 시작하십시오: 262 | ```bash 263 | cd .. 264 | composer-rest-server 265 | ``` 266 | 267 | 시작할 때 나타난 질문들에 답하십시오. 이를 통해 composer-rest-server는 하이퍼레저 패브릭에 연결하고 REST API 생성 방법을 구성할 수 있습니다. 268 | * 카드 이름으로 `admin@product-auction`를 입력하십시오. 269 | * 생성된 API에서 네임스페이스 사용 여부를 묻는다면 `never use namespaces`를 선택합니다. 270 | * 생성된 API의 보안 여부를 묻는다면 `No`를 선택합니다. 271 | * 이벤트 게시를 활성화할지 묻는다면 `Yes`를 선택합니다. 272 | * TLS 보안의 사용 여부를 뭍는다면 `No`를 선택합니다. 273 | 274 | **REST API 테스트** 275 | 276 | composer-rest-server가 성공적으로 시작된 경우, 다음 두 줄이 출력되어야 합니다: 277 | ``` 278 | Web server listening at: http://localhost:3000 279 | Browse your REST API at http://localhost:3000/explorer 280 | ``` 281 | 282 | 웹브라우저를 열어서 http://localhost:3000/explorer 로 이동합니다. 정상적으로 composer-rest-server가 구동되었다면, 웹브라우저에 REST API 리스트를 확인 할 수 있습니다. 283 | 284 | 생성된 REST API를 검사하고 테스트할 수 있도록 LoopBack API Explorer가 표시되어야 합니다. 위의 지시사항에 따라 컴포저 섹션에서 설명한대로 비즈니스 네트워크 정의(Business Network Definition)를 테스트하십시오. 285 | 286 | ## 3단계로 이동할 준비가 되었습니다! 287 | 축하합니다 - 2단계를 완수하셨습니다. 이제 [3단계](https://github.com/IBM/BlockchainEvents-CompositeJourney)로 이동합니다. 288 | 289 | ## 추가 리소스 290 | * [Hyperledger Fabric Docs](http://hyperledger-fabric.readthedocs.io/en/latest/) 291 | * [Hyperledger Composer Docs](https://hyperledger.github.io/composer/introduction/introduction.html) 292 | 293 | ## 라이센스 294 | [Apache 2.0](LICENSE) 295 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/productAuction.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 'use strict'; 15 | /** 16 | * Write the unit tests for your transction processor functions here 17 | */ 18 | const AdminConnection = require('composer-admin').AdminConnection; 19 | const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; 20 | const { 21 | BusinessNetworkDefinition, 22 | CertificateUtil, 23 | IdCard 24 | } = require('composer-common'); 25 | const path = require('path'); 26 | const chai = require('chai'); 27 | chai.should(); 28 | chai.use(require('chai-as-promised')); 29 | const namespace = 'org.acme.product.auction'; 30 | describe('#' + namespace, () => { 31 | // In-memory card store for testing so cards are not persisted to the file system 32 | const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore({ 33 | type: 'composer-wallet-inmemory' 34 | }); 35 | // Embedded connection used for local testing 36 | const connectionProfile = { 37 | name: 'embedded', 38 | 'x-type': 'embedded' 39 | }; 40 | // Name of the business network card containing the administrative identity for the business network 41 | const adminCardName = 'admin'; 42 | // Admin connection to the blockchain, used to deploy the business network 43 | let adminConnection; 44 | // This is the business network connection the tests will use. 45 | let businessNetworkConnection; 46 | // This is the factory for creating instances of types. 47 | let factory; 48 | // These are the identities for Alice and Bob. 49 | const danielCardName = 'daniel'; 50 | const simonCardName = 'simon'; 51 | const matthewCardName = 'matthew'; 52 | // These are a list of receieved events. 53 | let events; 54 | let businessNetworkName; 55 | before(async () => { 56 | // Generate certificates for use with the embedded connection 57 | const credentials = CertificateUtil.generate({ 58 | commonName: 'admin' 59 | }); 60 | // Identity used with the admin connection to deploy business networks 61 | const deployerMetadata = { 62 | version: 1, 63 | userName: 'PeerAdmin', 64 | roles: ['PeerAdmin', 'ChannelAdmin'] 65 | }; 66 | const deployerCard = new IdCard(deployerMetadata, connectionProfile); 67 | deployerCard.setCredentials(credentials); 68 | const deployerCardName = 'PeerAdmin'; 69 | adminConnection = new AdminConnection({ 70 | cardStore: cardStore 71 | }); 72 | await adminConnection.importCard(deployerCardName, deployerCard); 73 | await adminConnection.connect(deployerCardName); 74 | }); 75 | /** 76 | * 77 | * @param {String} cardName The card name to use for this identity 78 | * @param {Object} identity The identity details 79 | */ 80 | async function importCardForIdentity(cardName, identity) { 81 | const metadata = { 82 | userName: identity.userID, 83 | version: 1, 84 | enrollmentSecret: identity.userSecret, 85 | businessNetwork: businessNetworkName 86 | }; 87 | const card = new IdCard(metadata, connectionProfile); 88 | await adminConnection.importCard(cardName, card); 89 | } 90 | // This is called before each test is executed. 91 | beforeEach(async () => { 92 | // Generate a business network definition from the project directory. 93 | let businessNetworkDefinition = await BusinessNetworkDefinition.fromDirectory(path.resolve(__dirname, '..')); 94 | businessNetworkName = businessNetworkDefinition.getName(); 95 | await adminConnection.install(businessNetworkDefinition); 96 | const startOptions = { 97 | networkAdmins: [{ 98 | userName: 'admin', 99 | enrollmentSecret: 'adminpw' 100 | }] 101 | }; 102 | const adminCards = await adminConnection.start(businessNetworkName, businessNetworkDefinition.getVersion(), startOptions); 103 | await adminConnection.importCard(adminCardName, adminCards.get('admin')); 104 | // Create and establish a business network connection 105 | businessNetworkConnection = new BusinessNetworkConnection({ 106 | cardStore: cardStore 107 | }); 108 | events = []; 109 | businessNetworkConnection.on('event', event => { 110 | events.push(event); 111 | }); 112 | await businessNetworkConnection.connect(adminCardName); 113 | // Get the factory for the business network. 114 | const factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 115 | // create the auctioneer 116 | const seller = factory.newResource(namespace, 'Seller', 'daniel.selman@example.com'); 117 | seller.organisation = 'XYZ Corp'; 118 | seller.balance = 0; 119 | seller.products = []; 120 | // create potential buyers 121 | const buyer = factory.newResource(namespace, 'Member', 'sstone1@example.com'); 122 | buyer.firstName = 'Simon'; 123 | buyer.lastName = 'Stone'; 124 | buyer.balance = 100; 125 | buyer.products = []; 126 | const buyer2 = factory.newResource(namespace, 'Member', 'whitemat@example.com'); 127 | buyer2.firstName = 'Matthew'; 128 | buyer2.lastName = 'White'; 129 | buyer2.balance = 10000; 130 | buyer2.products = []; 131 | const sellerNS = namespace + '.Seller'; 132 | const buyerNS = namespace + '.Member'; 133 | const sellerRegistry = await businessNetworkConnection.getParticipantRegistry(sellerNS); 134 | const buyerRegistry = await businessNetworkConnection.getParticipantRegistry(buyerNS); 135 | await sellerRegistry.add(seller); 136 | await buyerRegistry.addAll([buyer, buyer2]); 137 | let identity = await businessNetworkConnection.issueIdentity(sellerNS + '#daniel.selman@example.com', 'daniel'); 138 | await importCardForIdentity(danielCardName, identity); 139 | identity = await businessNetworkConnection.issueIdentity(buyerNS + '#sstone1@example.com', 'simon'); 140 | await importCardForIdentity(simonCardName, identity); 141 | identity = await businessNetworkConnection.issueIdentity(buyerNS + '#whitemat@example.com', 'matthew'); 142 | await importCardForIdentity(matthewCardName, identity); 143 | await useIdentity(danielCardName); 144 | //const factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 145 | const product = factory.newTransaction(namespace, 'AddProduct'); 146 | product.description = 'My nice car'; 147 | product.productId = 'p1'; 148 | product.owner = factory.newRelationship(namespace, 'Seller', 'daniel.selman@example.com'); 149 | // Get the asset registry. 150 | await businessNetworkConnection.submitTransaction(product); 151 | //const sellerRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Seller'); 152 | const sellerReg = await sellerRegistry.get('daniel.selman@example.com'); 153 | sellerReg.products.length.should.equal(1); 154 | }); 155 | /** 156 | * Reconnect using a different identity. 157 | * @param {String} cardName The name of the card for the identity to use 158 | */ 159 | async function useIdentity(cardName) { 160 | await businessNetworkConnection.disconnect(); 161 | businessNetworkConnection = new BusinessNetworkConnection({ 162 | cardStore: cardStore 163 | }); 164 | events = []; 165 | businessNetworkConnection.on('event', (event) => { 166 | events.push(event); 167 | }); 168 | await businessNetworkConnection.connect(cardName); 169 | factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 170 | } 171 | it('Authorized owner should start the bidding', async () => { 172 | await useIdentity(danielCardName); 173 | const factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 174 | // create the auctioneer 175 | const sellerId = 'daniel.selman@example.com'; 176 | const sellerNS = namespace + '.Seller'; 177 | const sellerRegistry = await businessNetworkConnection.getParticipantRegistry(sellerNS); 178 | const seller = await sellerRegistry.get('daniel.selman@example.com'); 179 | var productid = seller.products[0].getIdentifier(); 180 | const listing = factory.newTransaction(namespace, 'StartBidding'); 181 | listing.listingId = 'l1'; 182 | listing.reservePrice = 50; 183 | listing.product = factory.newRelationship(namespace, 'Product', productid); 184 | await businessNetworkConnection.submitTransaction(listing); 185 | const productListingRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.ProductListing'); 186 | const assets = await productListingRegistry.getAll(); 187 | assets.should.have.lengthOf(1); 188 | }); 189 | it('Members bid for the product', async () => { 190 | await useIdentity(danielCardName); 191 | const factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 192 | const sellerId = 'daniel.selman@example.com'; 193 | const sellerNS = namespace + '.Seller'; 194 | const sellerRegistry = await businessNetworkConnection.getParticipantRegistry(sellerNS); 195 | const seller = await sellerRegistry.get('daniel.selman@example.com'); 196 | var productid = seller.products[0].getIdentifier(); 197 | const listing = factory.newTransaction(namespace, 'StartBidding'); 198 | listing.listingId = 'l1'; 199 | listing.reservePrice = 50; 200 | listing.product = factory.newRelationship(namespace, 'Product', productid); 201 | await businessNetworkConnection.submitTransaction(listing); 202 | const productListingRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.ProductListing'); 203 | const assets = await productListingRegistry.getAll(); 204 | assets.should.have.lengthOf(1); 205 | var assetId = assets[0].getIdentifier(); 206 | var buyerRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Member'); 207 | await useIdentity(simonCardName); 208 | var offer = factory.newTransaction(namespace, 'Offer'); 209 | offer.bidPrice = 100; 210 | offer.member = factory.newRelationship(namespace, 'Member', 'sstone1@example.com'); 211 | offer.listing = factory.newRelationship(namespace, 'ProductListing', assetId); 212 | await businessNetworkConnection.submitTransaction(offer); 213 | await useIdentity(matthewCardName); 214 | offer = factory.newTransaction(namespace, 'Offer'); 215 | offer.bidPrice = 1000; 216 | offer.member = factory.newRelationship(namespace, 'Member', 'whitemat@example.com'); 217 | offer.listing = factory.newRelationship(namespace, 'ProductListing', assetId); 218 | await businessNetworkConnection.submitTransaction(offer); 219 | var productListing = await productListingRegistry.get(assetId); 220 | productListing.offers.length.should.equal(2); 221 | }); 222 | it('Close bid for the product', async () => { 223 | await useIdentity(danielCardName); 224 | const factory = businessNetworkConnection.getBusinessNetwork().getFactory(); 225 | const sellerId = 'daniel.selman@example.com'; 226 | const sellerNS = namespace + '.Seller'; 227 | const sellerRegistry = await businessNetworkConnection.getParticipantRegistry(sellerNS); 228 | const seller = await sellerRegistry.get('daniel.selman@example.com'); 229 | var productid = seller.products[0].getIdentifier(); 230 | const listing = factory.newTransaction(namespace, 'StartBidding'); 231 | listing.listingId = 'l1'; 232 | listing.reservePrice = 50; 233 | listing.product = factory.newRelationship(namespace, 'Product', productid); 234 | await businessNetworkConnection.submitTransaction(listing); 235 | const productListingRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.ProductListing'); 236 | const assets = await productListingRegistry.getAll(); 237 | assets.should.have.lengthOf(1); 238 | var assetId = assets[0].getIdentifier(); 239 | var productId = assets[0].product.getIdentifier(); 240 | var buyerRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Member'); 241 | await useIdentity(simonCardName); 242 | var offer = factory.newTransaction(namespace, 'Offer'); 243 | offer.bidPrice = 100; 244 | offer.member = factory.newRelationship(namespace, 'Member', 'sstone1@example.com'); 245 | offer.listing = factory.newRelationship(namespace, 'ProductListing', assetId); 246 | await businessNetworkConnection.submitTransaction(offer); 247 | await useIdentity(matthewCardName); 248 | offer = factory.newTransaction(namespace, 'Offer'); 249 | offer.bidPrice = 1000; 250 | offer.member = factory.newRelationship(namespace, 'Member', 'whitemat@example.com'); 251 | offer.listing = factory.newRelationship(namespace, 'ProductListing', assetId); 252 | await businessNetworkConnection.submitTransaction(offer); 253 | var productListing = await productListingRegistry.get(assetId); 254 | productListing.offers.length.should.equal(2); 255 | await useIdentity(danielCardName); 256 | offer = factory.newTransaction(namespace, 'CloseBidding'); 257 | offer.listing = factory.newRelationship(namespace, 'ProductListing', assetId); 258 | await businessNetworkConnection.submitTransaction(offer); 259 | const productRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Product'); 260 | const product = await productRegistry.get(productid); 261 | product.owner.$identifier.should.equal('whitemat@example.com'); 262 | }); 263 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING: This repository is no longer maintained :warning: 2 | 3 | > The [Create and execute blockchain smart contracts](https://developer.ibm.com/patterns/create-and-execute-blockchain-smart-contracts/) from IBM Cloud has been sunset. This repository will not be updated. The repository will be kept available in read-only mode. Refer to https://github.com/IBM/SmartContractTrading-wFabric1-4-VSCodeExt for a similar example. 4 | 5 | # DISCLAIMER: 6 | 7 | > As of August 2018, IBM will not be contributing new features to Hyperledger Composer and will only be maintaining it through Fabric 2.x releases. IBM recommends using Hyperledger Composer solely for demos and proof-of-concepts. IBM does not provide support for networks using Hyperledger Composer in production (this includes the CLI, JavaScript APIs, REST server, and Web Playground). 8 | 9 | > This pattern has been upgraded to Fabric V2.0 10 | 11 | # Hyperledger Composer - Product Auction Network 12 | 13 | *Read this in other languages: [한국어](README-ko.md),[中国](README-cn.md).* 14 | 15 | Welcome to Part 2 of the Hyperledger Composer Composite Journey. This is a continuation of [Composer Network Setup journey](https://github.com/IBM/BlockchainNetwork-CompositeJourney#build-your-first-hyperledger-network). You should have installed the Hyperledger Composer Devlopment Tools, and Started the Hyperledger Fabric network. This journey introduces more complexity in using Composer to define your smart contract. You will learn how to add multiple participants and add access control to your blockchain application. To do that - you will create an interactive, distributed, product auction demo network. You will list assets for sale (setting a reserve price) and watch as assets that have met their reserve price are automatically transferred to the highest bidder at the end of the auction. Also each participant will have different level of access permissions depending on the Access Control Rules (ACL) in `permissions.acl` file. Access Control Lists (ACL) are the settings for sharing and privacy, which are automatically enforced by the Fabric Composer runtime. This Pattern has been updated and successfully tested and runs on Hyperledger Composer V0.20.5, Hyperledger Fabric V1.2. 16 | 17 | 18 | This business network defines: 19 | 20 | **Participants:** 21 | `Member` `Seller` 22 | 23 | **Assets:** 24 | `Product` `ProductListing` 25 | 26 | **Transactions:** 27 | `AddProduct` `StartBidding` `Offer` `CloseBidding` 28 | 29 | The `addProduct` function is called when an `AddProduct` transaction is submitted. The logic allows a seller to create a product asset and update its registry. 30 | 31 | The `publishListing` function is called when a `StartBidding` transaction is submitted by the owner of product. The logic allows a seller to create a smart contract in the form of product listing for their product with a reserve bid. 32 | 33 | The `makeOffer` function is called when an `Offer` transaction is submitted. The logic simply checks that the listing for the offer is still for sale, and then adds the offer to the listing, and then updates the offers in the `ProductListing` asset registry. 34 | 35 | The `closeBidding` function is called when a `CloseBidding` transaction is submitted for processing. The logic checks that the listing is still for sale, sort the offers by bid price, and then if the reserve has been met, transfers the ownership of the product associated with the listing to the highest bidder. Money is transferred from the buyer's account to the seller's account, and then all the modified assets are updated in their respective registries. 36 | 37 | `product.cto` file present in `models` directory defines a data model for the product auction demo which consists the definition for assets, participants and transactions. `logic.js` file present in `lib` directory implement the transactions defined in the `product.cto` file. Recall that the `.cto` file defines the structure of your business network in terms of Assets, Participants and Transactions. 38 | 39 | ACL rules are present in `permissions.acl` file to determine which user/role is permitted to create, read, update or delete an element in the business network's domain model. The default `System` user has all the permissions. Members of the network have read access to all the resources and the seller can create a product, start and close the bidding for their products. Members of the network can make their bid for the product listing. Participants can access only permitted resources and transactions. 40 | 41 | ## Included Components 42 | * Hyperledger Fabric v1.2 43 | * Hyperledger Composer v20.5 44 | * Docker v1.13 45 | 46 | ## Prerequisites 47 | We find that Blockchain can be finicky when it comes to installing Node. We want to share this [StackOverflow response](https://stackoverflow.com/questions/49744276/error-cannot-find-module-api-hyperledger-composer) - because many times the errors you see with Composer are derived in having installed either the wrong Node version or took an approach that is not supported by Composer: 48 | 49 | * [Docker](https://www.docker.com/products) - v1.13 or higher 50 | * [Docker Compose](https://docs.docker.com/compose/overview/) - v1.8 or higher 51 | * [NPM](https://www.npmjs.com/get-npm) - v5.6.0 or higher 52 | * [nvm]() - v8.11.3 (use to download and set what node version you are using) 53 | * [Node.js](https://nodejs.org/en/download/) - node v8.11.3 ** don't install in SUDO mode 54 | * [Git client](https://git-scm.com/downloads) - v 2.9.x or higher 55 | * [Python](https://www.python.org/downloads/) - 2.7.x 56 | 57 | ## Application Workflow Diagram 58 | ![Application Workflow](images/arch-smart-contract.png) 59 | 60 | Creating multiple participants and adding ACL 61 | * Adding additional participants 62 | * Adding Access Control Lists 63 | * Querying and invoking the Chaincode 64 | 65 | ## Steps 66 | 1. [Generate the Business Network Archive (BNA)](#1-generate-the-business-network-archive-bna) 67 | 2. [Deploy the Business Network Archive using Composer Playground](#2-deploy-the-business-network-archive-using-composer-playground) 68 | 3. [Deploy the Business Network Archive on Hyperledger Composer running locally](#3-deploy-the-business-network-archive-on-hyperledger-composer-running-locally) 69 | 70 | ## 1. Generate the Business Network Archive (BNA) 71 | 72 | To check that the structure of the files is valid, you can now generate a Business Network Archive (BNA) file for your business network definition. The BNA file is the deployable unit -- a file that can be deployed to the Composer runtime for execution. 73 | 74 | Use the following command to generate the network archive: 75 | ```bash 76 | npm install 77 | ``` 78 | You should see the following output: 79 | ```bash 80 | > mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/product-auction.bna 81 | 82 | Creating Business Network Archive 83 | 84 | Looking for package.json of Business Network Definition 85 | Input directory: /Users/ishan/Documents/git-demo/BlockchainBalanceTransfer-CompositeJourney 86 | 87 | Found: 88 | Description: Sample product auction network 89 | Name: product-auction 90 | Identifier: product-auction@0.0.1 91 | 92 | Written Business Network Definition Archive file to 93 | Output file: ./dist/product-auction.bna 94 | 95 | Command succeeded 96 | ``` 97 | The `composer archive create` command has created a file called `product-auction.bna` in the `dist` folder. 98 | 99 | You can test the business network definition against the embedded runtime that stores the state of 'the blockchain' in-memory in a Node.js process. 100 | From your project working directory, open the file test/productAuction.js and run the following command: 101 | ``` 102 | npm test 103 | ``` 104 | You should see the following output : 105 | ``` 106 | > product-auction@0.0.1 test /Users/ishan/Documents/git-demo/BlockchainBalanceTransfer-CompositeJourney 107 | > mocha --recursive 108 | 109 | ProductAuction - AddProduct Test 110 | #BiddingProcess 111 | ✓ Add the product to seller list (119ms) 112 | ✓ Authorized owner should start the bidding (90ms) 113 | ✓ Members bid for the product (127ms) 114 | ✓ Close bid for the product (53ms) 115 | 116 | 117 | 4 passing (2s) 118 | ``` 119 | 120 | ## 2. Deploy the Business Network Archive using Composer Playground 121 | 122 | Open [Composer Playground](https://composer-playground.mybluemix.net/), by default the Basic Sample Network is imported. 123 | If you have previously used Playground, be sure to clear your browser local storage by running `localStorage.clear()` in your browser Console. Now import the `product-auction.bna` file and click on deploy button. 124 | 125 | If you don't know how to import, take a [tour of Composer Playground](https://www.youtube.com/watch?time_continue=29&v=JQMh_DQ6wXc) 126 | 127 | 128 | >You can also setup [Composer Playground locally](https://hyperledger.github.io/composer/latest/installing/development-tools.html). 129 | 130 | You will see the following: 131 |

132 | 133 |

134 | 135 | 136 | To test this Business Network Definition in the **Test** tab: 137 | 138 | 139 | In the `Seller` participant registry, create a new participant. Make sure you click on the `Seller` tab on the far left-hand side. 140 | 141 | ``` 142 | { 143 | "$class": "org.acme.product.auction.Seller", 144 | "organisation": "ACME", 145 | "email": "auction@acme.org", 146 | "balance": 100, 147 | "products": [] 148 | } 149 | ``` 150 | 151 | In the `Member` participant registry, create two participants. Again, click on the `Member` tab on the far left-hand side. 152 | 153 | ``` 154 | { 155 | "$class": "org.acme.product.auction.Member", 156 | "firstName": "Amy", 157 | "lastName": "Williams", 158 | "email": "memberA@acme.org", 159 | "balance": 1000, 160 | "products": [] 161 | } 162 | ``` 163 | 164 | ``` 165 | { 166 | "$class": "org.acme.product.auction.Member", 167 | "firstName": "Billy", 168 | "lastName": "Thompson", 169 | "email": "memberB@acme.org", 170 | "balance": 1000, 171 | "products": [] 172 | } 173 | ``` 174 | 175 | Now we are ready to add **Access Control**. Do this by first clicking on the `admin` tab to issue **new ids** to the participants. Note: the ids are automatically added to the wallet. 176 | 177 | Select Admin-> ID Registry 178 | You will see the following: 179 |

180 | 181 |

182 | 183 | Please follow the instructions as shown in the images below: 184 | 185 | Click on `Issue New ID` button on upper-right hand side - the follow pop-up will appear: 186 | 187 | ![Admin Tab](images/IssueIDScreen.png) 188 | 189 | Enter the information you see in the graphic above. 190 | 191 | Enter `Seller` for ID Name. Then enter "org.acme.product.auction.Seller#auction@acme.org" in the participant field. **Note**: there is case-sensitivity wrt the name `Seller`. If you have it capitilized as a participant when you added participants under the `Test` page - you must match it here the way you enter it in the Participant field. Ensure you have checked the `Allow this ID to issue new IDs` checkbox. Select the `Create New` button. 192 | 193 | ![Generate New Id](images/generateNewId.png) 194 | 195 | Now issue IDs for MemberA and MemberB (example of issuing ID for MemberA in the graphic below - duplicate process for MemberB). First, again, select `Issue New ID`. Then complete the fields and check the checkbox. Select `Create New`. 196 | 197 | 198 | ![MemberA Id to Wallet](images/IdentityA.png) 199 | 200 | Once you complete those steps - your screen should appear as follows: 201 | 202 | ![Ids to Wallet](images/idtowallet.png) 203 | 204 | The Wallet tab is pictured in the image below. Select the `seller id` from `Wallet tab` tab (as demonstrated in the graphic below). Select the 'use now' button. Note the status of the `Seller` id is now `In Use`. Now click on the `test tab` to perform `AddProduct` and `StartBidding` transactions. 205 | 206 | ![Select Id](images/selectid.png) 207 | 208 | Now click on `Submit Transaction` button and select `AddProduct` transaction from the dropdown, to create a product for the seller. 209 | 210 |

211 | 212 |

213 | 214 | ``` 215 | { 216 | "$class": "org.acme.product.auction.AddProduct", 217 | "productId": "p1", 218 | "description": "Sample Product", 219 | "owner": "resource:org.acme.product.auction.Seller#auction@acme.org" 220 | } 221 | ``` 222 | You can verify the transaction by checking the product and seller registry. 223 | 224 | 225 | To create a product listing for the above product, submit `StartBidding` transaction. Again, select `Submit Transaction` button and then select `Start Bidding` transaction from the dropdown. 226 | 227 | ``` 228 | { 229 | "$class": "org.acme.product.auction.StartBidding", 230 | "listingId": "l1", 231 | "reservePrice": 50, 232 | "product": "resource:org.acme.product.auction.Product#p1" 233 | } 234 | ``` 235 | 236 | You've just listed `Sample Product - P1` for auction, with a reserve price of $50. 237 | A listing has been created in `ProductListing` registry for the product with `FOR_SALE` state. 238 | 239 | Now Member participants can submit `Offer` transactions to bid on a product listing. 240 | 241 | For each `member id`, select the user id from the tab on the upper right hand-side that probably says `Seller` at the moment. Select MemberA on the left hand side and then `use now` as is demonstrated in the graphic below. 242 | 243 | ![Select Id](images/select-member.png) 244 | 245 | 246 | To submit an `Offer` transaction select the `test tab` and click on `Submit Transaction` button. Select `offer` from the drop down. 247 | 248 | ![make offer](images/offer.png) 249 | 250 | ``` 251 | { 252 | "$class": "org.acme.product.auction.Offer", 253 | "bidPrice": 50, 254 | "listing": "resource:org.acme.product.auction.ProductListing#l1", 255 | "member": "resource:org.acme.product.auction.Member#memberA@acme.org" 256 | } 257 | ``` 258 | 259 | 260 | Repeat the process for MemberB. Remember to select 'use now' for `memberB` in the registry similar to what you did for `memberA`. 261 | 262 | ``` 263 | { 264 | "$class": "org.acme.product.auction.Offer", 265 | "bidPrice": 100, 266 | "listing": "resource:org.acme.product.auction.ProductListing#l1", 267 | "member": "resource:org.acme.product.auction.Member#memberB@acme.org" 268 | } 269 | ``` 270 | 271 | You can check the `ProductListing` registry, to view all the bids for the product. 272 | 273 | ![Product Offers](images/productoffers.png) 274 | 275 | Now again select the `seller id` from the `Wallet tab` tab. Set it to `use now`. Click on `test tab` and then end the auction by submitting a `CloseBidding` transaction for the listing. 276 | 277 | ![Close Bid](images/closebid.png) 278 | 279 | ``` 280 | { 281 | "$class": "org.acme.product.auction.CloseBidding", 282 | "listing": "resource:org.acme.product.auction.ProductListing#l1" 283 | } 284 | ``` 285 | 286 | This simply indicates that the auction for `ListingID` is now closed, triggering the `closeBidding` function that was described above. 287 | 288 | To check whether the Product is sold you need to click on the `ProductListing` asset registry and check the owner of the product. The highest bid was placed by owner `memberB@acme.org`, so `memberB@acme.org` should be the owner of the product. 289 | 290 | You can check the state of the ProductListing with `l1` is `SOLD`. 291 | 292 | 293 | ![Product Listing Sold](images/soldlisting.png) 294 | 295 | Click on the `Member` asset registry to verify the updated balance for buyer and seller. The product is added to the product list of the buyer `memberB@acme.org`. Note the balance of MemberB is $900. 296 | 297 | ![New Owner of Product](images/newowner.png) 298 | 299 | You can view history of all transactions by selecting the `All transactions` tab. 300 | 301 | ![Transaction History](images/transactions.png) 302 | 303 | > You can also use the default `System user` to perform all the actions as we have a rule in `permissions.acl` to permit all access `System user`. 304 | 305 | ## 3. Deploy the Business Network Archive on Hyperledger Composer running locally 306 | 307 | Please start the local Fabric using the [instructions](https://github.com/IBM/BlockchainNetwork-CompositeJourney#2-starting-hyperledger-fabric). 308 | Now change directory to the `dist` folder containing `product-auction.bna` file and type: 309 | ``` 310 | cd dist 311 | composer network install --card PeerAdmin@hlfv1 --archiveFile product-auction.bna 312 | 313 | composer network start --networkName product-auction --networkVersion 0.0.1 --networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv1 --file networkadmin.card 314 | composer card import --file networkadmin.card 315 | ``` 316 | 317 | You can verify that the network has been deployed by typing: 318 | ``` 319 | composer network ping --card admin@product-auction 320 | ``` 321 | 322 | You should see the the output as follows: 323 | ``` 324 | The connection to the network was successfully tested: product-auction 325 | version: 0.19.5 326 | participant: org.hyperledger.composer.system.NetworkAdmin#admin 327 | identity: org.hyperledger.composer.system.Identity#5b057b5ed98979e814a2c5792f853f03ec2f6cda378058eb4a4dda390cb86dee 328 | 329 | Command succeeded 330 | ``` 331 | 332 | To create the REST API we need to launch the `composer-rest-server` and tell it how to connect to our deployed business network. 333 | Now launch the server by changing directory to the product-auction folder and type: 334 | ```bash 335 | cd .. 336 | composer-rest-server 337 | ``` 338 | 339 | Answer the questions posed at startup. These allow the composer-rest-server to connect to Hyperledger Fabric and configure how the REST API is generated. 340 | * Enter `admin@product-auction` as the card name. 341 | * Select `never use namespaces` when asked whether to use namespaces in the generated API. 342 | * Select `No` when asked whether to secure the generated API. 343 | * Select `Yes` when asked whether to enable event publication. 344 | * Select `No` when asked whether to enable TLS security. 345 | 346 | **Test REST API** 347 | 348 | If the composer-rest-server started successfully you should see these two lines are output: 349 | ``` 350 | Web server listening at: http://localhost:3000 351 | Browse your REST API at http://localhost:3000/explorer 352 | ``` 353 | 354 | Open a web browser and navigate to http://localhost:3000/explorer 355 | 356 | You should see the LoopBack API Explorer, allowing you to inspect and test the generated REST API. Follow the instructions to test Business Network Definition as mentioned above in the composer section. 357 | 358 | ## Ready to move to Step 3! 359 | Congratulations - you have completed Step 2 of this Composite Journey - move onto [Step 3](https://github.com/IBM/BlockchainEvents-CompositeJourney). 360 | 361 | ## Additional Resources 362 | * [Hyperledger Fabric Docs](https://hyperledger-fabric.readthedocs.io/en/latest/) 363 | 364 | ## License 365 | 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). 366 | 367 | [Apache Software License (ASL) FAQ](https://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) 368 | 369 | --------------------------------------------------------------------------------