mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-26 11:35:10 +00:00
Merge branch 'main' into patch-1
This commit is contained in:
commit
f85d31222f
119 changed files with 6873 additions and 113 deletions
|
|
@ -10,10 +10,8 @@ fabric-samples uses a non-author code review policy, requiring a single approval
|
|||
| Chris Ferris | christo4ferris | cbf | chris.ferris@gmail.com |
|
||||
| Dave Enyeart | denyeart | dave.enyeart | enyeart@us.ibm.com |
|
||||
| Gari Singh | mastersingh24 | mastersingh24 | gari.r.singh@gmail.com |
|
||||
| Jason Yellick | jyellick | jyellick | jyellick@us.ibm.com |
|
||||
| Matthew B White | mbwhite | mbwhite | whitemat@uk.ibm.com |
|
||||
| Nikhil Gupta | nikhil550 | negupta | nikhilg550@gmail.com |
|
||||
| Simon Stone | sstone1 | sstone1 | sstone1@uk.ibm.com |
|
||||
|
||||
Also: Please see the [Release Manager section](https://github.com/hyperledger/fabric/blob/main/MAINTAINERS.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ Additional samples demonstrate various Fabric use cases and application patterns
|
|||
| [Token ERC-20](token-erc-20) | Smart contract demonstrating how to create and transfer fungible tokens using an account-based model. | [README](token-erc-20/README.md) |
|
||||
| [Token UTXO](token-utxo) | Smart contract demonstrating how to create and transfer fungible tokens using a UTXO (unspent transaction output) model. | [README](token-utxo/README.md) |
|
||||
| [High throughput](high-throughput) | Learn how you can design your smart contract to avoid transaction collisions in high volume environments. | [README](high-throughput/README.md) |
|
||||
| [Auction](auction) | Run an auction where bids are kept private until the auction is closed, after which users can reveal their bid | [README](auction/README.md) |
|
||||
| [Simple Auction](auction-simple) | Run an auction where bids are kept private until the auction is closed, after which users can reveal their bid. | [README](auction-simple/README.md) |
|
||||
| [Dutch Auction](auction-dutch) | Run an auction in which multiple items of the same type can be sold to more than one buyer. This example also includes the ability to add an auditor organization. | [README](auction-dutch/README.md) |
|
||||
| [Chaincode](chaincode) | A set of other sample smart contracts, many of which were used in tutorials prior to the asset transfer sample series. | |
|
||||
| [Interest rate swaps](interest_rate_swaps) | **Deprecated in favor of state based endorsement asset transfer sample** | |
|
||||
| [Fabcar](fabcar) | **Deprecated in favor of basic asset transfer sample** | |
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ There are two ways to report a security bug. The easiest is to email a descripti
|
|||
|
||||
The other way is to file a confidential security bug in our [JIRA bug tracking system](https://jira.hyperledger.org). Be sure to set the “Security Level” to “Security issue”.
|
||||
|
||||
The process by which the Hyperledger Security Team handles security bugs is documented further in our [Defect Response page](https://wiki.hyperledger.org/display/HYP/Defect+Response) on our [wiki](https://wiki.hyperledger.org).
|
||||
The process by which the Hyperledger Security Team handles security bugs is documented further in our [Defect Response page](https://wiki.hyperledger.org/display/SEC/Defect+Response) on our [wiki](https://wiki.hyperledger.org).
|
||||
|
||||
|
|
|
|||
15
asset-transfer-basic/application-typescript-hsm/.gitignore
vendored
Normal file
15
asset-transfer-basic/application-typescript-hsm/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Compiled TypeScript files
|
||||
dist
|
||||
|
||||
263
asset-transfer-basic/application-typescript-hsm/README.md
Normal file
263
asset-transfer-basic/application-typescript-hsm/README.md
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
# Asset Transfer Basic typescript HSM sample
|
||||
|
||||
This sample takes the Asset Transfer basic example and re-works it to focus on how
|
||||
you would use a Hardware Security Modules within your node client application.
|
||||
|
||||
## About the Sample
|
||||
|
||||
This typescript sample application is able to use any of the asset transfer basic
|
||||
chaincode samples. It will show how to register users with a Fabric CA and enroll users which will store keys in an HSM (In this case the sample uses SoftHSM which is an HSM implementation that should be used for development and testing purposes only). It also demonstrates setting up a wallet that will store identities that can then be used to transact on the fabric network which are HSM managed.
|
||||
|
||||
## C Compilers
|
||||
|
||||
In order for the client application to run successfully you must ensure you have C compilers and Python 3 (Note that Python 2 may still work however Python 2 is out of support and could stop working in the future) installed otherwise the node dependency `pkcs11js` will not be built and the application will fail. The failure will look like
|
||||
|
||||
```
|
||||
Loaded the network configuration located at /home/dave/temp/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/connection-org1.json
|
||||
******** FAILED to run the application: Error: Cannot find module 'pkcs11js'
|
||||
Require stack:
|
||||
- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/lib/impl/bccsp_pkcs11.js
|
||||
- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/lib/Utils.js
|
||||
- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-common/index.js
|
||||
- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-ca-client/lib/FabricCAServices.js
|
||||
- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/node_modules/fabric-ca-client/index.js
|
||||
- /home/dave/temp/fabric-samples/asset-transfer-basic/application-typescript-hsm/dist/app.js
|
||||
```
|
||||
|
||||
how to install the required C Compilers and Python will depend on your operating system and version.
|
||||
|
||||
## Configuring and running a Hardware Security Module
|
||||
|
||||
This sample sets the hsmOptions for the wallet as follows
|
||||
|
||||
```javascript
|
||||
const softHSMOptions: HsmOptions = {
|
||||
lib: await findSoftHSMPKCS11Lib(),
|
||||
pin: process.env.PKCS11_PIN || '98765432',
|
||||
label: process.env.PKCS11_LABEL || 'ForFabric',
|
||||
};
|
||||
```
|
||||
|
||||
which is specific to using SoftHSM which has been initialised with a token labelled `ForFabric`
|
||||
and a user pin of `98765432`, however you can override these values to use your own HSM by either
|
||||
editting the application or use these environment variables to pass in the values:
|
||||
|
||||
* PKCS11_LIB - path to the your specific HSM Library
|
||||
* PKCS11_PIN - your HSM pin
|
||||
* PKCS11_LABEL - your HSM label
|
||||
|
||||
Alternatively you could install SoftHSM to try out the application as described in the next sections
|
||||
|
||||
### Install SoftHSM
|
||||
|
||||
In order to run the application in the absence of a real HSM, a software
|
||||
emulator of the PKCS#11 interface is required.
|
||||
For more information please refer to [SoftHSM](https://www.opendnssec.org/softhsm/).
|
||||
|
||||
SoftHSM can either be installed using the package manager for your host system:
|
||||
|
||||
* Ubuntu: `sudo apt install softhsm2`
|
||||
* macOS: `brew install softhsm`
|
||||
* Windows: **unsupported**
|
||||
|
||||
Or compiled and installed from source:
|
||||
|
||||
1. install openssl 1.0.0+ or botan 1.10.0+
|
||||
2. download the source code from <https://dist.opendnssec.org/source/softhsm-2.5.0.tar.gz>
|
||||
3. `tar -xvf softhsm-2.5.0.tar.gz`
|
||||
4. `cd softhsm-2.5.0`
|
||||
5. `./configure --disable-gost` (would require additional libraries, turn it off unless you need 'gost' algorithm support for the Russian market)
|
||||
6. `make`
|
||||
7. `sudo make install`
|
||||
|
||||
### Specify the SoftHSM configuration file
|
||||
|
||||
A configuration file for SoftHSM is provided in this sample directory. This file
|
||||
uses /tmp as the location for SoftHSM to store it's data which means (depending on
|
||||
your operating system configuration) the data could be deleted at some point, for example
|
||||
when you reboot your system. If this data is lost then you will have to delete the wallet
|
||||
created. An alternative is to change this file to store SoftHSM data in a permanent location
|
||||
on your file system.
|
||||
To use this configuration file you need to export an environment variable to point to it
|
||||
for example, if you are in the application directory then you can use the following command
|
||||
|
||||
```bash
|
||||
export SOFTHSM2_CONF=$PWD/softhsm2.conf
|
||||
```
|
||||
|
||||
Ensure you have this set when initializing the token as well as running the application
|
||||
|
||||
### Initialize a token to store keys in SoftHSM
|
||||
|
||||
If you have not initialized a token previously (or it has been deleted) then you will need to perform this one time operation
|
||||
|
||||
```bash
|
||||
softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234
|
||||
```
|
||||
|
||||
The Security Officer PIN, specified with the `--so-pin` flag, can be used to re-initialize the token,
|
||||
and the user PIN (see below), specified with the `--pin` flag, is used by applications to access the token for
|
||||
generating and retrieving keys.
|
||||
|
||||
## Running the sample
|
||||
|
||||
Use the Fabric test network utility (`network.sh`) to start a Fabric network and deploy one
|
||||
one of the sample chaincodes.
|
||||
|
||||
Follow these step in order.
|
||||
- Start the test network with a Certificate Authority and create a channel, so assuming you are in the ensuring you are in the `application-typescript-hsm` directory
|
||||
|
||||
```
|
||||
cd ../../test-network
|
||||
./network.sh down
|
||||
./network.sh up createChannel -c mychannel -ca
|
||||
```
|
||||
|
||||
- Deploy the chaincode (smart contract)
|
||||
|
||||
```
|
||||
# to deploy the javascript version
|
||||
./network.sh deployCC -ccs 1 -ccv 1 -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl javascript -ccp ./../asset-transfer-basic/chaincode-javascript/ -ccn basic
|
||||
```
|
||||
|
||||
- Run the application
|
||||
|
||||
```
|
||||
cd ../asset-transfer-basic/application-typescript-hsm
|
||||
npm install
|
||||
npm run build
|
||||
node dist/app.js
|
||||
```
|
||||
|
||||
### Expected successful execution
|
||||
|
||||
If the sample runs successfully then it will output several messages showing the various actions and finally display the message
|
||||
|
||||
```
|
||||
*** The sample ran successfully ***
|
||||
```
|
||||
|
||||
One the first run of the sample, a CA admin id from Org1 will be enrolled from the CA. The certificate for this admin will be stored in the application wallet but the private key will have been stored in the HSM, it will not be in the application wallet. A User in Org1 will be registered in the the Org1 CA and then enrolled. Again only it's certificate will be stored in the application wallet.
|
||||
All signing of transactions done by the application (driven by the node sdk) will actually be done by the HSM rather than within the node sdk as the private key will never be directly available outside of the HSM.
|
||||
|
||||
This sample can be run multiple times even while the same network remains up. As the certificates are already in the application wallet it will not have to enroll a CA admin or register and enroll a user.
|
||||
|
||||
### Possible issues you could encounter running the sample
|
||||
|
||||
If you see this error:
|
||||
|
||||
```
|
||||
******** FAILED to run the application: Pkcs11Error: CKR_GENERAL_ERROR
|
||||
```
|
||||
Make sure you have exported SOFTHSM2_CONF correctly and it points to a valid SoftHSM configuration file that references
|
||||
a directory containing a initialised SoftHSM token
|
||||
|
||||
|
||||
If you see this error:
|
||||
|
||||
```
|
||||
2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied
|
||||
******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied
|
||||
```
|
||||
|
||||
Then the current certificates in your wallet are not valid for the network you are trying to interact with. This would happen if you had
|
||||
shutdown the fabric network using `network.sh down` and created a new network because this causes all the root certificates to be recreated
|
||||
To address this problem, you can delete the `wallet` directory in the `dist` folder (`fabric-samples/asset-transfer-basic/application-typescript-hsm/dist/wallet`) of this sample to create new certificates. Because new keypairs are generated these will be stored in SoftHSM and the existing old ones will not be referenced
|
||||
|
||||
If you see this error
|
||||
|
||||
```
|
||||
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
|
||||
******** FAILED to run the application: Error: Identity not found in wallet: appUser
|
||||
```
|
||||
|
||||
Then the most likely cause is you have deleted your wallet. You would need to either
|
||||
- stop and create a new fabric network using the `network.sh down` and then following the above instructions to start a new fabric network (but you should not re-initialize the softhsm token)
|
||||
- or change the application such that it registers a new user instead of `appUser`. This is because be default a registered user can only be enrolled once (using the userid and it's secret)
|
||||
|
||||
If you see this error
|
||||
|
||||
```
|
||||
******** FAILED to run the application: Error: _pkcs11OpenSession[336]: PKCS11 label ForFabric cannot be found in the slot list
|
||||
```
|
||||
|
||||
Then the SoftHSM token directory has been deleted (could be due to the /tmp file being cleaned up if you use the sample softhsm2.conf file provided).
|
||||
You will either need to
|
||||
- delete your existing wallet, bring down the network as described in `Clean up` section and recreate the network including re-initializing the softhsm token
|
||||
- or you could just re-initialise the softhsm token but you will need to change the application so that it registers a new user instead of `appUser`
|
||||
|
||||
If you see this error (note the number following `SKI` will not be the same)
|
||||
|
||||
```
|
||||
******** FAILED to run the application: Error: _pkcs11SkiToHandle[572]: no key with SKI 27f3557183cd5f26384ab69968ba74944c94c0e24f681c4fadd6502886891da0 found
|
||||
```
|
||||
|
||||
Then the certificates in your wallet do not have corresponding keys in SoftHSM. You can should bring down the network and delete your current wallet (as described in `Clean up` section) and create the network again
|
||||
|
||||
|
||||
If you see either of these errors when the application ends
|
||||
|
||||
```
|
||||
free(): double free detected in tcache 2
|
||||
Aborted
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
node[61480]: ../src/node_http2.cc:521:void node::http2::Http2Session::CheckAllocatedSize(size_t) const: Assertion `(current_nghttp2_memory_) >= (previous_size)' failed.
|
||||
1: 0xa1a640 node::Abort() [node]
|
||||
2: 0xa1a6be [node]
|
||||
3: 0xa55e2a node::mem::NgLibMemoryManager<node::http2::Http2Session, nghttp2_mem>::ReallocImpl(void*, unsigned long, void*) [node]
|
||||
4: 0xa55ed3 node::mem::NgLibMemoryManager<node::http2::Http2Session, nghttp2_mem>::FreeImpl(void*, void*) [node]
|
||||
5: 0x18b0388 nghttp2_session_close_stream [node]
|
||||
6: 0x18b76ea nghttp2_session_mem_recv [node]
|
||||
7: 0xa54937 node::http2::Http2Session::ConsumeHTTP2Data() [node]
|
||||
8: 0xa54d5e node::http2::Http2Session::OnStreamRead(long, uv_buf_t const&) [node]
|
||||
9: 0xb6a651 node::TLSWrap::ClearOut() [node]
|
||||
10: 0xb6bcdb node::TLSWrap::OnStreamRead(long, uv_buf_t const&) [node]
|
||||
11: 0xaf54b1 [node]
|
||||
12: 0x137fed9 [node]
|
||||
13: 0x1380500 [node]
|
||||
14: 0x1386de5 [node]
|
||||
15: 0x137458f uv_run [node]
|
||||
16: 0xa5d7a6 node::NodeMainInstance::Run() [node]
|
||||
17: 0x9eab6c node::Start(int, char**) [node]
|
||||
18: 0x7fd7612180b3 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6]
|
||||
19: 0x981fe5 [node]
|
||||
Aborted (core dumped)
|
||||
```
|
||||
|
||||
then this is due to a bug in `node`. May sure you are using the latest supported version of node, however at the time of writing (node 14.17.1 & node 12.22.1) a fix had not been released by node.js
|
||||
|
||||
### Enabling Node SDK logging
|
||||
|
||||
To see the SDK workings, try setting the logging to show on the console before running
|
||||
```
|
||||
export HFC_LOGGING='{"debug":"console"}'
|
||||
```
|
||||
or log to a file
|
||||
```
|
||||
export HFC_LOGGING='{"debug":"./debug.log"}'
|
||||
```
|
||||
|
||||
## Clean up
|
||||
|
||||
When you are finished, you can bring down the test network.
|
||||
The command will remove all the nodes of the test network, and delete any
|
||||
ledger data that you created:
|
||||
|
||||
```bash
|
||||
cd ../../test-network
|
||||
./network.sh down
|
||||
```
|
||||
Be sure to delete the wallet directory create by the application.
|
||||
The stored identities will no longer be valid when restarting the network.
|
||||
For example if you are in the application-typescript-hsm directory
|
||||
|
||||
```bash
|
||||
rm -fr dist/wallet
|
||||
```
|
||||
|
||||
## Suggestions
|
||||
When typescript node applications log problems or terminate with a stack trace you normally would have to look at the compiled .js code to find the code that was executed in the stack trace. It would be easier if you could find the corresponding lines in the typescript source file. One solution to this problem can be found here https://github.com/evanw/node-source-map-support which will convert stack trace output to corresponding typescript file lines using the generated source maps.
|
||||
50
asset-transfer-basic/application-typescript-hsm/package.json
Normal file
50
asset-transfer-basic/application-typescript-hsm/package.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "asset-transfer-basic",
|
||||
"version": "1.0.0",
|
||||
"description": "Asset Transfer Basic contract implemented in TypeScript",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'",
|
||||
"pretest": "npm run lint",
|
||||
"start": "npm run build && node dist/app.js",
|
||||
"build": "tsc",
|
||||
"build:watch": "tsc -w",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-ca-client": "^2.2.8",
|
||||
"fabric-network": "^2.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^3.1.6"
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts",
|
||||
".tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"coverage/**",
|
||||
"dist/**"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"all": true,
|
||||
"check-coverage": true,
|
||||
"statements": 100,
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
directories.tokendir = /tmp/
|
||||
objectstore.backend = file
|
||||
|
||||
# ERROR, WARNING, INFO, DEBUG
|
||||
log.level = INFO
|
||||
278
asset-transfer-basic/application-typescript-hsm/src/app.ts
Normal file
278
asset-transfer-basic/application-typescript-hsm/src/app.ts
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import * as FabricCAServices from 'fabric-ca-client';
|
||||
import { Contract, Gateway, GatewayOptions, HsmOptions, HsmX509Provider, Wallets } from 'fabric-network';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { buildCCPOrg1, prettyJSONString } from './utils//AppUtil';
|
||||
import { enrollUserToWallet, registerUser, UserToEnroll, UserToRegister } from './utils/CAUtil';
|
||||
|
||||
const walletPath = path.join(__dirname, 'wallet');
|
||||
|
||||
// define information about the channel and chaincode id that will be driven by this application
|
||||
const channelName = 'mychannel';
|
||||
const chaincodeId = 'basic';
|
||||
|
||||
// define the CA Registrar
|
||||
const mspOrg1 = 'Org1MSP';
|
||||
const org1CAAdminUserId = 'admin';
|
||||
const org1CAAdminSecret = 'adminpw';
|
||||
|
||||
// define the user in org1 to use
|
||||
const org1UserId = 'appUser';
|
||||
const org1Secret = 'appUserSecret';
|
||||
const org1Affiliation = 'org1.department1';
|
||||
|
||||
type Request = 'submit' | 'evaluate';
|
||||
|
||||
interface TransactionToSendFormat {
|
||||
request: Request;
|
||||
txName: string;
|
||||
txArgs: string[];
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// The set of transactions to be invoked
|
||||
const transactionsToSend: TransactionToSendFormat[] = [
|
||||
{
|
||||
request: 'submit',
|
||||
txName: 'InitLedger',
|
||||
txArgs: [],
|
||||
description: '\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger',
|
||||
},
|
||||
{
|
||||
request: 'evaluate',
|
||||
txName: 'GetAllAssets',
|
||||
txArgs: [],
|
||||
description: '\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger',
|
||||
},
|
||||
{
|
||||
request: 'submit',
|
||||
txName: 'CreateAsset',
|
||||
txArgs: ['asset13', 'yellow', '5', 'Tom', '1300'],
|
||||
description: '\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments',
|
||||
},
|
||||
{
|
||||
request: 'evaluate',
|
||||
txName: 'ReadAsset',
|
||||
txArgs: ['asset13'],
|
||||
description: '\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID',
|
||||
},
|
||||
{
|
||||
request: 'evaluate',
|
||||
txName: 'AssetExists',
|
||||
txArgs: ['asset1'],
|
||||
description: '\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with given assetID exist',
|
||||
},
|
||||
{
|
||||
request: 'submit',
|
||||
txName: 'UpdateAsset',
|
||||
txArgs: ['asset1', 'blue', '5', 'Tomoko', '350'],
|
||||
description: '\n--> Submit Transaction: UpdateAsset asset1, change the appraisedValue to 350',
|
||||
},
|
||||
{
|
||||
request: 'evaluate',
|
||||
txName: 'ReadAsset',
|
||||
txArgs: ['asset1'],
|
||||
description: '\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes',
|
||||
},
|
||||
{
|
||||
request: 'submit',
|
||||
txName: 'UpdateAsset',
|
||||
txArgs: ['asset70', 'blue', '5', 'Tomoko', '300'],
|
||||
description: '\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error',
|
||||
},
|
||||
{
|
||||
request: 'submit',
|
||||
txName: 'TransferAsset',
|
||||
txArgs: ['asset1', 'Tom'],
|
||||
description: '\n--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom',
|
||||
},
|
||||
{
|
||||
request: 'evaluate',
|
||||
txName: 'ReadAsset',
|
||||
txArgs: ['asset1'],
|
||||
description: '\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* A test application to show basic queries operations with any of the asset-transfer-basic chaincodes
|
||||
* -- How to submit a transaction
|
||||
* -- How to query and check the results
|
||||
*
|
||||
* To see the SDK workings, try setting the logging to show on the console before running
|
||||
* export HFC_LOGGING='{"debug":"console"}'
|
||||
*/
|
||||
async function main() {
|
||||
try {
|
||||
// build an in memory object with the network configuration (also known as a connection profile)
|
||||
const ccp = buildCCPOrg1();
|
||||
|
||||
// softhsm pkcs11 options to use
|
||||
const softHSMOptions: HsmOptions = {
|
||||
lib: await findSoftHSMPKCS11Lib(),
|
||||
pin: process.env.PKCS11_PIN || '98765432',
|
||||
label: process.env.PKCS11_LABEL || 'ForFabric',
|
||||
};
|
||||
|
||||
// setup the wallet to hold the credentials used by this application
|
||||
// Here we add the HSM provider to the provider registry of the wallet
|
||||
const wallet = await Wallets.newFileSystemWallet(walletPath);
|
||||
const hsmProvider = new HsmX509Provider(softHSMOptions);
|
||||
wallet.getProviderRegistry().addProvider(hsmProvider);
|
||||
|
||||
// build an instance of the fabric ca services client based on
|
||||
// the information in the network configuration
|
||||
const caInfo = ccp.certificateAuthorities['ca.org1.example.com']; // lookup CA details from config
|
||||
const caTLSCACerts = caInfo.tlsCACerts.pem;
|
||||
|
||||
// Here we make sure we pass in an HSM enabled cryptosuite to this CA instance
|
||||
const hsmCAClient = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName, hsmProvider.getCryptoSuite());
|
||||
console.log(`Built a CA Client named ${caInfo.caName}`);
|
||||
|
||||
// enroll the CA admin into the wallet if it doesn't already exist in the wallet
|
||||
if (!await wallet.get(org1CAAdminUserId)) {
|
||||
await enrollUserToWallet({
|
||||
caClient: hsmCAClient,
|
||||
wallet,
|
||||
orgMspId: mspOrg1,
|
||||
userId: org1CAAdminUserId,
|
||||
userIdSecret: org1CAAdminSecret,
|
||||
} as UserToEnroll);
|
||||
}
|
||||
|
||||
// if we don't have the required user in the wallet then
|
||||
// register this user to the CA (if it's not already registered)
|
||||
// then enroll that user into the wallet
|
||||
if (!await wallet.get(org1UserId)) {
|
||||
await registerUser({
|
||||
caClient: hsmCAClient,
|
||||
adminId: org1CAAdminUserId,
|
||||
wallet,
|
||||
orgMspId: mspOrg1,
|
||||
userId: org1UserId,
|
||||
userIdSecret: org1Secret,
|
||||
affiliation: org1Affiliation,
|
||||
} as UserToRegister);
|
||||
|
||||
// By default you can only enroll a user once, after that you would have to re-enroll using the current
|
||||
// certificate rather than using the secret.
|
||||
await enrollUserToWallet({
|
||||
caClient: hsmCAClient,
|
||||
wallet,
|
||||
orgMspId: mspOrg1,
|
||||
userId: org1UserId,
|
||||
userIdSecret: org1Secret,
|
||||
} as UserToEnroll);
|
||||
}
|
||||
|
||||
// Create a new gateway instance for interacting with the fabric network bound to our user
|
||||
// This sample expects a locally deployed fabric network (ie running on the same machine in docker containers)
|
||||
// therefore we set asLocalhost to true
|
||||
const gateway = new Gateway();
|
||||
|
||||
const gatewayOpts: GatewayOptions = {
|
||||
wallet,
|
||||
identity: org1UserId,
|
||||
discovery: { enabled: true, asLocalhost: true }, // using asLocalhost as this gateway is using a fabric network deployed locally
|
||||
};
|
||||
|
||||
try {
|
||||
// setup the gateway instance
|
||||
// The user will now be able to create connections to the fabric network and be able to
|
||||
// submit transactions and query. All transactions submitted by this gateway will be
|
||||
// signed by this user using the credentials stored in the wallet.
|
||||
await gateway.connect(ccp, gatewayOpts);
|
||||
|
||||
// Build a network instance based on the channel where the smart contract is deployed
|
||||
const network = await gateway.getNetwork(channelName);
|
||||
|
||||
// Get the contract from the network.
|
||||
const contract = network.getContract(chaincodeId);
|
||||
|
||||
// loop around all transactions to send, each one will be sent sequentially
|
||||
// through the same gateway/network/contract as subsequent transations expect the
|
||||
// previous submits to have been committed
|
||||
// Note however that a gateway/network/contract can support concurrent submitted
|
||||
// transactions.
|
||||
for (const transactionToSend of transactionsToSend) {
|
||||
await interactWithFabric(contract, transactionToSend);
|
||||
}
|
||||
|
||||
console.log('*** The sample ran successfully ***');
|
||||
} finally {
|
||||
|
||||
// Disconnect from the gateway when the application is closing
|
||||
// This will close all connections to the network
|
||||
gateway.disconnect();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the location of the SoftHSM PKCS11 Library
|
||||
* @returns the location of the SoftHSM PKCS11 Library or 'NOT FOUND' if not found
|
||||
*/
|
||||
async function findSoftHSMPKCS11Lib() {
|
||||
const commonSoftHSMPathNames = [
|
||||
'/usr/lib/softhsm/libsofthsm2.so',
|
||||
'/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so',
|
||||
'/usr/local/lib/softhsm/libsofthsm2.so',
|
||||
'/usr/lib/libacsp-pkcs11.so',
|
||||
];
|
||||
|
||||
let pkcsLibPath = 'NOT FOUND';
|
||||
if (typeof process.env.PKCS11_LIB === 'string' && process.env.PKCS11_LIB !== '') {
|
||||
pkcsLibPath = process.env.PKCS11_LIB;
|
||||
} else {
|
||||
//
|
||||
// Check common locations for PKCS library
|
||||
//
|
||||
for (const pathnameToTry of commonSoftHSMPathNames) {
|
||||
if (fs.existsSync(pathnameToTry)) {
|
||||
pkcsLibPath = pathnameToTry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pkcsLibPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interact with the Fabric Network via the Contact with the required transaction to perform.
|
||||
* @param contract The contract to send the transaction to
|
||||
* @param transactionToPerform the transaction to perform
|
||||
*/
|
||||
async function interactWithFabric(contract: Contract, transactionToPerform: TransactionToSendFormat): Promise<void> {
|
||||
console.log(transactionToPerform.description);
|
||||
try {
|
||||
switch (transactionToPerform.request) {
|
||||
case 'submit': {
|
||||
await contract.submitTransaction(transactionToPerform.txName, ...transactionToPerform.txArgs);
|
||||
console.log('*** Result: committed');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'evaluate': {
|
||||
const result = await contract.evaluateTransaction(transactionToPerform.txName, ...transactionToPerform.txArgs);
|
||||
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`*** Successfully caught the error: \n ${error}`);
|
||||
// In reality applications should check the returned error to decide whether the transaction needs to be retried (ie go through
|
||||
// endorsement again) for example an MVCC_READ_CONFLICT error, resubmitted to the orderer for example a timeout because it was
|
||||
// never committed (for example due to networking issues the transaction never gets included in a block), or whether it should
|
||||
// be reported back, for example they tried to perform an invalid application action.
|
||||
}
|
||||
}
|
||||
|
||||
// execute the main function
|
||||
main();
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const buildCCPOrg1 = (): Record<string, any> => {
|
||||
// load the common connection configuration file
|
||||
const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network',
|
||||
'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json');
|
||||
const fileExists = fs.existsSync(ccpPath);
|
||||
if (!fileExists) {
|
||||
throw new Error(`no such file or directory: ${ccpPath}`);
|
||||
}
|
||||
const contents = fs.readFileSync(ccpPath, 'utf8');
|
||||
|
||||
// build a JSON object from the file contents
|
||||
const ccp = JSON.parse(contents);
|
||||
|
||||
console.log(`Loaded the network configuration located at ${ccpPath}`);
|
||||
return ccp;
|
||||
};
|
||||
|
||||
const buildCCPOrg2 = (): Record<string, any> => {
|
||||
// load the common connection configuration file
|
||||
const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network',
|
||||
'organizations', 'peerOrganizations', 'org2.example.com', 'connection-org2.json');
|
||||
const fileExists = fs.existsSync(ccpPath);
|
||||
if (!fileExists) {
|
||||
throw new Error(`no such file or directory: ${ccpPath}`);
|
||||
}
|
||||
const contents = fs.readFileSync(ccpPath, 'utf8');
|
||||
|
||||
// build a JSON object from the file contents
|
||||
const ccp = JSON.parse(contents);
|
||||
|
||||
console.log(`Loaded the network configuration located at ${ccpPath}`);
|
||||
return ccp;
|
||||
};
|
||||
|
||||
const prettyJSONString = (inputString: string): string => {
|
||||
if (inputString) {
|
||||
return JSON.stringify(JSON.parse(inputString), null, 2);
|
||||
} else {
|
||||
return inputString;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
buildCCPOrg1,
|
||||
buildCCPOrg2,
|
||||
prettyJSONString,
|
||||
};
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as FabricCAServices from 'fabric-ca-client';
|
||||
import { Wallet } from 'fabric-network';
|
||||
|
||||
export interface UserToEnroll {
|
||||
caClient: FabricCAServices;
|
||||
wallet: Wallet;
|
||||
orgMspId: string;
|
||||
userId: string;
|
||||
userIdSecret: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* enroll a registered CA user and store the credentials in the wallet
|
||||
* @param userToEnroll details about the user and the wallet to use
|
||||
*/
|
||||
export const enrollUserToWallet = async (userToEnroll: UserToEnroll): Promise<void> => {
|
||||
try {
|
||||
|
||||
// check that the identity isn't already in the wallet
|
||||
const identity = await userToEnroll.wallet.get(userToEnroll.userId);
|
||||
if (identity) {
|
||||
console.log(`Identity ${userToEnroll.userId} already exists in the wallet`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Enroll the user
|
||||
const enrollment = await userToEnroll.caClient.enroll({ enrollmentID: userToEnroll.userId, enrollmentSecret: userToEnroll.userIdSecret });
|
||||
|
||||
// store the user
|
||||
const hsmIdentity = {
|
||||
credentials: {
|
||||
certificate: enrollment.certificate,
|
||||
},
|
||||
mspId: userToEnroll.orgMspId,
|
||||
type: 'HSM-X.509',
|
||||
};
|
||||
await userToEnroll.wallet.put(userToEnroll.userId, hsmIdentity);
|
||||
console.log(`Successfully enrolled user ${userToEnroll.userId} and imported it into the wallet`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to enroll user ${userToEnroll.userId}: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
export interface UserToRegister {
|
||||
caClient: FabricCAServices;
|
||||
wallet: Wallet;
|
||||
orgMspId: string;
|
||||
adminId: string;
|
||||
userId: string;
|
||||
userIdSecret: string;
|
||||
affiliation: string;
|
||||
}
|
||||
|
||||
export const registerUser = async (userToRegister: UserToRegister): Promise<void> => {
|
||||
try {
|
||||
|
||||
// Must use a CA admin (registrar) to register a new user
|
||||
const adminIdentity = await userToRegister.wallet.get(userToRegister.adminId);
|
||||
if (!adminIdentity) {
|
||||
console.log('An identity for the admin user does not exist in the wallet');
|
||||
console.log('Enroll the admin user before retrying');
|
||||
return;
|
||||
}
|
||||
|
||||
// build a user object for authenticating with the CA
|
||||
const provider = userToRegister.wallet.getProviderRegistry().getProvider(adminIdentity.type);
|
||||
const adminUser = await provider.getUserContext(adminIdentity, userToRegister.adminId);
|
||||
|
||||
// Register the user
|
||||
// if affiliation is specified by client, the affiliation value must be configured in CA
|
||||
await userToRegister.caClient.register({
|
||||
affiliation: userToRegister.affiliation,
|
||||
enrollmentID: userToRegister.userId,
|
||||
enrollmentSecret: userToRegister.userIdSecret,
|
||||
role: 'client',
|
||||
}, adminUser);
|
||||
console.log(`Successfully registered ${userToRegister.userId}.`);
|
||||
return;
|
||||
|
||||
} catch (error) {
|
||||
// check to see if it's an already registered error, if it is, then we can ignore it
|
||||
// otherwise we rethrow the error
|
||||
if (error.errors[0].code !== 74) {
|
||||
console.error(`Failed to register user : ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"target": "es2017",
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"noImplicitAny": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
24
asset-transfer-basic/application-typescript-hsm/tslint.json
Normal file
24
asset-transfer-basic/application-typescript-hsm/tslint.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"indent": [true, "spaces", 4],
|
||||
"linebreak-style": [true, "LF"],
|
||||
"quotemark": [true, "single"],
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"curly": true,
|
||||
"triple-equals": true,
|
||||
"no-string-throw": true,
|
||||
"no-var-keyword": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"max-line-length": false,
|
||||
"interface-name":false
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
*.tar.gz
|
||||
*.tgz
|
||||
crypto/*.pem
|
||||
|
|
|
|||
|
|
@ -172,3 +172,65 @@ node app.js
|
|||
```
|
||||
|
||||
If all goes well, the program should run exactly the same as described in the "Writing Your First Application" tutorial.
|
||||
|
||||
## Enabling TLS for chaincode and peer communication
|
||||
|
||||
**Note:** This section uses an example of self-signed certificate. You may use your organization hosted CA to issue the certificate and generate a key for production deployment.
|
||||
|
||||
In the sample so far, you connected both peers in `test-network` to the single instance of chaincode server. However, if you would like to enable TLS between the peer nodes and the chaincode server, each peer node needs to have its own CA certificate. Enabling TLS is made possible at runtime in the chaincode.
|
||||
|
||||
- As a first step generate a keypair that can be used. Run these commands from the `fabric-samples/asset-transfer-basic/chaincode-external` directory.
|
||||
|
||||
*Find instructions to install `openssl` in [openssl.org](https://www.openssl.org/)*
|
||||
|
||||
For `org1.example.com`
|
||||
|
||||
```
|
||||
openssl req -nodes -x509 -newkey rsa:4096 -keyout crypto/key1.pem -out crypto/cert1.pem -subj "/C=IN/ST=KA/L=Bangalore/O=example Inc/OU=Developer/CN=asset-transfer-basic.org1.example.com/emailAddress=dev@asset-transfer-basic.org1.example.com"
|
||||
```
|
||||
|
||||
For `org2.example.com`
|
||||
|
||||
```
|
||||
openssl req -nodes -x509 -newkey rsa:4096 -keyout crypto/key2.pem -out crypto/cert2.pem -subj "/C=IN/ST=KA/L=Bangalore/O=example Inc/OU=Developer/CN=asset-transfer-basic.org2.example.com/emailAddress=dev@asset-transfer-basic.org2.example.com"
|
||||
```
|
||||
|
||||
- Copy the CA file contents for both `org1.example.com` & `org2.example.com`
|
||||
|
||||
```
|
||||
cp ../../test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem crypto/rootcert1.pem
|
||||
cp ../../test-network/organizations/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem crypto/rootcert2.pem
|
||||
```
|
||||
|
||||
- Generate a client key and cert for auth purpose. You need a key and cert generated from the CA of each organization. Peer nodes act as clients to chaincode server.
|
||||
|
||||
- Change the `connection.json` with the below contents. The `root_cert` parameter is the root CA certificate which the chaincode server is run with. You may run the below commands to get the certificate file contents as strings and copy them when needed.
|
||||
|
||||
```
|
||||
awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' crypto/cert1.pem
|
||||
awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' crypto/cert2.pem
|
||||
```
|
||||
|
||||
Similarly, replace the `client_key` and the `client_cert` contents with the values from the previous step.
|
||||
|
||||
```
|
||||
{
|
||||
"address": "asset-transfer-basic.org1.example.com:9999",
|
||||
"dial_timeout": "10s",
|
||||
"tls_required": true,
|
||||
"client_auth_required": true,
|
||||
"client_key": "-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----",
|
||||
"client_cert": "-----BEGIN CERTIFICATE---- ... -----END CERTIFICATE-----",
|
||||
"root_cert": "-----BEGIN CERTIFICATE---- ... -----END CERTIFICATE-----"
|
||||
}
|
||||
```
|
||||
|
||||
- Follow the instructions in [Package](#packaging-and-installing-chaincode) and [Install](#installing-the-external-chaincode) steps for each organization. Remember that the chaincode server's address for the second organization is `asset-transfer-basic.org2.example.com:9999`.
|
||||
|
||||
- Copy the appropriate `CHAINCODE_ID` to both [chaincode1.env](./chaincode1.env) and [chaincode2.env](./chaincode2.env) files. Bring up the chaincode containers using the docker-compose command below
|
||||
|
||||
```
|
||||
docker-compose up -f docker-compose-chaincode.yaml up --build -d
|
||||
```
|
||||
|
||||
- Follow the instructions in [Finish Deployment](#finish-deploying-the-asset-transfer-basic-external-chaincode-) for each organization seperately.
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
|
|
@ -224,12 +226,66 @@ func main() {
|
|||
CCID: config.CCID,
|
||||
Address: config.Address,
|
||||
CC: chaincode,
|
||||
TLSProps: shim.TLSProperties{
|
||||
Disabled: true,
|
||||
},
|
||||
TLSProps: getTLSProperties(),
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
log.Panicf("error starting asset-transfer-basic chaincode: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getTLSProperties() shim.TLSProperties {
|
||||
// Check if chaincode is TLS enabled
|
||||
tlsDisabledStr := getEnvOrDefault("CHAINCODE_TLS_DISABLED", "true")
|
||||
key := getEnvOrDefault("CHAINCODE_TLS_KEY", "")
|
||||
cert := getEnvOrDefault("CHAINCODE_TLS_CERT", "")
|
||||
clientCACert := getEnvOrDefault("CHAINCODE_CLIENT_CA_CERT", "")
|
||||
|
||||
// convert tlsDisabledStr to boolean
|
||||
tlsDisabled := getBoolOrDefault(tlsDisabledStr, false)
|
||||
var keyBytes, certBytes, clientCACertBytes []byte
|
||||
var err error
|
||||
|
||||
if !tlsDisabled {
|
||||
keyBytes, err = ioutil.ReadFile(key)
|
||||
if err != nil {
|
||||
log.Panicf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
certBytes, err = ioutil.ReadFile(cert)
|
||||
if err != nil {
|
||||
log.Panicf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
}
|
||||
// Did not request for the peer cert verification
|
||||
if clientCACert != "" {
|
||||
clientCACertBytes, err = ioutil.ReadFile(clientCACert)
|
||||
if err != nil {
|
||||
log.Panicf("error while reading the crypto file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return shim.TLSProperties{
|
||||
Disabled: tlsDisabled,
|
||||
Key: keyBytes,
|
||||
Cert: certBytes,
|
||||
ClientCACerts: clientCACertBytes,
|
||||
}
|
||||
}
|
||||
|
||||
func getEnvOrDefault(env, defaultVal string) string {
|
||||
value, ok := os.LookupEnv(env)
|
||||
if !ok {
|
||||
value = defaultVal
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Note that the method returns default value if the string
|
||||
// cannot be parsed!
|
||||
func getBoolOrDefault(value string, defaultVal bool) bool {
|
||||
parsed, err := strconv.ParseBool(value)
|
||||
if err!= nil {
|
||||
return defaultVal
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,3 +6,19 @@ CHAINCODE_SERVER_ADDRESS=asset-transfer-basic.org1.example.com:9999
|
|||
# on install. The `peer lifecycle chaincode queryinstalled` command can be
|
||||
# used to get the ID after install if required
|
||||
CHAINCODE_ID=basic_1.0:0262396ccaffaa2174bc09f750f742319c4f14d60b16334d2c8921b6842c090c
|
||||
|
||||
# Optional parameters that will be used for TLS connection between peer node
|
||||
# and the chaincode.
|
||||
# TLS is disabled by default, uncomment the following line to enable TLS connection
|
||||
# CHAINCODE_TLS_DISABLED=false
|
||||
|
||||
# Following variables will be ignored if TLS is not enabled.
|
||||
# They need to be in PEM format
|
||||
# CHAINCODE_TLS_KEY=/path/to/private/key/file
|
||||
# CHAINCODE_TLS_CERT=/path/to/public/cert/file
|
||||
|
||||
# The following variable will be used by the chaincode server to verify the
|
||||
# connection from the peer node.
|
||||
# Note that when this is set a single chaincode server cannot be shared
|
||||
# across organizations unless their root CA is same.
|
||||
# CHAINCODE_CLIENT_CA_CERT=/path/to/peer/organization/root/ca/cert/file
|
||||
|
|
|
|||
24
asset-transfer-basic/chaincode-external/chaincode1.env
Normal file
24
asset-transfer-basic/chaincode-external/chaincode1.env
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# CHAINCODE_SERVER_ADDRESS must be set to the host and port where the peer can
|
||||
# connect to the chaincode server
|
||||
CHAINCODE_SERVER_ADDRESS=asset-transfer-basic.org1.example.com:9999
|
||||
|
||||
# CHAINCODE_ID must be set to the Package ID that is assigned to the chaincode
|
||||
# on install. The `peer lifecycle chaincode queryinstalled` command can be
|
||||
# used to get the ID after install if required
|
||||
CHAINCODE_ID=basic_1.0:6726c6b6d8ff66fcf5710b72c6ce512d24f118c51c3de510b3d43e51fa592a7d
|
||||
|
||||
# Optional parameters that will be used for TLS connection between peer node
|
||||
# and the chaincode.
|
||||
# TLS is disabled by default, uncomment the following line to enable TLS connection
|
||||
CHAINCODE_TLS_DISABLED=false
|
||||
|
||||
# Following variables will be ignored if TLS is not enabled.
|
||||
# They need to be in PEM format
|
||||
CHAINCODE_TLS_KEY=/crypto/key1.pem
|
||||
CHAINCODE_TLS_CERT=/crypto/cert1.pem
|
||||
|
||||
# The following variable will be used by the chaincode server to verify the
|
||||
# connection from the peer node.
|
||||
# Note that when this is set a single chaincode server cannot be shared
|
||||
# across organizations unless their root CA is same.
|
||||
CHAINCODE_CLIENT_CA_CERT=/crypto/rootcert1.pem
|
||||
24
asset-transfer-basic/chaincode-external/chaincode2.env
Normal file
24
asset-transfer-basic/chaincode-external/chaincode2.env
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# CHAINCODE_SERVER_ADDRESS must be set to the host and port where the peer can
|
||||
# connect to the chaincode server
|
||||
CHAINCODE_SERVER_ADDRESS=asset-transfer-basic.org2.example.com:9999
|
||||
|
||||
# CHAINCODE_ID must be set to the Package ID that is assigned to the chaincode
|
||||
# on install. The `peer lifecycle chaincode queryinstalled` command can be
|
||||
# used to get the ID after install if required
|
||||
CHAINCODE_ID=basic_1.0:e8f9052385e3763ecf5635591155da05d8efbb6905ccbfc1c7229eb6bd28df1b
|
||||
|
||||
# Optional parameters that will be used for TLS connection between peer node
|
||||
# and the chaincode.
|
||||
# TLS is disabled by default, uncomment the following line to enable TLS connection
|
||||
CHAINCODE_TLS_DISABLED=false
|
||||
|
||||
# Following variables will be ignored if TLS is not enabled.
|
||||
# They need to be in PEM format
|
||||
CHAINCODE_TLS_KEY=/crypto/key2.pem
|
||||
CHAINCODE_TLS_CERT=/crypto/cert2.pem
|
||||
|
||||
# The following variable will be used by the chaincode server to verify the
|
||||
# connection from the peer node.
|
||||
# Note that when this is set a single chaincode server cannot be shared
|
||||
# across organizations unless their root CA is same.
|
||||
CHAINCODE_CLIENT_CA_CERT=/crypto/rootcert2.pem
|
||||
0
asset-transfer-basic/chaincode-external/crypto/.gitkeep
Normal file
0
asset-transfer-basic/chaincode-external/crypto/.gitkeep
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
version: "3.6"
|
||||
|
||||
networks:
|
||||
docker_test:
|
||||
external: true
|
||||
|
||||
services:
|
||||
asset-transfer-basic.org1.example.com:
|
||||
build: .
|
||||
container_name: asset-transfer-basic.org1.example.com
|
||||
hostname: asset-transfer-basic.org1.example.com
|
||||
volumes:
|
||||
- ./crypto:/crypto
|
||||
env_file:
|
||||
- chaincode1.env
|
||||
networks:
|
||||
docker_test:
|
||||
expose:
|
||||
- 9999
|
||||
|
||||
asset-transfer-basic.org2.example.com:
|
||||
build: .
|
||||
container_name: asset-transfer-basic.org2.example.com
|
||||
hostname: asset-transfer-basic.org2.example.com
|
||||
volumes:
|
||||
- ./crypto:/crypto
|
||||
env_file:
|
||||
- chaincode2.env
|
||||
networks:
|
||||
docker_test:
|
||||
expose:
|
||||
- 9999
|
||||
|
|
@ -14,7 +14,8 @@ version '1.0-SNAPSHOT'
|
|||
|
||||
dependencies {
|
||||
|
||||
compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
implementation 'org.json:json:+'
|
||||
implementation 'com.owlike:genson:1.5'
|
||||
testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
|
||||
|
|
|
|||
|
|
@ -65,6 +65,11 @@ class AssetTransfer extends Contract {
|
|||
|
||||
// CreateAsset issues a new asset to the world state with given details.
|
||||
async CreateAsset(ctx, id, color, size, owner, appraisedValue) {
|
||||
const exists = await this.AssetExists(ctx, id);
|
||||
if (exists) {
|
||||
throw new Error(`The asset ${id} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
Color: color,
|
||||
|
|
@ -72,7 +77,7 @@ class AssetTransfer extends Contract {
|
|||
Owner: owner,
|
||||
AppraisedValue: appraisedValue,
|
||||
};
|
||||
ctx.stub.putState(id, Buffer.from(JSON.stringify(asset)));
|
||||
await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset)));
|
||||
return JSON.stringify(asset);
|
||||
}
|
||||
|
||||
|
|
@ -146,8 +151,6 @@ class AssetTransfer extends Contract {
|
|||
}
|
||||
return JSON.stringify(allResults);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = AssetTransfer;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
|
|
|
|||
|
|
@ -65,6 +65,11 @@ export class AssetTransferContract extends Contract {
|
|||
// CreateAsset issues a new asset to the world state with given details.
|
||||
@Transaction()
|
||||
public async CreateAsset(ctx: Context, id: string, color: string, size: number, owner: string, appraisedValue: number): Promise<void> {
|
||||
const exists = await this.AssetExists(ctx, id);
|
||||
if (exists) {
|
||||
throw new Error(`The asset ${id} already exists`);
|
||||
}
|
||||
|
||||
const asset = {
|
||||
ID: id,
|
||||
Color: color,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ group 'org.hyperledger.fabric.samples'
|
|||
version '1.0-SNAPSHOT'
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
implementation 'org.json:json:+'
|
||||
testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
const sinon = require('sinon');
|
||||
const chai = require('chai');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// ====CHAINCODE EXECUTION SAMPLES (CLI) ==================
|
||||
|
|
@ -179,7 +181,7 @@ class Chaincode extends Contract {
|
|||
async GetAssetsByRange(ctx, startKey, endKey) {
|
||||
|
||||
let resultsIterator = await ctx.stub.getStateByRange(startKey, endKey);
|
||||
let results = await this.GetAllResults(resultsIterator, false);
|
||||
let results = await this._GetAllResults(resultsIterator, false);
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
|
@ -247,7 +249,7 @@ class Chaincode extends Contract {
|
|||
async GetQueryResultForQueryString(ctx, queryString) {
|
||||
|
||||
let resultsIterator = await ctx.stub.getQueryResult(queryString);
|
||||
let results = await this.GetAllResults(resultsIterator, false);
|
||||
let results = await this._GetAllResults(resultsIterator, false);
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
|
@ -260,7 +262,7 @@ class Chaincode extends Contract {
|
|||
async GetAssetsByRangeWithPagination(ctx, startKey, endKey, pageSize, bookmark) {
|
||||
|
||||
const {iterator, metadata} = await ctx.stub.getStateByRangeWithPagination(startKey, endKey, pageSize, bookmark);
|
||||
const results = await this.GetAllResults(iterator, false);
|
||||
const results = await this._GetAllResults(iterator, false);
|
||||
|
||||
results.ResponseMetadata = {
|
||||
RecordsCount: metadata.fetched_records_count,
|
||||
|
|
@ -280,7 +282,7 @@ class Chaincode extends Contract {
|
|||
async QueryAssetsWithPagination(ctx, queryString, pageSize, bookmark) {
|
||||
|
||||
const {iterator, metadata} = await ctx.stub.getQueryResultWithPagination(queryString, pageSize, bookmark);
|
||||
const results = await this.GetAllResults(iterator, false);
|
||||
const results = await this._GetAllResults(iterator, false);
|
||||
|
||||
results.ResponseMetadata = {
|
||||
RecordsCount: metadata.fetched_records_count,
|
||||
|
|
@ -294,7 +296,7 @@ class Chaincode extends Contract {
|
|||
async GetAssetHistory(ctx, assetName) {
|
||||
|
||||
let resultsIterator = await ctx.stub.getHistoryForKey(assetName);
|
||||
let results = await this.GetAllResults(resultsIterator, true);
|
||||
let results = await this._GetAllResults(resultsIterator, true);
|
||||
|
||||
return JSON.stringify(results);
|
||||
}
|
||||
|
|
@ -306,7 +308,11 @@ class Chaincode extends Contract {
|
|||
return assetState && assetState.length > 0;
|
||||
}
|
||||
|
||||
async GetAllResults(iterator, isHistory) {
|
||||
// This is JavaScript so without Funcation Decorators, all functions are assumed
|
||||
// to be transaction functions
|
||||
//
|
||||
// For internal functions... prefix them with _
|
||||
async _GetAllResults(iterator, isHistory) {
|
||||
let allResults = [];
|
||||
let res = await iterator.next();
|
||||
while (!res.done) {
|
||||
|
|
@ -314,7 +320,7 @@ class Chaincode extends Contract {
|
|||
let jsonRes = {};
|
||||
console.log(res.value.value.toString('utf8'));
|
||||
if (isHistory && isHistory === true) {
|
||||
jsonRes.TxId = res.value.tx_id;
|
||||
jsonRes.TxId = res.value.txId;
|
||||
jsonRes.Timestamp = res.value.timestamp;
|
||||
try {
|
||||
jsonRes.Value = JSON.parse(res.value.value.toString('utf8'));
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ version '1.0-SNAPSHOT'
|
|||
|
||||
dependencies {
|
||||
|
||||
compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
implementation 'org.json:json:+'
|
||||
|
||||
testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
|
||||
|
|
|
|||
|
|
@ -14,8 +14,12 @@ version '1.0-SNAPSHOT'
|
|||
|
||||
dependencies {
|
||||
|
||||
compileOnly 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
implementation 'org.json:json:+'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.+'
|
||||
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-protos:2.+'
|
||||
implementation 'com.owlike:genson:1.5'
|
||||
|
||||
testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.+'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
|
||||
testImplementation 'org.assertj:assertj-core:3.11.1'
|
||||
|
|
|
|||
615
auction-dutch/README.md
Normal file
615
auction-dutch/README.md
Normal file
|
|
@ -0,0 +1,615 @@
|
|||
## Dutch auction
|
||||
|
||||
This example allows you to run a [Dutch auction](https://en.wikipedia.org/wiki/Dutch_auction) that sells multiple items of the same good. All items are sold at the price that clears the auction. You also have the option of adding an auditor organization to the auction. If the organizations running the auction cannot agree, or encounter a technical error that prevents them from updating the auction, one of the auction participants can appeal to an auditor organization. The dutch auction smart contract provides an example of how create a complex signature policy by creating a protobuf and then using the policy for state based endorsement.
|
||||
|
||||
This tutorial uses the example smart contract to run an auction in which a single seller wants to sell 100 tickets to multiple bidders. If you chose to add an auditor to the auction, you can appeal to the auditor to end the auction by overriding the standard auction endorsement policy.
|
||||
|
||||
## Deploy the chaincode
|
||||
|
||||
Change into the test network directory.
|
||||
```
|
||||
cd fabric-samples/test-network
|
||||
```
|
||||
|
||||
If the test network is already running, run the following command to bring the network down and start from a clean initial state.
|
||||
```
|
||||
./network.sh down
|
||||
```
|
||||
|
||||
You can then run the following command to deploy a new network.
|
||||
```
|
||||
./network.sh up createChannel -ca
|
||||
```
|
||||
|
||||
Run the following command to deploy the dutch auction smart contract.
|
||||
```
|
||||
./network.sh deployCC -ccn auction -ccp ../auction-dutch/chaincode-go/ -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl go
|
||||
```
|
||||
|
||||
Note that we deploy the smart contract with an endorsement policy of `"OR('Org1MSP.peer','Org2MSP.peer')" ` instead of using the default endorsement policy of the majority of orgs on the channel. Either Org1 or Org2 can create an auction without the endorsement of the other organization.
|
||||
|
||||
## Add an auditor (optional)
|
||||
|
||||
The smart contract allows you to add an auditor organization to the auction. The auditor can add bids, close the auction, or end the auction if participants cannot cooperate. In this tutorial, we will add the Org3 organization to the test network channel and install an auditor specific version of the dutch auction smart contract. This allows you to use Org3 as the auditor organization.
|
||||
|
||||
From the `test-network` directory, issue the following commands to add Org3 to the channel:
|
||||
|
||||
```
|
||||
cd addOrg3
|
||||
./addOrg3.sh up
|
||||
```
|
||||
|
||||
Navigate back to the test network directory:
|
||||
```
|
||||
cd ..
|
||||
```
|
||||
|
||||
Set the following environment to interact with the test network as Org3.
|
||||
```
|
||||
export PATH=${PWD}/../bin:$PATH
|
||||
export FABRIC_CFG_PATH=${PWD}/../config/
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_LOCALMSPID="Org3MSP"
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt
|
||||
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp
|
||||
export CORE_PEER_ADDRESS=localhost:11051
|
||||
```
|
||||
|
||||
To deploy the smart contract on the Org3 peer, we need to use the peer lifecycle chaincode commands to install the chaincode package and approve the chaincode definition as Org3. Run the following command to package the auditor version of the dutch auction smart contract:
|
||||
```
|
||||
peer lifecycle chaincode package auction.tar.gz --path ../auction-dutch/chaincode-go-auditor/ --lang golang --label auction_1
|
||||
```
|
||||
Install the chaincode package on the Org3 peer:
|
||||
```
|
||||
peer lifecycle chaincode install auction.tar.gz
|
||||
```
|
||||
|
||||
The next step is to approve the chaincode as the Org3 admin. This requires getting the package ID of the chaincode that we just installed.
|
||||
```
|
||||
peer lifecycle chaincode queryinstalled
|
||||
```
|
||||
|
||||
The command should return a response similar to the following:
|
||||
```
|
||||
Installed chaincodes on peer:
|
||||
Package ID: auction_1:8f0d6b6b5a616a1c2b6a9268418f2ee65718acc3c07ea12e123b189b3fb4fb14, Label: auction_1
|
||||
```
|
||||
|
||||
Save the package ID returned by the command above as an environment variable. The package ID will not be the same for all users, so you need to complete this step using the package ID returned from your console.
|
||||
```
|
||||
export CC_PACKAGE_ID=auction_1:8f0d6b6b5a616a1c2b6a9268418f2ee65718acc3c07ea12e123b189b3fb4fb14
|
||||
```
|
||||
|
||||
You can now approve the auction chaincode for Org3:
|
||||
```
|
||||
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --channelID mychannel --name auction --version 1.0 --package-id $CC_PACKAGE_ID --sequence 1 --signature-policy "OR('Org1MSP.peer','Org2MSP.peer')"
|
||||
```
|
||||
|
||||
The command will start the dutch auction chaincode on the Org3 peer. Note that we did not update the endorsement policy before we added the auditor organization. Only Org1 and Org2 will be able create an auction. The auditor is added the endorsement policy after the auction is created. Because the auditor does not need to create an auction or create new bids, the auditor can run a different version of the smart contract than the auction participants. The auditor version of the smart contract also adds logic to check that the request is submitted by one of the auction participants before the auditor can intervene.
|
||||
|
||||
## Install the application dependencies
|
||||
|
||||
We will run the dutch auction using a series of Node.js applications. Change into the `application-javascript` directory:
|
||||
```
|
||||
cd fabric-samples/auction-dutch/application-javascript
|
||||
```
|
||||
|
||||
From this directory, run the following command to download the application dependencies if you have not done so already:
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
## Register and enroll the application identities
|
||||
|
||||
To interact with the network, you will need to enroll the Certificate Authority administrators of Org1 and Org2. You can use the `enrollAdmin.js` program for this task. Run the following command to enroll the Org1 admin:
|
||||
```
|
||||
node enrollAdmin.js org1
|
||||
```
|
||||
You should see the logs of the admin wallet being created on your local file system. Now run the command to enroll the CA admin of Org2:
|
||||
```
|
||||
node enrollAdmin.js org2
|
||||
```
|
||||
|
||||
We can use the CA admins of both organizations to register and enroll the identities of the seller that will create the auction and the bidders who will try to purchase the tickets. Run the following command to register and enroll the seller identity that will create the auction. The seller will belong to Org1.
|
||||
```
|
||||
node registerEnrollUser.js org1 seller
|
||||
```
|
||||
|
||||
You should see the logs of the seller wallet being created as well. Run the following commands to register and enroll two bidders from Org1 and another three bidders from Org2:
|
||||
```
|
||||
node registerEnrollUser.js org1 bidder1
|
||||
node registerEnrollUser.js org1 bidder2
|
||||
node registerEnrollUser.js org2 bidder3
|
||||
node registerEnrollUser.js org2 bidder4
|
||||
node registerEnrollUser.js org2 bidder5
|
||||
```
|
||||
|
||||
## Create the auction
|
||||
|
||||
The seller from Org1 would like to create an auction to sell 100 tickets. Run the following command to use the seller wallet to run the `createAuction.js` application. The seller needs to provide an auction ID, the item to be sold, and the quantity to be sold to create the auction. The seller uses `withAuditor` to indicate that Org3 will be added as the auditor organization. If you do not want to add an auditor, you can provide a value of `noAuditor`. You will see the application query the auction after it is created.
|
||||
```
|
||||
node createAuction.js org1 seller auction1 tickets 100 withAuditor
|
||||
```
|
||||
|
||||
Adding an auditor to the auction creates an endorsement policy with the auditor included. Without the auditor, each organization with sellers or bidders participating in the auction is added to the auction endorsement policy. For example, if the auction had two organizations participating in the auction, the auction endorsement policy would be `AND(Org1, Org2)`. However, if the selling organization decides to add an auditor, the auditor organization would be added to the endorsement policy. If the participating organizations disagree, or if a participant has a technical problem, the auditor can join any one of the participating organizations and agree to update the auction. Extending the example above, if the auction with two organizations added an auditor, the auction endorsement policy would be `OR(AND(Org1, Org2), AND(auditor, OR(Org1, Org2)))`.
|
||||
|
||||
## Bid on the auction
|
||||
|
||||
We can now use the bidder wallets to submit bids to the auction:
|
||||
|
||||
### Bid as bidder1
|
||||
|
||||
Bidder1 will create a bid to purchase 50 tickets for 80 dollars.
|
||||
```
|
||||
node bid.js org1 bidder1 auction1 50 80
|
||||
```
|
||||
|
||||
The application will query the bid after it is created:
|
||||
```
|
||||
*** Result: Bid: {
|
||||
"objectType": "bid",
|
||||
"quantity": 50,
|
||||
"price": 80,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
|
||||
}
|
||||
```
|
||||
|
||||
The `bid.js` application also prints the bidID:
|
||||
```
|
||||
*** Result ***SAVE THIS VALUE*** BidID: 6630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1
|
||||
```
|
||||
|
||||
The BidID acts as the unique identifier for the bid. This ID allows you to query the bid using the `queryBid.js` program and add the bid to the auction. Save the bidID returned by the application as an environment variable in your terminal:
|
||||
```
|
||||
export BIDDER1_BID_ID=6630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1
|
||||
```
|
||||
This value will be different for each transaction, so you will need to use the value returned in your terminal.
|
||||
|
||||
Now that the bid has been created, you can submit the bid to the auction. Run the following command to submit the bid that was just created:
|
||||
```
|
||||
node submitBid.js org1 bidder1 auction1 $BIDDER1_BID_ID
|
||||
```
|
||||
|
||||
The hash of bid is added to the list of private bids in that have been submitted to `auction1`. Storing the hash on the public auction ledger allows users to prove the accuracy of the bids they reveal once bidding is closed. The application queries the auction to verify that the bid was added:
|
||||
```
|
||||
*** Result: Auction: {
|
||||
"objectType": "auction",
|
||||
"item": "tickets",
|
||||
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
|
||||
}
|
||||
},
|
||||
"revealedBids": {},
|
||||
"winners": [],
|
||||
"price": 0,
|
||||
"status": "open",
|
||||
"auditor": true
|
||||
}
|
||||
```
|
||||
|
||||
### Bid as bidder2
|
||||
|
||||
Let's submit another bid. Bidder2 would like to purchase 40 tickets for 50 dollars.
|
||||
```
|
||||
node bid.js org1 bidder2 auction1 40 50
|
||||
```
|
||||
|
||||
Save the Bid ID returned by the application:
|
||||
```
|
||||
export BIDDER2_BID_ID=5796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8
|
||||
```
|
||||
|
||||
Submit bidder2's bid to the auction:
|
||||
```
|
||||
node submitBid.js org1 bidder2 auction1 $BIDDER2_BID_ID
|
||||
```
|
||||
|
||||
### Bid as bidder3 from Org2
|
||||
|
||||
Bidder3 will bid for 30 tickets at 70 dollars:
|
||||
```
|
||||
node bid.js org2 bidder3 auction1 30 70
|
||||
```
|
||||
|
||||
Save the Bid ID returned by the application:
|
||||
```
|
||||
export BIDDER3_BID_ID=d52ea4d9b4bc428d395db2d68323bc12cc9b5c1f8617900f459ccd41c38d3c0a
|
||||
```
|
||||
|
||||
Add bidder3's bid to the auction:
|
||||
```
|
||||
node submitBid.js org2 bidder3 auction1 $BIDDER3_BID_ID
|
||||
```
|
||||
|
||||
Because bidder3 belongs to Org2, submitting the bid will add Org2 to the list of participating organizations. You can see the Org2 MSP ID has been added to the list of `"organizations"` in the updated auction returned by the application:
|
||||
```
|
||||
*** Result: Auction: {
|
||||
"objectType": "auction",
|
||||
"item": "tickets",
|
||||
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u00005796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "598749480aa3af816a829455e1fdac25a44f31c2ae81f911f85d004f44dbbe6c"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000d52ea4d9b4bc428d395db2d68323bc12cc9b5c1f8617900f459ccd41c38d3c0a\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "bf1e9fb80ea3e29780fe13b4781b6dad28fa83b4b5db68bd7e90252875d152fb"
|
||||
}
|
||||
},
|
||||
"revealedBids": {},
|
||||
"winners": [],
|
||||
"price": 0,
|
||||
"status": "open",
|
||||
"auditor": true
|
||||
}
|
||||
```
|
||||
|
||||
Now that a bid from Org2 has been added to the auction, any updates to the auction need to be endorsed by the Org2 peer. The applications will use the `"organizations"` field to specify which organizations need to endorse submitting a new bid, revealing a bid, or updating the auction status.
|
||||
|
||||
### Bid as bidder4
|
||||
|
||||
Bidder4 from Org2 would like to purchase 15 tickets for 60 dollars:
|
||||
```
|
||||
node bid.js org2 bidder4 auction1 15 60
|
||||
```
|
||||
|
||||
Save the Bid ID returned by the application:
|
||||
```
|
||||
export BIDDER4_BID_ID=c6464f984bb01e639a46e58b94c496e8bbd829b5e4fa7ffcc150d9a565d45684
|
||||
```
|
||||
|
||||
Add bidder4's bid to the auction:
|
||||
```
|
||||
node submitBid.js org2 bidder4 auction1 $BIDDER4_BID_ID
|
||||
```
|
||||
|
||||
### Bid as bidder5
|
||||
|
||||
Bidder5 from Org2 will bid for 20 tickets at 60 dollars:
|
||||
```
|
||||
node bid.js org2 bidder5 auction1 20 60
|
||||
```
|
||||
|
||||
Save the Bid ID returned by the application:
|
||||
```
|
||||
export BIDDER5_BID_ID=f4024ab09b4dacf0a636927414850dde2a2a5e8ec4601e2a0071f5c233248207
|
||||
```
|
||||
|
||||
Add bidder5's bid to the auction:
|
||||
```
|
||||
node submitBid.js org2 bidder5 auction1 $BIDDER5_BID_ID
|
||||
```
|
||||
|
||||
|
||||
## Close the auction
|
||||
|
||||
Now that all five bidders have joined the auction, the seller would like to close the auction and allow buyers to reveal their bids. The seller identity that created the auction needs to submit the transaction:
|
||||
```
|
||||
node closeAuction.js org1 seller auction1
|
||||
```
|
||||
|
||||
The application will query the auction to allow you to verify that the auction status has changed to closed.
|
||||
|
||||
## Reveal bids
|
||||
|
||||
After the auction is closed, bidders can try to win the auction by revealing their bids. The transaction to reveal a bid needs to pass four checks:
|
||||
1. The auction is closed.
|
||||
2. The transaction was submitted by the identity that created the bid.
|
||||
3. The hash of the revealed bid matches the hash of the bid on the channel ledger. This confirms that the bid is the same as the bid that is stored in the private data collection.
|
||||
4. The hash of the revealed bid matches the hash that was submitted to the auction. This confirms that the bid was not altered after the auction was closed.
|
||||
|
||||
Use the `revealBid.js` application to reveal the bid of Bidder1:
|
||||
```
|
||||
node revealBid.js org1 bidder1 auction1 $BIDDER1_BID_ID
|
||||
```
|
||||
|
||||
The full bid details, including the quantity and price, are now visible:
|
||||
```
|
||||
*** Result: Auction: {
|
||||
"objectType": "auction",
|
||||
"item": "tickets",
|
||||
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u00005796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "598749480aa3af816a829455e1fdac25a44f31c2ae81f911f85d004f44dbbe6c"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000c6464f984bb01e639a46e58b94c496e8bbd829b5e4fa7ffcc150d9a565d45684\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "eefcadf8e9e5cb8322a6e642ab6d5512d62e6d68f37a72b00f5b0d9e580eddb9"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000d52ea4d9b4bc428d395db2d68323bc12cc9b5c1f8617900f459ccd41c38d3c0a\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "bf1e9fb80ea3e29780fe13b4781b6dad28fa83b4b5db68bd7e90252875d152fb"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000f4024ab09b4dacf0a636927414850dde2a2a5e8ec4601e2a0071f5c233248207\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "de82232141bac06ea3818146fb650dc9930d45b9ceab506ac66942b119eec094"
|
||||
}
|
||||
},
|
||||
"revealedBids": {
|
||||
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 50,
|
||||
"price": 80,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
|
||||
}
|
||||
},
|
||||
"winners": [],
|
||||
"price": 0,
|
||||
"status": "closed",
|
||||
"auditor": true
|
||||
}
|
||||
```
|
||||
We will add three more bidders, the second bidder from Org1 and two bidders from Org2. Run the following commands to reveal the bidders:
|
||||
```
|
||||
node revealBid.js org1 bidder2 auction1 $BIDDER2_BID_ID
|
||||
node revealBid.js org2 bidder4 auction1 $BIDDER4_BID_ID
|
||||
node revealBid.js org2 bidder5 auction1 $BIDDER5_BID_ID
|
||||
```
|
||||
|
||||
Let's try to end the auction using the seller identity and see what happens.
|
||||
|
||||
```
|
||||
node endAuction.js org1 seller auction1
|
||||
```
|
||||
|
||||
The output should look something like the following:
|
||||
|
||||
```
|
||||
--> Submit the transaction to end the auction
|
||||
2021-01-28T16:47:27.501Z - error: [DiscoveryHandler]: compareProposalResponseResults[undefined] - read/writes result sets do not match index=1
|
||||
2021-01-28T16:47:27.503Z - error: [Transaction]: Error: No valid responses from any peers. Errors:
|
||||
peer=undefined, status=grpc, message=Peer endorsements do not match
|
||||
******** FAILED to submit bid: Error: No valid responses from any peers. Errors:
|
||||
peer=undefined, status=grpc, message=Peer endorsements do not match
|
||||
```
|
||||
|
||||
Instead of ending the auction, the transaction results in an endorsement policy failure. The end of the auction needs to be endorsed by Org2. Before endorsing the transaction, the Org2 peer queries its private data collection for any winning bids that have not yet been revealed. Because the price that would clear the auction with the currently revealed bids is lower than the bid of Bidder3, the Org2 peer refuses to endorse the transaction that would end the auction.
|
||||
|
||||
In order to end the auction, Org1 would either need to wait for Org2 to reveal the final bid or appeal to the auditor. Depending on if you created the organization with an auditor, you can end the auction with either set of steps.
|
||||
|
||||
## End the auction using an auditor
|
||||
|
||||
If Org2 is unable to endorse the transaction to end the auction, Org1 can ask the auditor to intervene. The following program gets an endorsement from the Org3 auditor and Org1 to end the auction. As a result, the transaction would meet the auditor component of the state based endorsement policy.
|
||||
```
|
||||
node endAuctionwithAuditor org1 seller auction1
|
||||
```
|
||||
|
||||
Even though Org2 has not agreed to the end of the auction, the endorsement Org1 is sufficient to end the auction if the auditor agrees. As part of ending the auction, both Org1 and the auditor need to calculate the same price and the same set of winners. Each winning bidder is listed next to the quantity that was allocated to them.
|
||||
```
|
||||
*** Result: Auction: {
|
||||
"objectType": "auction",
|
||||
"item": "tickets",
|
||||
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u00005796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "598749480aa3af816a829455e1fdac25a44f31c2ae81f911f85d004f44dbbe6c"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000c6464f984bb01e639a46e58b94c496e8bbd829b5e4fa7ffcc150d9a565d45684\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "eefcadf8e9e5cb8322a6e642ab6d5512d62e6d68f37a72b00f5b0d9e580eddb9"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000d52ea4d9b4bc428d395db2d68323bc12cc9b5c1f8617900f459ccd41c38d3c0a\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "bf1e9fb80ea3e29780fe13b4781b6dad28fa83b4b5db68bd7e90252875d152fb"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000f4024ab09b4dacf0a636927414850dde2a2a5e8ec4601e2a0071f5c233248207\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "de82232141bac06ea3818146fb650dc9930d45b9ceab506ac66942b119eec094"
|
||||
}
|
||||
},
|
||||
"revealedBids": {
|
||||
"\u0000bid\u0000auction1\u00005796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 40,
|
||||
"price": 50,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "x509::CN=bidder2,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 50,
|
||||
"price": 80,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000c6464f984bb01e639a46e58b94c496e8bbd829b5e4fa7ffcc150d9a565d45684\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 15,
|
||||
"price": 60,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000f4024ab09b4dacf0a636927414850dde2a2a5e8ec4601e2a0071f5c233248207\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 20,
|
||||
"price": 60,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "x509::CN=bidder5,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
|
||||
}
|
||||
},
|
||||
"winners": [
|
||||
{
|
||||
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
|
||||
"quantity": 50
|
||||
},
|
||||
{
|
||||
"buyer": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
|
||||
"quantity": 15
|
||||
},
|
||||
{
|
||||
"buyer": "x509::CN=bidder5,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
|
||||
"quantity": 20
|
||||
},
|
||||
{
|
||||
"buyer": "x509::CN=bidder2,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
|
||||
"quantity": 15
|
||||
}
|
||||
],
|
||||
"price": 50,
|
||||
"status": "ended",
|
||||
"auditor": true
|
||||
}
|
||||
```
|
||||
|
||||
The auction allocates tickets to the highest bids first. Because all 100 tickets are sold after allocating tickets to the bid that was submitted at 50, 50 is the `"price"` that clears the auction.
|
||||
|
||||
## End the auction without an auditor
|
||||
|
||||
If we did not add an auditor to the auction, we need to add the remaining bid so that Org2 will endorse ending the auction.
|
||||
```
|
||||
node revealBid.js org2 bidder3 auction1 $BIDDER3_BID_ID
|
||||
```
|
||||
|
||||
Now that all the winning bids have been revealed, we can submit the transaction to end the auction once more.
|
||||
```
|
||||
node endAuction org1 seller auction1
|
||||
```
|
||||
|
||||
The transaction was successfully endorsed by both Org1 and Org2, who both calculated the same price and winners of the auction. Each winning bidder is listed next to the quantity that was allocated to them.
|
||||
```
|
||||
*** Result: Auction: {
|
||||
"objectType": "auction",
|
||||
"item": "tickets",
|
||||
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u0000482b2a68fbbfae329b0b4bc9d70b90f3a55fdcbae5f5274dec34d438efb6847e\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000048d93017ac65cff0dd23406cc29918724fd84c8e7014eee30fd492fef760e6a4\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "bf1e9fb80ea3e29780fe13b4781b6dad28fa83b4b5db68bd7e90252875d152fb"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u00005ba4c856224cdc8209b0e42f30a757331e3fb8a8b660b64a55e1bcf688b745ad\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "598749480aa3af816a829455e1fdac25a44f31c2ae81f911f85d004f44dbbe6c"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000063c8a192dae1332ae42af890f8a966fea2ae8365ca9746447e014a7c0494d64e\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "de82232141bac06ea3818146fb650dc9930d45b9ceab506ac66942b119eec094"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000066ff6d8bbe81e98654fc417915808031d49e93cd8d7475f15317d801317254fa\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "eefcadf8e9e5cb8322a6e642ab6d5512d62e6d68f37a72b00f5b0d9e580eddb9"
|
||||
}
|
||||
},
|
||||
"revealedBids": {
|
||||
"\u0000bid\u0000auction1\u0000482b2a68fbbfae329b0b4bc9d70b90f3a55fdcbae5f5274dec34d438efb6847e\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 50,
|
||||
"price": 80,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000048d93017ac65cff0dd23406cc29918724fd84c8e7014eee30fd492fef760e6a4\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 30,
|
||||
"price": 70,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "x509::CN=bidder3,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u00005ba4c856224cdc8209b0e42f30a757331e3fb8a8b660b64a55e1bcf688b745ad\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 40,
|
||||
"price": 50,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "x509::CN=bidder2,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000063c8a192dae1332ae42af890f8a966fea2ae8365ca9746447e014a7c0494d64e\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 20,
|
||||
"price": 60,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "x509::CN=bidder5,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000066ff6d8bbe81e98654fc417915808031d49e93cd8d7475f15317d801317254fa\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 15,
|
||||
"price": 60,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
|
||||
}
|
||||
},
|
||||
"winners": [
|
||||
{
|
||||
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
|
||||
"quantity": 50
|
||||
},
|
||||
{
|
||||
"buyer": "x509::CN=bidder3,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
|
||||
"quantity": 30
|
||||
},
|
||||
{
|
||||
"buyer": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
|
||||
"quantity": 15
|
||||
},
|
||||
{
|
||||
"buyer": "x509::CN=bidder5,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
|
||||
"quantity": 5
|
||||
}
|
||||
],
|
||||
"price": 60,
|
||||
"status": "ended",
|
||||
"auditor": false
|
||||
}
|
||||
```
|
||||
|
||||
The auction allocates tickets to the highest bids first. Because all 100 tickets are sold after allocating tickets to the bids that were submitted at 60, 60 is the `"price"` that clears the auction. The first 80 tickets are allocated to Bidder1 and Bidder3. The remaining 20 tickers are allocated to Bidder4 and Bidder5. When bids are tied, the auction smart contract fills the smaller bids first. As a result, Bidder4 is awarded their full bid of 15 tickets, while Bidder5 is allocated the remaining 5 tickets.
|
||||
|
||||
## Clean up
|
||||
|
||||
When your are done using the auction smart contract, you can bring down the network and clean up the environment. In the `auction-dutch/application-javascript` directory, run the following command to remove the wallets used to run the applications:
|
||||
```
|
||||
rm -rf wallet
|
||||
```
|
||||
|
||||
You can then navigate to the test network directory and bring down the network:
|
||||
````
|
||||
cd ../../test-network/
|
||||
./network.sh down
|
||||
````
|
||||
98
auction-dutch/application-javascript/bid.js
Normal file
98
auction-dutch/application-javascript/bid.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function bid (ccp, wallet, user, orgMSP, auctionID, quantity, price) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
console.log('\n--> Evaluate Transaction: get your client ID');
|
||||
const buyer = await contract.evaluateTransaction('GetSubmittingClientIdentity');
|
||||
console.log('*** Result: Buyer ID is ' + buyer.toString());
|
||||
|
||||
const bidData = { objectType: 'bid', quantity: parseInt(quantity), price: parseInt(price), org: orgMSP, buyer: buyer.toString() };
|
||||
|
||||
const statefulTxn = contract.createTransaction('Bid');
|
||||
statefulTxn.setEndorsingOrganizations(orgMSP);
|
||||
const tmapData = Buffer.from(JSON.stringify(bidData));
|
||||
statefulTxn.setTransient({
|
||||
bid: tmapData
|
||||
});
|
||||
|
||||
const bidID = statefulTxn.getTransactionId();
|
||||
|
||||
console.log('\n--> Submit Transaction: Create the bid that is stored in your private data collection of your organization');
|
||||
await statefulTxn.submit(auctionID);
|
||||
console.log('*** Result: committed');
|
||||
console.log('*** Result ***SAVE THIS VALUE*** BidID: ' + bidID.toString());
|
||||
|
||||
console.log('\n--> Evaluate Transaction: read the bid that was just created');
|
||||
const result = await contract.evaluateTransaction('QueryBid', auctionID, bidID);
|
||||
console.log('*** Result: Bid: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined ||
|
||||
process.argv[4] === undefined || process.argv[5] === undefined ||
|
||||
process.argv[6] === undefined) {
|
||||
console.log('Usage: node bid.js org userID auctionID quantity price');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
const quantity = process.argv[5];
|
||||
const price = process.argv[6];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const orgMSP = 'Org1MSP';
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await bid(ccp, wallet, user, orgMSP, auctionID, quantity, price);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const orgMSP = 'Org2MSP';
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await bid(ccp, wallet, user, orgMSP, auctionID, quantity, price);
|
||||
} else {
|
||||
console.log('Usage: node bid.js org userID auctionID quantity price');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
90
auction-dutch/application-javascript/closeAuction.js
Normal file
90
auction-dutch/application-javascript/closeAuction.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function closeAuction (ccp, wallet, user, auctionID) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
// Query the auction to get the list of endorsing orgs.
|
||||
// console.log('\n--> Evaluate Transaction: query the auction you want to close');
|
||||
const auctionString = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
// console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
|
||||
const auctionJSON = JSON.parse(auctionString);
|
||||
|
||||
const statefulTxn = contract.createTransaction('CloseAuction');
|
||||
|
||||
if (auctionJSON.organizations.length === 2) {
|
||||
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0], auctionJSON.organizations[1]);
|
||||
} else {
|
||||
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
|
||||
}
|
||||
|
||||
console.log('\n--> Submit Transaction: close auction');
|
||||
await statefulTxn.submit(auctionID);
|
||||
console.log('*** Result: committed');
|
||||
|
||||
console.log('\n--> Evaluate Transaction: query the updated auction');
|
||||
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined || process.argv[4] === undefined) {
|
||||
console.log('Usage: node closeAuction.js org userID auctionID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await closeAuction(ccp, wallet, user, auctionID);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await closeAuction(ccp, wallet, user, auctionID);
|
||||
} else {
|
||||
console.log('Usage: node closeAuction.js org userID auctionID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
78
auction-dutch/application-javascript/createAuction.js
Normal file
78
auction-dutch/application-javascript/createAuction.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function createAuction (ccp, wallet, user, auctionID, item, quantity, auditor) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
const statefulTxn = contract.createTransaction('CreateAuction');
|
||||
|
||||
console.log('\n--> Submit Transaction: Propose a new auction');
|
||||
await statefulTxn.submit(auctionID, item, parseInt(quantity), auditor);
|
||||
console.log('*** Result: committed');
|
||||
|
||||
console.log('\n--> Evaluate Transaction: query the auction that was just created');
|
||||
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined ||
|
||||
process.argv[4] === undefined || process.argv[5] === undefined ||
|
||||
process.argv[6] === undefined) {
|
||||
console.log('Usage: node createAuction.js org userID auctionID item quantity');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
const item = process.argv[5];
|
||||
const quantity = process.argv[6];
|
||||
const auditor = process.argv[7];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await createAuction(ccp, wallet, user, auctionID, item, quantity, auditor);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await createAuction(ccp, wallet, user, auctionID, item, quantity, auditor);
|
||||
} else {
|
||||
console.log('Usage: node createAuction.js org userID auctionID item quantity');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
91
auction-dutch/application-javascript/endAuction.js
Normal file
91
auction-dutch/application-javascript/endAuction.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function endAuction (ccp, wallet, user, auctionID) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
// Query the auction to get the list of endorsing orgs.
|
||||
// console.log('\n--> Evaluate Transaction: query the auction you want to end');
|
||||
const auctionString = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
// console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
|
||||
const auctionJSON = JSON.parse(auctionString);
|
||||
|
||||
const statefulTxn = contract.createTransaction('EndAuction');
|
||||
|
||||
if (auctionJSON.organizations.length === 2) {
|
||||
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0], auctionJSON.organizations[1]);
|
||||
} else {
|
||||
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
|
||||
}
|
||||
|
||||
console.log('\n--> Submit the transaction to end the auction');
|
||||
await statefulTxn.submit(auctionID);
|
||||
console.log('*** Result: committed');
|
||||
|
||||
console.log('\n--> Evaluate Transaction: query the updated auction');
|
||||
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined ||
|
||||
process.argv[4] === undefined) {
|
||||
console.log('Usage: node endAuction.js org userID auctionID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await endAuction(ccp, wallet, user, auctionID);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await endAuction(ccp, wallet, user, auctionID);
|
||||
} else {
|
||||
console.log('Usage: node endAuction.js org userID auctionID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function endAuction (ccp, wallet, org, user, auctionID) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
const statefulTxn = contract.createTransaction('EndAuction');
|
||||
|
||||
statefulTxn.setEndorsingOrganizations(org, 'Org3MSP');
|
||||
|
||||
console.log('\n--> Submit the transaction to end the auction');
|
||||
await statefulTxn.submit(auctionID);
|
||||
console.log('*** Result: committed');
|
||||
|
||||
console.log('\n--> Evaluate Transaction: query the updated auction');
|
||||
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined ||
|
||||
process.argv[4] === undefined) {
|
||||
console.log('Usage: node endAuction.js org userID auctionID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const orgMSP = 'Org1MSP';
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await endAuction(ccp, wallet, orgMSP, user, auctionID);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const orgMSP = 'Org2MSP';
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await endAuction(ccp, wallet, orgMSP, user, auctionID);
|
||||
} else {
|
||||
console.log('Usage: node endAuction.js org userID auctionID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
62
auction-dutch/application-javascript/enrollAdmin.js
Normal file
62
auction-dutch/application-javascript/enrollAdmin.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Wallets } = require('fabric-network');
|
||||
const FabricCAServices = require('fabric-ca-client');
|
||||
const path = require('path');
|
||||
const { buildCAClient, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const mspOrg1 = 'Org1MSP';
|
||||
const mspOrg2 = 'Org2MSP';
|
||||
|
||||
async function connectToOrg1CA () {
|
||||
console.log('\n--> Enrolling the Org1 CA admin');
|
||||
const ccpOrg1 = buildCCPOrg1();
|
||||
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
|
||||
|
||||
const walletPathOrg1 = path.join(__dirname, 'wallet/org1');
|
||||
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
|
||||
|
||||
await enrollAdmin(caOrg1Client, walletOrg1, mspOrg1);
|
||||
}
|
||||
|
||||
async function connectToOrg2CA () {
|
||||
console.log('\n--> Enrolling the Org2 CA admin');
|
||||
const ccpOrg2 = buildCCPOrg2();
|
||||
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
|
||||
|
||||
const walletPathOrg2 = path.join(__dirname, 'wallet/org2');
|
||||
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
|
||||
|
||||
await enrollAdmin(caOrg2Client, walletOrg2, mspOrg2);
|
||||
}
|
||||
async function main () {
|
||||
if (process.argv[2] === undefined) {
|
||||
console.log('Usage: node enrollAdmin.js Org');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
|
||||
try {
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
await connectToOrg1CA();
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
await connectToOrg2CA();
|
||||
} else {
|
||||
console.log('Usage: node registerUser.js org userID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in enrolling admin: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
23
auction-dutch/application-javascript/package.json
Normal file
23
auction-dutch/application-javascript/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "auction",
|
||||
"version": "1.0.0",
|
||||
"description": "auction application implemented in JavaScript",
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=5"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"author": "Hyperledger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fabric-ca-client": "^2.2.4",
|
||||
"fabric-network": "^2.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.3.1"
|
||||
}
|
||||
}
|
||||
68
auction-dutch/application-javascript/queryAuction.js
Normal file
68
auction-dutch/application-javascript/queryAuction.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function queryAuction (ccp, wallet, user, auctionID) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
console.log('\n--> Evaluate Transaction: query the auction');
|
||||
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined ||
|
||||
process.argv[4] === undefined) {
|
||||
console.log('Usage: node queryAuction.js org userID auctionID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await queryAuction(ccp, wallet, user, auctionID);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await queryAuction(ccp, wallet, user, auctionID);
|
||||
} else {
|
||||
console.log('Usage: node queryAuction.js org userID auctionID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
69
auction-dutch/application-javascript/queryBid.js
Normal file
69
auction-dutch/application-javascript/queryBid.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function queryBid (ccp, wallet, user, auctionID, bidID) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
console.log('\n--> Evaluate Transaction: read bid from private data store');
|
||||
const result = await contract.evaluateTransaction('QueryBid', auctionID, bidID);
|
||||
console.log('*** Result: Bid: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined ||
|
||||
process.argv[4] === undefined || process.argv[5] === undefined) {
|
||||
console.log('Usage: node bid.js org userID auctionID bidID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
const bidID = process.argv[5];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await queryBid(ccp, wallet, user, auctionID, bidID);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await queryBid(ccp, wallet, user, auctionID, bidID);
|
||||
} else {
|
||||
console.log('Usage: node bid.js org userID auctionID bidID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
63
auction-dutch/application-javascript/registerEnrollUser.js
Normal file
63
auction-dutch/application-javascript/registerEnrollUser.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Wallets } = require('fabric-network');
|
||||
const FabricCAServices = require('fabric-ca-client');
|
||||
const path = require('path');
|
||||
const { buildCAClient, registerAndEnrollUser } = require('../../test-application/javascript/CAUtil.js');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const mspOrg1 = 'Org1MSP';
|
||||
const mspOrg2 = 'Org2MSP';
|
||||
|
||||
async function connectToOrg1CA (UserID) {
|
||||
console.log('\n--> Register and enrolling new user');
|
||||
const ccpOrg1 = buildCCPOrg1();
|
||||
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
|
||||
|
||||
const walletPathOrg1 = path.join(__dirname, 'wallet/org1');
|
||||
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
|
||||
|
||||
await registerAndEnrollUser(caOrg1Client, walletOrg1, mspOrg1, UserID, 'org1.department1');
|
||||
}
|
||||
|
||||
async function connectToOrg2CA (UserID) {
|
||||
console.log('\n--> Register and enrolling new user');
|
||||
const ccpOrg2 = buildCCPOrg2();
|
||||
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
|
||||
|
||||
const walletPathOrg2 = path.join(__dirname, 'wallet/org2');
|
||||
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
|
||||
|
||||
await registerAndEnrollUser(caOrg2Client, walletOrg2, mspOrg2, UserID, 'org2.department1');
|
||||
}
|
||||
async function main () {
|
||||
if (process.argv[2] === undefined && process.argv[3] === undefined) {
|
||||
console.log('Usage: node registerEnrollUser.js org userID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const userId = process.argv[3];
|
||||
|
||||
try {
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
await connectToOrg1CA(userId);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
await connectToOrg2CA(userId);
|
||||
} else {
|
||||
console.log('Usage: node registerEnrollUser.js org userID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in enrolling admin: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
100
auction-dutch/application-javascript/revealBid.js
Normal file
100
auction-dutch/application-javascript/revealBid.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function addBid (ccp, wallet, user, auctionID, bidID) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
console.log('\n--> Evaluate Transaction: read your bid');
|
||||
const bidString = await contract.evaluateTransaction('QueryBid', auctionID, bidID);
|
||||
const bidJSON = JSON.parse(bidString);
|
||||
|
||||
// console.log('\n--> Evaluate Transaction: query the auction you want to join');
|
||||
const auctionString = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
// console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
|
||||
const auctionJSON = JSON.parse(auctionString);
|
||||
|
||||
const bidData = { objectType: 'bid', quantity: parseInt(bidJSON.quantity), price: parseInt(bidJSON.price), org: bidJSON.org, buyer: bidJSON.buyer };
|
||||
console.log('*** Result: Bid: ' + JSON.stringify(bidData, null, 2));
|
||||
|
||||
const statefulTxn = contract.createTransaction('RevealBid');
|
||||
const tmapData = Buffer.from(JSON.stringify(bidData));
|
||||
statefulTxn.setTransient({
|
||||
bid: tmapData
|
||||
});
|
||||
|
||||
if (auctionJSON.organizations.length === 2) {
|
||||
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0], auctionJSON.organizations[1]);
|
||||
} else {
|
||||
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
|
||||
}
|
||||
|
||||
await statefulTxn.submit(auctionID, bidID);
|
||||
|
||||
console.log('\n--> Evaluate Transaction: query the auction to see that our bid was added');
|
||||
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined ||
|
||||
process.argv[4] === undefined || process.argv[5] === undefined) {
|
||||
console.log('Usage: node revealBid.js org userID auctionID bidID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
const bidID = process.argv[5];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await addBid(ccp, wallet, user, auctionID, bidID);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await addBid(ccp, wallet, user, auctionID, bidID);
|
||||
} else {
|
||||
console.log('Usage: node revealBid.js org userID auctionID bidID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
89
auction-dutch/application-javascript/submitBid.js
Normal file
89
auction-dutch/application-javascript/submitBid.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Gateway, Wallets } = require('fabric-network');
|
||||
const path = require('path');
|
||||
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
|
||||
|
||||
const myChannel = 'mychannel';
|
||||
const myChaincodeName = 'auction';
|
||||
|
||||
async function submitBid (ccp, wallet, user, auctionID, bidID) {
|
||||
try {
|
||||
const gateway = new Gateway();
|
||||
// connect using Discovery enabled
|
||||
|
||||
await gateway.connect(ccp,
|
||||
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
|
||||
|
||||
const network = await gateway.getNetwork(myChannel);
|
||||
const contract = network.getContract(myChaincodeName);
|
||||
|
||||
console.log('\n--> Evaluate Transaction: query the auction you want to join');
|
||||
const auctionString = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
const auctionJSON = JSON.parse(auctionString);
|
||||
|
||||
const statefulTxn = contract.createTransaction('SubmitBid');
|
||||
|
||||
if (auctionJSON.organizations.length === 2) {
|
||||
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0], auctionJSON.organizations[1]);
|
||||
} else {
|
||||
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
|
||||
}
|
||||
|
||||
console.log('\n--> Submit Transaction: add bid to the auction');
|
||||
await statefulTxn.submit(auctionID, bidID);
|
||||
|
||||
console.log('\n--> Evaluate Transaction: query the auction to see that our bid was added');
|
||||
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
|
||||
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
|
||||
|
||||
gateway.disconnect();
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to submit bid: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
if (process.argv[2] === undefined || process.argv[3] === undefined ||
|
||||
process.argv[4] === undefined || process.argv[5] === undefined) {
|
||||
console.log('Usage: node submitBid.js org userID auctionID bidID');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const org = process.argv[2];
|
||||
const user = process.argv[3];
|
||||
const auctionID = process.argv[4];
|
||||
const bidID = process.argv[5];
|
||||
|
||||
if (org === 'Org1' || org === 'org1') {
|
||||
const ccp = buildCCPOrg1();
|
||||
const walletPath = path.join(__dirname, 'wallet/org1');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await submitBid(ccp, wallet, user, auctionID, bidID);
|
||||
} else if (org === 'Org2' || org === 'org2') {
|
||||
const ccp = buildCCPOrg2();
|
||||
const walletPath = path.join(__dirname, 'wallet/org2');
|
||||
const wallet = await buildWallet(Wallets, walletPath);
|
||||
await submitBid(ccp, wallet, user, auctionID, bidID);
|
||||
} else {
|
||||
console.log('Usage: node submitBid.js org userID auctionID bidID');
|
||||
console.log('Org must be Org1 or Org2');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`******** FAILED to run the application: ${error}`);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
10
auction-dutch/chaincode-go-auditor/go.mod
Normal file
10
auction-dutch/chaincode-go-auditor/go.mod
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
module github.com/hyperledger/fabric-samples/auction/dutch-auction/chaincode-go-auditor
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20201119163726-f8ef75b17719
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.1
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20210127161553-4f432a78f286
|
||||
)
|
||||
166
auction-dutch/chaincode-go-auditor/go.sum
Normal file
166
auction-dutch/chaincode-go-auditor/go.sum
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20201119163726-f8ef75b17719 h1:FQ9AMLVSFt5QW2YBLraXW5V4Au6aFFpSl4xKFARM58Y=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20201119163726-f8ef75b17719/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.1 h1:gDhOC18gjgElNZ85kFWsbCQq95hyUP/21n++m0Sv6B0=
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.1/go.mod h1:+39cWxbh5py3NtXpRA63rAH7NzXyED+QJx1EZr0tJPo=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20210127161553-4f432a78f286 h1:cFLrvWUprlCbVixFkaeONNlUtbsjv3c20ujb4RJFBl8=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20210127161553-4f432a78f286/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ=
|
||||
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
446
auction-dutch/chaincode-go-auditor/smart-contract/auction.go
Normal file
446
auction-dutch/chaincode-go-auditor/smart-contract/auction.go
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
)
|
||||
|
||||
type SmartContract struct {
|
||||
contractapi.Contract
|
||||
}
|
||||
|
||||
// Auction data
|
||||
type Auction struct {
|
||||
Type string `json:"objectType"`
|
||||
ItemSold string `json:"item"`
|
||||
Seller string `json:"seller"`
|
||||
Quantity int `json:"quantity"`
|
||||
Orgs []string `json:"organizations"`
|
||||
PrivateBids map[string]BidHash `json:"privateBids"`
|
||||
RevealedBids map[string]FullBid `json:"revealedBids"`
|
||||
Winners []Winners `json:"winners"`
|
||||
Price int `json:"price"`
|
||||
Status string `json:"status"`
|
||||
Auditor bool `json:"auditor"`
|
||||
}
|
||||
|
||||
// FullBid is the structure of a revealed bid
|
||||
type FullBid struct {
|
||||
Type string `json:"objectType"`
|
||||
Quantity int `json:"quantity"`
|
||||
Price int `json:"price"`
|
||||
Org string `json:"org"`
|
||||
Buyer string `json:"buyer"`
|
||||
}
|
||||
|
||||
// BidHash is the structure of a private bid
|
||||
type BidHash struct {
|
||||
Org string `json:"org"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
// Winners stores the winners of the auction
|
||||
type Winners struct {
|
||||
Buyer string `json:"buyer"`
|
||||
Quantity int `json:"quantity"`
|
||||
}
|
||||
|
||||
const bidKeyType = "bid"
|
||||
|
||||
// SubmitBid is used by the bidder to add the hash of that bid stored in private data to the
|
||||
// auction. Note that this function alters the auction in private state, and needs
|
||||
// to meet the auction endorsement policy. Transaction ID is used identify the bid
|
||||
func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
|
||||
|
||||
// get the MSP ID of the bidder's org
|
||||
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
// get the auction from public state
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
||||
// the auction needs to be open for users to add their bid
|
||||
status := auction.Status
|
||||
if status != "open" {
|
||||
return fmt.Errorf("cannot join closed or ended auction")
|
||||
}
|
||||
|
||||
// get the inplicit collection name of bidder's org
|
||||
collection, err := getCollectionName(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get implicit collection name: %v", err)
|
||||
}
|
||||
|
||||
// use the transaction ID passed as a parameter to create composite bid key
|
||||
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
// get the hash of the bid if found in private collection
|
||||
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read bid bash from collection: %v", err)
|
||||
}
|
||||
if bidHash == nil {
|
||||
return fmt.Errorf("bid hash does not exist: %s", bidKey)
|
||||
}
|
||||
|
||||
// store the hash along with the bidder's organization
|
||||
newHash := BidHash{
|
||||
Org: clientOrgID,
|
||||
Hash: fmt.Sprintf("%x", bidHash),
|
||||
}
|
||||
|
||||
bidders := make(map[string]BidHash)
|
||||
bidders = auction.PrivateBids
|
||||
bidders[bidKey] = newHash
|
||||
auction.PrivateBids = bidders
|
||||
|
||||
// Add the bidding organization to the list of participating organization's if it is not already
|
||||
orgs := auction.Orgs
|
||||
if !(contains(orgs, clientOrgID)) {
|
||||
newOrgs := append(orgs, clientOrgID)
|
||||
auction.Orgs = newOrgs
|
||||
|
||||
err = setAssetStateBasedEndorsement(ctx, auctionID, newOrgs, auction.Auditor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
newAuctionJSON, _ := json.Marshal(auction)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, newAuctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update auction: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RevealBid is used by a bidder to reveal their bid after the auction is closed
|
||||
func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
|
||||
|
||||
// get the MSP ID of the bidder's org
|
||||
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
// get bid from transient map
|
||||
transientMap, err := ctx.GetStub().GetTransient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting transient: %v", err)
|
||||
}
|
||||
|
||||
transientBidJSON, ok := transientMap["bid"]
|
||||
if !ok {
|
||||
return fmt.Errorf("bid key not found in the transient map")
|
||||
}
|
||||
|
||||
// get implicit collection name of organization ID
|
||||
collection, err := getCollectionName(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get implicit collection name: %v", err)
|
||||
}
|
||||
|
||||
// use transaction ID to create composit bid key
|
||||
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
// get bid hash of bid if private bid on the public ledger
|
||||
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read bid bash from collection: %v", err)
|
||||
}
|
||||
if bidHash == nil {
|
||||
return fmt.Errorf("bid hash does not exist: %s", bidKey)
|
||||
}
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
||||
// check that the bidders org is a participant in the auction
|
||||
orgs := auction.Orgs
|
||||
if !(contains(orgs, clientOrgID)) {
|
||||
return fmt.Errorf("Particiant is not a member of the auction", err)
|
||||
}
|
||||
|
||||
// Complete a series of three checks before we add the bid to the auction
|
||||
|
||||
// check 1: check that the auction is closed. We cannot reveal an
|
||||
// bid to an open auction
|
||||
status := auction.Status
|
||||
if status != "closed" {
|
||||
return fmt.Errorf("cannot reveal bid for open or ended auction")
|
||||
}
|
||||
|
||||
// check 2: check that hash of revealed bid matches hash of private bid
|
||||
// on the public ledger. This checks that the bidder is telling the truth
|
||||
// about the value of their bid
|
||||
|
||||
hash := sha256.New()
|
||||
hash.Write(transientBidJSON)
|
||||
calculatedBidJSONHash := hash.Sum(nil)
|
||||
|
||||
// verify that the hash of the passed immutable properties matches the on-chain hash
|
||||
if !bytes.Equal(calculatedBidJSONHash, bidHash) {
|
||||
return fmt.Errorf("hash %x for bid JSON %s does not match hash in auction: %x",
|
||||
calculatedBidJSONHash,
|
||||
transientBidJSON,
|
||||
bidHash,
|
||||
)
|
||||
}
|
||||
|
||||
// check 3; check hash of relealed bid matches hash of private bid that was
|
||||
// added earlier. This ensures that the bid has not changed since it
|
||||
// was added to the auction
|
||||
|
||||
bidders := auction.PrivateBids
|
||||
privateBidHashString := bidders[bidKey].Hash
|
||||
|
||||
onChainBidHashString := fmt.Sprintf("%x", bidHash)
|
||||
if privateBidHashString != onChainBidHashString {
|
||||
return fmt.Errorf("hash %s for bid JSON %s does not match hash in auction: %s, bidder must have changed bid",
|
||||
privateBidHashString,
|
||||
transientBidJSON,
|
||||
onChainBidHashString,
|
||||
)
|
||||
}
|
||||
|
||||
// we can add the bid to the auction if all checks have passed
|
||||
type transientBidInput struct {
|
||||
Quantity int `json:"quantity"`
|
||||
Price int `json:"price"`
|
||||
Org string `json:"org"`
|
||||
Buyer string `json:"buyer"`
|
||||
}
|
||||
|
||||
// unmarshal bid imput
|
||||
var bidInput transientBidInput
|
||||
err = json.Unmarshal(transientBidJSON, &bidInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
// marshal transient parameters and ID and MSPID into bid object
|
||||
newBid := FullBid{
|
||||
Type: bidKeyType,
|
||||
Quantity: bidInput.Quantity,
|
||||
Price: bidInput.Price,
|
||||
Org: bidInput.Org,
|
||||
Buyer: bidInput.Buyer,
|
||||
}
|
||||
|
||||
// check 4: make sure that the transaction is being submitted is the bidder
|
||||
if bidInput.Buyer != clientID {
|
||||
return fmt.Errorf("Permission denied, client id %v is not the owner of the bid", clientID)
|
||||
}
|
||||
|
||||
revealedBids := make(map[string]FullBid)
|
||||
revealedBids = auction.RevealedBids
|
||||
revealedBids[bidKey] = newBid
|
||||
auction.RevealedBids = revealedBids
|
||||
|
||||
auctionJSON, _ := json.Marshal(auction)
|
||||
|
||||
// put auction with bid added back into state
|
||||
err = ctx.GetStub().PutState(auctionID, auctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update auction: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseAuction can be used by the seller to close the auction. This prevents
|
||||
// bids from being added to the auction, and allows users to reveal their bid
|
||||
func (s *SmartContract) CloseAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
|
||||
|
||||
// get the MSP ID of the bidder's org
|
||||
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
||||
// check that the bidders org is a participant in the auction
|
||||
orgs := auction.Orgs
|
||||
if !(contains(orgs, clientOrgID)) {
|
||||
return fmt.Errorf("Particiant is not a member of the auction", err)
|
||||
}
|
||||
|
||||
// the auction can only be closed by the seller
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
seller := auction.Seller
|
||||
if seller != clientID {
|
||||
return fmt.Errorf("auction can only be closed by seller: %v", err)
|
||||
}
|
||||
|
||||
status := auction.Status
|
||||
if status != "open" {
|
||||
return fmt.Errorf("cannot close auction that is not open")
|
||||
}
|
||||
|
||||
auction.Status = string("closed")
|
||||
|
||||
closedAuctionJSON, _ := json.Marshal(auction)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, closedAuctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to close auction: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EndAuction both changes the auction status to closed and calculates the winners
|
||||
// of the auction
|
||||
func (s *SmartContract) EndAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
|
||||
|
||||
// get the MSP ID of the bidder's org
|
||||
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
||||
// check that the bidders org is a participant in the auction
|
||||
orgs := auction.Orgs
|
||||
if !(contains(orgs, clientOrgID)) {
|
||||
return fmt.Errorf("Particiant is not a member of the auction", err)
|
||||
}
|
||||
|
||||
// Check that the auction is being ended by the seller
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
seller := auction.Seller
|
||||
if seller != clientID {
|
||||
return fmt.Errorf("auction can only be ended by seller: %v", err)
|
||||
}
|
||||
|
||||
status := auction.Status
|
||||
if status != "closed" {
|
||||
return fmt.Errorf("Can only end a closed auction")
|
||||
}
|
||||
|
||||
// get the list of revealed bids
|
||||
|
||||
revealedBidMap := auction.RevealedBids
|
||||
if len(auction.RevealedBids) == 0 {
|
||||
return fmt.Errorf("No bids have been revealed, cannot end auction: %v", err)
|
||||
}
|
||||
|
||||
// sort the map of revealed bids to make it easier to calculate winners
|
||||
// if bids are tied, fill smaller bids first
|
||||
var bidders []FullBid
|
||||
|
||||
for _, bid := range revealedBidMap {
|
||||
bidders = append(bidders, bid)
|
||||
}
|
||||
|
||||
sort.Slice(bidders, func(p, q int) bool {
|
||||
if bidders[p].Price > bidders[q].Price {
|
||||
return true
|
||||
}
|
||||
if bidders[p].Price < bidders[q].Price {
|
||||
return false
|
||||
}
|
||||
return bidders[p].Quantity < bidders[q].Quantity
|
||||
})
|
||||
|
||||
i := 0
|
||||
remainingQuantity := auction.Quantity
|
||||
|
||||
// calculate the winners
|
||||
for remainingQuantity > 0 {
|
||||
|
||||
// create the next winning bid
|
||||
winner := Winners{
|
||||
Buyer: bidders[i].Buyer,
|
||||
Quantity: bidders[i].Quantity,
|
||||
}
|
||||
|
||||
// add them to the list of winners and change the winning price
|
||||
auction.Winners = append(auction.Winners, winner)
|
||||
auction.Price = bidders[i].Price
|
||||
|
||||
// Calculate the quantity that goes to the winner
|
||||
// if there is sufficient quantity to give them the full bid
|
||||
if remainingQuantity > bidders[i].Quantity {
|
||||
remainingQuantity = remainingQuantity - bidders[i].Quantity
|
||||
|
||||
// if there is not, give the remainder
|
||||
} else {
|
||||
auction.Winners[i].Quantity = remainingQuantity
|
||||
remainingQuantity = 0
|
||||
}
|
||||
i++
|
||||
if i == len(bidders) {
|
||||
remainingQuantity = 0
|
||||
}
|
||||
}
|
||||
|
||||
// check if there is a winning bid that has yet to be revealed
|
||||
err = checkForHigherBid(ctx, auction.Price, auction.RevealedBids, auction.PrivateBids)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot end auction: %v", err)
|
||||
}
|
||||
|
||||
auction.Status = string("ended")
|
||||
|
||||
endedAuctionJSON, _ := json.Marshal(auction)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, endedAuctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to end auction: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
)
|
||||
|
||||
// QueryAuction allows all members of the channel to read a public auction
|
||||
func (s *SmartContract) QueryAuction(ctx contractapi.TransactionContextInterface, auctionID string) (*Auction, error) {
|
||||
|
||||
auctionJSON, err := ctx.GetStub().GetState(auctionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get auction object %v: %v", auctionID, err)
|
||||
}
|
||||
if auctionJSON == nil {
|
||||
return nil, fmt.Errorf("auction does not exist")
|
||||
}
|
||||
|
||||
var auction *Auction
|
||||
err = json.Unmarshal(auctionJSON, &auction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return auction, nil
|
||||
}
|
||||
|
||||
// checkForHigherBid is an internal function that is used to determine if a winning bid has yet to be revealed
|
||||
func checkForHigherBid(ctx contractapi.TransactionContextInterface, auctionPrice int, revealedBidders map[string]FullBid, bidders map[string]BidHash) error {
|
||||
|
||||
// Get MSP ID of peer org
|
||||
peerMSPID, err := shim.GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
|
||||
}
|
||||
|
||||
var error error
|
||||
error = nil
|
||||
|
||||
for bidKey, privateBid := range bidders {
|
||||
|
||||
if _, bidInAuction := revealedBidders[bidKey]; bidInAuction {
|
||||
|
||||
//bid is already revealed, no action to take
|
||||
|
||||
} else {
|
||||
|
||||
collection := "_implicit_org_" + privateBid.Org
|
||||
|
||||
if privateBid.Org == peerMSPID {
|
||||
|
||||
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get bid %v: %v", bidKey, err)
|
||||
}
|
||||
if bidJSON == nil {
|
||||
return fmt.Errorf("bid %v does not exist", bidKey)
|
||||
}
|
||||
|
||||
var bid *FullBid
|
||||
err = json.Unmarshal(bidJSON, &bid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bid.Price > auctionPrice {
|
||||
error = fmt.Errorf("Cannot close auction, bidder has a higher price: %v", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
hash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read bid hash from collection: %v", err)
|
||||
}
|
||||
if hash == nil {
|
||||
return fmt.Errorf("bid hash does not exist: %s", bidKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error
|
||||
}
|
||||
203
auction-dutch/chaincode-go-auditor/smart-contract/utils.go
Normal file
203
auction-dutch/chaincode-go-auditor/smart-contract/utils.go
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
"github.com/hyperledger/fabric-protos-go/common"
|
||||
"github.com/hyperledger/fabric-protos-go/msp"
|
||||
)
|
||||
|
||||
func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
|
||||
b64ID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read clientID: %v", err)
|
||||
}
|
||||
decodeID, err := base64.StdEncoding.DecodeString(b64ID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to base64 decode clientID: %v", err)
|
||||
}
|
||||
return string(decodeID), nil
|
||||
}
|
||||
|
||||
// getCollectionName is an internal helper function to get collection of submitting client identity.
|
||||
func getCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
|
||||
// Get the MSP ID of submitting client identity
|
||||
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get verified MSPID: %v", err)
|
||||
}
|
||||
|
||||
// Create the collection name
|
||||
orgCollection := "_implicit_org_" + clientMSPID
|
||||
|
||||
return orgCollection, nil
|
||||
}
|
||||
|
||||
// verifyClientOrgMatchesPeerOrg is an internal function used to verify that client org id matches peer org id.
|
||||
func verifyClientOrgMatchesPeerOrg(ctx contractapi.TransactionContextInterface) error {
|
||||
|
||||
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting the client's MSPID: %v", err)
|
||||
}
|
||||
peerMSPID, err := shim.GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
|
||||
}
|
||||
|
||||
if clientMSPID != peerMSPID {
|
||||
return fmt.Errorf("client from org %v is not authorized to read or write private data from an org %v peer", clientMSPID, peerMSPID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(sli []string, str string) bool {
|
||||
for _, a := range sli {
|
||||
if a == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetId string, mspids []string, auditor bool) error {
|
||||
|
||||
principals := make([]*msp.MSPPrincipal, len(mspids))
|
||||
participantSigsPolicy := make([]*common.SignaturePolicy, len(mspids))
|
||||
|
||||
for i, id := range mspids {
|
||||
principal, err := proto.Marshal(
|
||||
&msp.MSPRole{
|
||||
Role: msp.MSPRole_PEER,
|
||||
MspIdentifier: id,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
principals[i] = &msp.MSPPrincipal{
|
||||
PrincipalClassification: msp.MSPPrincipal_ROLE,
|
||||
Principal: principal,
|
||||
}
|
||||
participantSigsPolicy[i] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_SignedBy{
|
||||
SignedBy: int32(i),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if auditor == false {
|
||||
// create the defalt policy for an auction without an auditor
|
||||
|
||||
policy := &common.SignaturePolicyEnvelope{
|
||||
Version: 0,
|
||||
Rule: &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: int32(len(mspids)),
|
||||
Rules: participantSigsPolicy,
|
||||
},
|
||||
},
|
||||
},
|
||||
Identities: principals,
|
||||
}
|
||||
|
||||
spBytes, err := proto.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
|
||||
}
|
||||
} else {
|
||||
|
||||
// create the defalt policy for an auction with an auditor
|
||||
|
||||
// create the auditor identity and signature policy
|
||||
auditorMSP, err := proto.Marshal(
|
||||
&msp.MSPRole{
|
||||
Role: msp.MSPRole_PEER,
|
||||
MspIdentifier: "Org3MSP",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
principals = append(principals, &msp.MSPPrincipal{
|
||||
PrincipalClassification: msp.MSPPrincipal_ROLE,
|
||||
Principal: auditorMSP,
|
||||
},
|
||||
)
|
||||
// Create the policies in case the auditor is needed. In this case, an
|
||||
// auditor and 1 participant can update the auction.
|
||||
auditorPolicies := make([]*common.SignaturePolicy, 2)
|
||||
auditorPolicies[0] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_SignedBy{
|
||||
SignedBy: int32(len(principals) - 1),
|
||||
},
|
||||
}
|
||||
auditorPolicies[1] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: 1,
|
||||
Rules: participantSigsPolicy,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// The auditor policy below is equivilent to AND(auditor, OR(participants))
|
||||
policies := make([]*common.SignaturePolicy, 2)
|
||||
policies[0] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: 2,
|
||||
Rules: auditorPolicies,
|
||||
},
|
||||
},
|
||||
}
|
||||
// Participants can also update the auction without an auditor
|
||||
policies[1] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: int32(len(mspids)),
|
||||
Rules: participantSigsPolicy,
|
||||
},
|
||||
},
|
||||
}
|
||||
// Either the auditor policy or the participant policy can update
|
||||
// the auction
|
||||
policy := &common.SignaturePolicyEnvelope{
|
||||
Version: 0,
|
||||
Rule: &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: 1,
|
||||
Rules: policies,
|
||||
},
|
||||
},
|
||||
},
|
||||
Identities: principals,
|
||||
}
|
||||
spBytes, err := proto.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
23
auction-dutch/chaincode-go-auditor/smartContract.go
Normal file
23
auction-dutch/chaincode-go-auditor/smartContract.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
auction "github.com/hyperledger/fabric-samples/auction/dutch-auction/chaincode-go-auditor/smart-contract"
|
||||
)
|
||||
|
||||
func main() {
|
||||
auctionSmartContract, err := contractapi.NewChaincode(&auction.SmartContract{})
|
||||
if err != nil {
|
||||
log.Panicf("Error creating auction chaincode: %v", err)
|
||||
}
|
||||
|
||||
if err := auctionSmartContract.Start(); err != nil {
|
||||
log.Panicf("Error starting auction chaincode: %v", err)
|
||||
}
|
||||
}
|
||||
13
auction-dutch/chaincode-go/go.mod
Normal file
13
auction-dutch/chaincode-go/go.mod
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module github.com/hyperledger/fabric-samples/auction/dutch-auction/chaincode-go
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20201119163726-f8ef75b17719
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.1
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20210127161553-4f432a78f286
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
)
|
||||
172
auction-dutch/chaincode-go/go.sum
Normal file
172
auction-dutch/chaincode-go/go.sum
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20201119163726-f8ef75b17719 h1:FQ9AMLVSFt5QW2YBLraXW5V4Au6aFFpSl4xKFARM58Y=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20201119163726-f8ef75b17719/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.1 h1:gDhOC18gjgElNZ85kFWsbCQq95hyUP/21n++m0Sv6B0=
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.1/go.mod h1:+39cWxbh5py3NtXpRA63rAH7NzXyED+QJx1EZr0tJPo=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20210127161553-4f432a78f286 h1:cFLrvWUprlCbVixFkaeONNlUtbsjv3c20ujb4RJFBl8=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20210127161553-4f432a78f286/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
517
auction-dutch/chaincode-go/smart-contract/auction.go
Normal file
517
auction-dutch/chaincode-go/smart-contract/auction.go
Normal file
|
|
@ -0,0 +1,517 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
)
|
||||
|
||||
type SmartContract struct {
|
||||
contractapi.Contract
|
||||
}
|
||||
|
||||
// Auction data
|
||||
type Auction struct {
|
||||
Type string `json:"objectType"`
|
||||
ItemSold string `json:"item"`
|
||||
Seller string `json:"seller"`
|
||||
Quantity int `json:"quantity"`
|
||||
Orgs []string `json:"organizations"`
|
||||
PrivateBids map[string]BidHash `json:"privateBids"`
|
||||
RevealedBids map[string]FullBid `json:"revealedBids"`
|
||||
Winners []Winners `json:"winners"`
|
||||
Price int `json:"price"`
|
||||
Status string `json:"status"`
|
||||
Auditor bool `json:"auditor"`
|
||||
}
|
||||
|
||||
// FullBid is the structure of a revealed bid
|
||||
type FullBid struct {
|
||||
Type string `json:"objectType"`
|
||||
Quantity int `json:"quantity"`
|
||||
Price int `json:"price"`
|
||||
Org string `json:"org"`
|
||||
Buyer string `json:"buyer"`
|
||||
}
|
||||
|
||||
// BidHash is the structure of a private bid
|
||||
type BidHash struct {
|
||||
Org string `json:"org"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
// Winners stores the winners of the auction
|
||||
type Winners struct {
|
||||
Buyer string `json:"buyer"`
|
||||
Quantity int `json:"quantity"`
|
||||
}
|
||||
|
||||
const bidKeyType = "bid"
|
||||
|
||||
// CreateAuction creates on auction on the public channel. The identity that
|
||||
// submits the transacion becomes the seller of the auction
|
||||
func (s *SmartContract) CreateAuction(ctx contractapi.TransactionContextInterface, auctionID string, itemsold string, quantity int, withAuditor string) error {
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
// get org of submitting client
|
||||
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
auditor := false
|
||||
|
||||
if withAuditor == "withAuditor" {
|
||||
auditor = true
|
||||
}
|
||||
|
||||
// Create auction
|
||||
bidders := make(map[string]BidHash)
|
||||
revealedBids := make(map[string]FullBid)
|
||||
|
||||
auction := Auction{
|
||||
Type: "auction",
|
||||
ItemSold: itemsold,
|
||||
Quantity: quantity,
|
||||
Price: 0,
|
||||
Seller: clientID,
|
||||
Orgs: []string{clientOrgID},
|
||||
PrivateBids: bidders,
|
||||
RevealedBids: revealedBids,
|
||||
Winners: []Winners{},
|
||||
Status: "open",
|
||||
Auditor: auditor,
|
||||
}
|
||||
|
||||
auctionJSON, err := json.Marshal(auction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// put auction into state
|
||||
err = ctx.GetStub().PutState(auctionID, auctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put auction in public data: %v", err)
|
||||
}
|
||||
|
||||
// set the seller of the auction as an endorser
|
||||
err = setAssetStateBasedEndorsement(ctx, auctionID, []string{clientOrgID}, auditor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bid is used to add a users bid to the auction. The bid is stored in the private
|
||||
// data collection on the peer of the bidder's organization. The function returns
|
||||
// the transaction ID so that users can identify and query their bid
|
||||
func (s *SmartContract) Bid(ctx contractapi.TransactionContextInterface, auctionID string) (string, error) {
|
||||
|
||||
// get bid from transient map
|
||||
transientMap, err := ctx.GetStub().GetTransient()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting transient: %v", err)
|
||||
}
|
||||
|
||||
bidJSON, ok := transientMap["bid"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("bid key not found in the transient map")
|
||||
}
|
||||
|
||||
// get the implicit collection name using the bidder's organization ID
|
||||
collection, err := getCollectionName(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get implicit collection name: %v", err)
|
||||
}
|
||||
|
||||
// the bidder has to target their peer to store the bid
|
||||
err = verifyClientOrgMatchesPeerOrg(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Cannot store bid on this peer, not a member of this org: Error %v", err)
|
||||
}
|
||||
|
||||
// the transaction ID is used as a unique index for the bid
|
||||
txID := ctx.GetStub().GetTxID()
|
||||
|
||||
// create a composite key using the transaction ID
|
||||
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
// put the bid into the organization's implicit data collection
|
||||
err = ctx.GetStub().PutPrivateData(collection, bidKey, bidJSON)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to input price into collection: %v", err)
|
||||
}
|
||||
|
||||
// return the trannsaction ID so that the uset can identify their bid
|
||||
return txID, nil
|
||||
}
|
||||
|
||||
// SubmitBid is used by the bidder to add the hash of that bid stored in private data to the
|
||||
// auction. Note that this function alters the auction in private state, and needs
|
||||
// to meet the auction endorsement policy. Transaction ID is used identify the bid
|
||||
func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
|
||||
|
||||
// get the MSP ID of the bidder's org
|
||||
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client MSP ID: %v", err)
|
||||
}
|
||||
|
||||
// get the auction from public state
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
||||
// the auction needs to be open for users to add their bid
|
||||
status := auction.Status
|
||||
if status != "open" {
|
||||
return fmt.Errorf("cannot join closed or ended auction")
|
||||
}
|
||||
|
||||
// get the inplicit collection name of bidder's org
|
||||
collection, err := getCollectionName(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get implicit collection name: %v", err)
|
||||
}
|
||||
|
||||
// use the transaction ID passed as a parameter to create composite bid key
|
||||
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
// get the hash of the bid if found in private collection
|
||||
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read bid bash from collection: %v", err)
|
||||
}
|
||||
if bidHash == nil {
|
||||
return fmt.Errorf("bid hash does not exist: %s", bidKey)
|
||||
}
|
||||
|
||||
// store the hash along with the bidder's organization
|
||||
newHash := BidHash{
|
||||
Org: clientOrgID,
|
||||
Hash: fmt.Sprintf("%x", bidHash),
|
||||
}
|
||||
|
||||
bidders := make(map[string]BidHash)
|
||||
bidders = auction.PrivateBids
|
||||
bidders[bidKey] = newHash
|
||||
auction.PrivateBids = bidders
|
||||
|
||||
// Add the bidding organization to the list of participating organization's if it is not already
|
||||
orgs := auction.Orgs
|
||||
if !(contains(orgs, clientOrgID)) {
|
||||
newOrgs := append(orgs, clientOrgID)
|
||||
auction.Orgs = newOrgs
|
||||
|
||||
err = setAssetStateBasedEndorsement(ctx, auctionID, newOrgs, auction.Auditor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
newAuctionJSON, _ := json.Marshal(auction)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, newAuctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update auction: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RevealBid is used by a bidder to reveal their bid after the auction is closed
|
||||
func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
|
||||
|
||||
// get bid from transient map
|
||||
transientMap, err := ctx.GetStub().GetTransient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting transient: %v", err)
|
||||
}
|
||||
|
||||
transientBidJSON, ok := transientMap["bid"]
|
||||
if !ok {
|
||||
return fmt.Errorf("bid key not found in the transient map")
|
||||
}
|
||||
|
||||
// get implicit collection name of organization ID
|
||||
collection, err := getCollectionName(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get implicit collection name: %v", err)
|
||||
}
|
||||
|
||||
// use transaction ID to create composit bid key
|
||||
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
// get bid hash of bid if private bid on the public ledger
|
||||
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read bid bash from collection: %v", err)
|
||||
}
|
||||
if bidHash == nil {
|
||||
return fmt.Errorf("bid hash does not exist: %s", bidKey)
|
||||
}
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
||||
// Complete a series of three checks before we add the bid to the auction
|
||||
|
||||
// check 1: check that the auction is closed. We cannot reveal an
|
||||
// bid to an open auction
|
||||
status := auction.Status
|
||||
if status != "closed" {
|
||||
return fmt.Errorf("cannot reveal bid for open or ended auction")
|
||||
}
|
||||
|
||||
// check 2: check that hash of revealed bid matches hash of private bid
|
||||
// on the public ledger. This checks that the bidder is telling the truth
|
||||
// about the value of their bid
|
||||
|
||||
hash := sha256.New()
|
||||
hash.Write(transientBidJSON)
|
||||
calculatedBidJSONHash := hash.Sum(nil)
|
||||
|
||||
// verify that the hash of the passed immutable properties matches the on-chain hash
|
||||
if !bytes.Equal(calculatedBidJSONHash, bidHash) {
|
||||
return fmt.Errorf("hash %x for bid JSON %s does not match hash in auction: %x",
|
||||
calculatedBidJSONHash,
|
||||
transientBidJSON,
|
||||
bidHash,
|
||||
)
|
||||
}
|
||||
|
||||
// check 3; check hash of relealed bid matches hash of private bid that was
|
||||
// added earlier. This ensures that the bid has not changed since it
|
||||
// was added to the auction
|
||||
|
||||
bidders := auction.PrivateBids
|
||||
privateBidHashString := bidders[bidKey].Hash
|
||||
|
||||
onChainBidHashString := fmt.Sprintf("%x", bidHash)
|
||||
if privateBidHashString != onChainBidHashString {
|
||||
return fmt.Errorf("hash %s for bid JSON %s does not match hash in auction: %s, bidder must have changed bid",
|
||||
privateBidHashString,
|
||||
transientBidJSON,
|
||||
onChainBidHashString,
|
||||
)
|
||||
}
|
||||
|
||||
// we can add the bid to the auction if all checks have passed
|
||||
type transientBidInput struct {
|
||||
Quantity int `json:"quantity"`
|
||||
Price int `json:"price"`
|
||||
Org string `json:"org"`
|
||||
Buyer string `json:"buyer"`
|
||||
}
|
||||
|
||||
// unmarshal bid imput
|
||||
var bidInput transientBidInput
|
||||
err = json.Unmarshal(transientBidJSON, &bidInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
// marshal transient parameters and ID and MSPID into bid object
|
||||
newBid := FullBid{
|
||||
Type: bidKeyType,
|
||||
Quantity: bidInput.Quantity,
|
||||
Price: bidInput.Price,
|
||||
Org: bidInput.Org,
|
||||
Buyer: bidInput.Buyer,
|
||||
}
|
||||
|
||||
// check 4: make sure that the transaction is being submitted is the bidder
|
||||
if bidInput.Buyer != clientID {
|
||||
return fmt.Errorf("Permission denied, client id %v is not the owner of the bid", clientID)
|
||||
}
|
||||
|
||||
revealedBids := make(map[string]FullBid)
|
||||
revealedBids = auction.RevealedBids
|
||||
revealedBids[bidKey] = newBid
|
||||
auction.RevealedBids = revealedBids
|
||||
|
||||
auctionJSON, _ := json.Marshal(auction)
|
||||
|
||||
// put auction with bid added back into state
|
||||
err = ctx.GetStub().PutState(auctionID, auctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update auction: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseAuction can be used by the seller to close the auction. This prevents
|
||||
// bids from being added to the auction, and allows users to reveal their bid
|
||||
func (s *SmartContract) CloseAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
||||
// the auction can only be closed by the seller
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
seller := auction.Seller
|
||||
if seller != clientID {
|
||||
return fmt.Errorf("auction can only be closed by seller: %v", err)
|
||||
}
|
||||
|
||||
status := auction.Status
|
||||
if status != "open" {
|
||||
return fmt.Errorf("cannot close auction that is not open")
|
||||
}
|
||||
|
||||
auction.Status = string("closed")
|
||||
|
||||
closedAuctionJSON, _ := json.Marshal(auction)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, closedAuctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to close auction: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EndAuction both changes the auction status to closed and calculates the winners
|
||||
// of the auction
|
||||
func (s *SmartContract) EndAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
||||
// Check that the auction is being ended by the seller
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
seller := auction.Seller
|
||||
if seller != clientID {
|
||||
return fmt.Errorf("auction can only be ended by seller: %v", err)
|
||||
}
|
||||
|
||||
status := auction.Status
|
||||
if status != "closed" {
|
||||
return fmt.Errorf("Can only end a closed auction")
|
||||
}
|
||||
|
||||
// get the list of revealed bids
|
||||
|
||||
revealedBidMap := auction.RevealedBids
|
||||
if len(auction.RevealedBids) == 0 {
|
||||
return fmt.Errorf("No bids have been revealed, cannot end auction: %v", err)
|
||||
}
|
||||
|
||||
// sort the map of revealed bids to make it easier to calculate winners
|
||||
// if bids are tied, fill smaller bids first
|
||||
var bidders []FullBid
|
||||
|
||||
for _, bid := range revealedBidMap {
|
||||
bidders = append(bidders, bid)
|
||||
}
|
||||
|
||||
sort.Slice(bidders, func(p, q int) bool {
|
||||
if bidders[p].Price > bidders[q].Price {
|
||||
return true
|
||||
}
|
||||
if bidders[p].Price < bidders[q].Price {
|
||||
return false
|
||||
}
|
||||
return bidders[p].Quantity < bidders[q].Quantity
|
||||
})
|
||||
|
||||
i := 0
|
||||
remainingQuantity := auction.Quantity
|
||||
|
||||
// calculate the winners
|
||||
for remainingQuantity > 0 {
|
||||
|
||||
// create the next winning bid
|
||||
winner := Winners{
|
||||
Buyer: bidders[i].Buyer,
|
||||
Quantity: bidders[i].Quantity,
|
||||
}
|
||||
|
||||
// add them to the list of winners and change the winning price
|
||||
auction.Winners = append(auction.Winners, winner)
|
||||
auction.Price = bidders[i].Price
|
||||
|
||||
// Calculate the quantity that goes to the winner
|
||||
// if there is sufficient quantity to give them the full bid
|
||||
if remainingQuantity > bidders[i].Quantity {
|
||||
remainingQuantity = remainingQuantity - bidders[i].Quantity
|
||||
|
||||
// if there is not, give the remainder
|
||||
} else {
|
||||
auction.Winners[i].Quantity = remainingQuantity
|
||||
remainingQuantity = 0
|
||||
}
|
||||
i++
|
||||
if i == len(bidders) {
|
||||
remainingQuantity = 0
|
||||
}
|
||||
}
|
||||
|
||||
// check if there is a winning bid that has yet to be revealed
|
||||
err = checkForHigherBid(ctx, auction.Price, auction.RevealedBids, auction.PrivateBids)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot end auction: %v", err)
|
||||
}
|
||||
|
||||
auction.Status = string("ended")
|
||||
|
||||
endedAuctionJSON, _ := json.Marshal(auction)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, endedAuctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to end auction: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
136
auction-dutch/chaincode-go/smart-contract/auctionQueries.go
Normal file
136
auction-dutch/chaincode-go/smart-contract/auctionQueries.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
)
|
||||
|
||||
// QueryAuction allows all members of the channel to read a public auction
|
||||
func (s *SmartContract) QueryAuction(ctx contractapi.TransactionContextInterface, auctionID string) (*Auction, error) {
|
||||
|
||||
auctionJSON, err := ctx.GetStub().GetState(auctionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get auction object %v: %v", auctionID, err)
|
||||
}
|
||||
if auctionJSON == nil {
|
||||
return nil, fmt.Errorf("auction does not exist")
|
||||
}
|
||||
|
||||
var auction *Auction
|
||||
err = json.Unmarshal(auctionJSON, &auction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return auction, nil
|
||||
}
|
||||
|
||||
// QueryBid allows the submitter of the bid to read their bid from public state
|
||||
func (s *SmartContract) QueryBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) (*FullBid, error) {
|
||||
|
||||
err := verifyClientOrgMatchesPeerOrg(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get implicit collection name: %v", err)
|
||||
}
|
||||
|
||||
clientID, err := s.GetSubmittingClientIdentity(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
collection, err := getCollectionName(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get implicit collection name: %v", err)
|
||||
}
|
||||
|
||||
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create composite key: %v", err)
|
||||
}
|
||||
|
||||
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get bid %v: %v", bidKey, err)
|
||||
}
|
||||
if bidJSON == nil {
|
||||
return nil, fmt.Errorf("bid %v does not exist", bidKey)
|
||||
}
|
||||
|
||||
var bid *FullBid
|
||||
err = json.Unmarshal(bidJSON, &bid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check that the client querying the bid is the bid owner
|
||||
if bid.Buyer != clientID {
|
||||
return nil, fmt.Errorf("Permission denied, client id %v is not the owner of the bid", clientID)
|
||||
}
|
||||
|
||||
return bid, nil
|
||||
}
|
||||
|
||||
// checkForHigherBid is an internal function that is used to determine if a winning bid has yet to be revealed
|
||||
func checkForHigherBid(ctx contractapi.TransactionContextInterface, auctionPrice int, revealedBidders map[string]FullBid, bidders map[string]BidHash) error {
|
||||
|
||||
// Get MSP ID of peer org
|
||||
peerMSPID, err := shim.GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
|
||||
}
|
||||
|
||||
var error error
|
||||
error = nil
|
||||
|
||||
for bidKey, privateBid := range bidders {
|
||||
|
||||
if _, bidInAuction := revealedBidders[bidKey]; bidInAuction {
|
||||
|
||||
//bid is already revealed, no action to take
|
||||
|
||||
} else {
|
||||
|
||||
collection := "_implicit_org_" + privateBid.Org
|
||||
|
||||
if privateBid.Org == peerMSPID {
|
||||
|
||||
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get bid %v: %v", bidKey, err)
|
||||
}
|
||||
if bidJSON == nil {
|
||||
return fmt.Errorf("bid %v does not exist", bidKey)
|
||||
}
|
||||
|
||||
var bid *FullBid
|
||||
err = json.Unmarshal(bidJSON, &bid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bid.Price > auctionPrice {
|
||||
error = fmt.Errorf("Cannot close auction, bidder has a higher price: %v", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
hash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read bid hash from collection: %v", err)
|
||||
}
|
||||
if hash == nil {
|
||||
return fmt.Errorf("bid hash does not exist: %s", bidKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error
|
||||
}
|
||||
205
auction-dutch/chaincode-go/smart-contract/utils.go
Normal file
205
auction-dutch/chaincode-go/smart-contract/utils.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
"github.com/hyperledger/fabric-protos-go/common"
|
||||
"github.com/hyperledger/fabric-protos-go/msp"
|
||||
)
|
||||
|
||||
func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
|
||||
b64ID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read clientID: %v", err)
|
||||
}
|
||||
decodeID, err := base64.StdEncoding.DecodeString(b64ID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to base64 decode clientID: %v", err)
|
||||
}
|
||||
return string(decodeID), nil
|
||||
}
|
||||
|
||||
// getCollectionName is an internal helper function to get collection of submitting client identity.
|
||||
func getCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
|
||||
// Get the MSP ID of submitting client identity
|
||||
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get verified MSPID: %v", err)
|
||||
}
|
||||
|
||||
// Create the collection name
|
||||
orgCollection := "_implicit_org_" + clientMSPID
|
||||
|
||||
return orgCollection, nil
|
||||
}
|
||||
|
||||
// verifyClientOrgMatchesPeerOrg is an internal function used to verify that client org id matches peer org id.
|
||||
func verifyClientOrgMatchesPeerOrg(ctx contractapi.TransactionContextInterface) error {
|
||||
|
||||
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting the client's MSPID: %v", err)
|
||||
}
|
||||
peerMSPID, err := shim.GetMSPID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
|
||||
}
|
||||
|
||||
if clientMSPID != peerMSPID {
|
||||
return fmt.Errorf("client from org %v is not authorized to read or write private data from an org %v peer", clientMSPID, peerMSPID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(sli []string, str string) bool {
|
||||
for _, a := range sli {
|
||||
if a == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetId string, mspids []string, auditor bool) error {
|
||||
|
||||
principals := make([]*msp.MSPPrincipal, len(mspids))
|
||||
participantSigsPolicy := make([]*common.SignaturePolicy, len(mspids))
|
||||
|
||||
for i, id := range mspids {
|
||||
principal, err := proto.Marshal(
|
||||
&msp.MSPRole{
|
||||
Role: msp.MSPRole_PEER,
|
||||
MspIdentifier: id,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
principals[i] = &msp.MSPPrincipal{
|
||||
PrincipalClassification: msp.MSPPrincipal_ROLE,
|
||||
Principal: principal,
|
||||
}
|
||||
participantSigsPolicy[i] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_SignedBy{
|
||||
SignedBy: int32(i),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if auditor == false {
|
||||
// create the defalt policy for an auction without an auditor
|
||||
|
||||
policy := &common.SignaturePolicyEnvelope{
|
||||
Version: 0,
|
||||
Rule: &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: int32(len(mspids)),
|
||||
Rules: participantSigsPolicy,
|
||||
},
|
||||
},
|
||||
},
|
||||
Identities: principals,
|
||||
}
|
||||
|
||||
spBytes, err := proto.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
|
||||
}
|
||||
} else {
|
||||
|
||||
// create the defalt policy for an auction with an auditor
|
||||
|
||||
// create the auditor identity and signature policy
|
||||
auditorMSP, err := proto.Marshal(
|
||||
&msp.MSPRole{
|
||||
Role: msp.MSPRole_PEER,
|
||||
MspIdentifier: "Org3MSP",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
principals = append(principals, &msp.MSPPrincipal{
|
||||
PrincipalClassification: msp.MSPPrincipal_ROLE,
|
||||
Principal: auditorMSP,
|
||||
},
|
||||
)
|
||||
// Create the policies in case the auditor is needed. In this case, an
|
||||
// auditor and 1 participant can update the auction.
|
||||
auditorPolicies := make([]*common.SignaturePolicy, 2)
|
||||
auditorPolicies[0] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_SignedBy{
|
||||
SignedBy: int32(len(principals) - 1),
|
||||
},
|
||||
}
|
||||
auditorPolicies[1] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: 1,
|
||||
Rules: participantSigsPolicy,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// For two organizations, the auditor policy below is equivilent to
|
||||
// AND(auditor, OR(Org1, Org2))
|
||||
policies := make([]*common.SignaturePolicy, 2)
|
||||
policies[0] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: 2,
|
||||
Rules: auditorPolicies,
|
||||
},
|
||||
},
|
||||
}
|
||||
// Participants can also update the auction without an auditor
|
||||
policies[1] = &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: int32(len(mspids)),
|
||||
Rules: participantSigsPolicy,
|
||||
},
|
||||
},
|
||||
}
|
||||
// Either the auditor policy or the participant policy can update
|
||||
// the auction. For example, for two organizations, the full policy would be
|
||||
// equivilent to OR(AND(Org1, Org2),AND(auditor, OR(Org1, Org2)))
|
||||
policy := &common.SignaturePolicyEnvelope{
|
||||
Version: 0,
|
||||
Rule: &common.SignaturePolicy{
|
||||
Type: &common.SignaturePolicy_NOutOf_{
|
||||
NOutOf: &common.SignaturePolicy_NOutOf{
|
||||
N: 1,
|
||||
Rules: policies,
|
||||
},
|
||||
},
|
||||
},
|
||||
Identities: principals,
|
||||
}
|
||||
spBytes, err := proto.Marshal(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
23
auction-dutch/chaincode-go/smartContract.go
Normal file
23
auction-dutch/chaincode-go/smartContract.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
auction "github.com/hyperledger/fabric-samples/auction/dutch-auction/chaincode-go/smart-contract"
|
||||
)
|
||||
|
||||
func main() {
|
||||
auctionSmartContract, err := contractapi.NewChaincode(&auction.SmartContract{})
|
||||
if err != nil {
|
||||
log.Panicf("Error creating auction chaincode: %v", err)
|
||||
}
|
||||
|
||||
if err := auctionSmartContract.Start(); err != nil {
|
||||
log.Panicf("Error starting auction chaincode: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,14 +29,14 @@ Note that we use the `-ca` flag to deploy the network using certificate authorit
|
|||
|
||||
Run the following command to deploy the auction smart contract. We will override the default endorsement policy to allow any channel member to create an auction without requiring an endorsement from another organization.
|
||||
```
|
||||
./network.sh deployCC -ccn auction -ccp ../auction/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
|
||||
./network.sh deployCC -ccn auction -ccp ../auction-simple/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
|
||||
```
|
||||
|
||||
## Install the application dependencies
|
||||
|
||||
We will interact with the auction smart contract through a set of Node.js applications. Change into the `application-javascript` directory:
|
||||
```
|
||||
cd fabric-samples/auction/application-javascript
|
||||
cd fabric-samples/auction-simple/application-javascript
|
||||
```
|
||||
|
||||
From this directory, run the following command to download the application dependencies:
|
||||
|
|
@ -398,7 +398,7 @@ The transaction was successfully endorsed by both Org1 and Org2, who both calcul
|
|||
|
||||
## Clean up
|
||||
|
||||
When your are done using the auction smart contract, you can bring down the network and clean up the environment. In the `auction/application-javascript` directory, run the following command to remove the wallets used to run the applications:
|
||||
When your are done using the auction smart contract, you can bring down the network and clean up the environment. In the `auction-simple/application-javascript` directory, run the following command to remove the wallets used to run the applications:
|
||||
```
|
||||
rm -rf wallet
|
||||
```
|
||||
5
auction-simple/application-javascript/.eslintignore
Normal file
5
auction-simple/application-javascript/.eslintignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
coverage
|
||||
36
auction-simple/application-javascript/.eslintrc.js
Normal file
36
auction-simple/application-javascript/.eslintrc.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
mocha: true
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
sourceType: 'script'
|
||||
},
|
||||
extends: 'eslint:recommended',
|
||||
rules: {
|
||||
indent: ['error', 'tab'],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'always'],
|
||||
'no-unused-vars': ['error', { args: 'none' }],
|
||||
'no-console': 'off',
|
||||
curly: 'error',
|
||||
eqeqeq: 'error',
|
||||
'no-throw-literal': 'error',
|
||||
strict: 'error',
|
||||
'no-var': 'error',
|
||||
'dot-notation': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-use-before-define': 'error',
|
||||
'no-useless-call': 'error',
|
||||
'no-with': 'error',
|
||||
'operator-linebreak': 'error',
|
||||
yoda: 'error',
|
||||
'quote-props': ['error', 'as-needed']
|
||||
}
|
||||
};
|
||||
|
|
@ -32,10 +32,10 @@ type Auction struct {
|
|||
|
||||
// FullBid is the structure of a revealed bid
|
||||
type FullBid struct {
|
||||
Type string `json:"objectType"`
|
||||
Price int `json:"price"`
|
||||
Org string `json:"org"`
|
||||
Bidder string `json:"bidder"`
|
||||
Type string `json:"objectType"`
|
||||
Price int `json:"price"`
|
||||
Org string `json:"org"`
|
||||
Bidder string `json:"bidder"`
|
||||
}
|
||||
|
||||
// BidHash is the structure of a private bid
|
||||
|
|
@ -157,7 +157,7 @@ func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, a
|
|||
}
|
||||
|
||||
// get the auction from public state
|
||||
auction, err := s.QueryAuction(ctx,auctionID)
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
|
@ -258,7 +258,7 @@ func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, a
|
|||
}
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx,auctionID)
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
|
@ -307,12 +307,12 @@ func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, a
|
|||
|
||||
// we can add the bid to the auction if all checks have passed
|
||||
type transientBidInput struct {
|
||||
Price int `json:"price"`
|
||||
Org string `json:"org"`
|
||||
Bidder string `json:"bidder"`
|
||||
Price int `json:"price"`
|
||||
Org string `json:"org"`
|
||||
Bidder string `json:"bidder"`
|
||||
}
|
||||
|
||||
// unmarshal bid imput
|
||||
// unmarshal bid input
|
||||
var bidInput transientBidInput
|
||||
err = json.Unmarshal(transientBidJSON, &bidInput)
|
||||
if err != nil {
|
||||
|
|
@ -327,10 +327,10 @@ func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, a
|
|||
|
||||
// marshal transient parameters and ID and MSPID into bid object
|
||||
NewBid := FullBid{
|
||||
Type: bidKeyType,
|
||||
Price: bidInput.Price,
|
||||
Org: bidInput.Org,
|
||||
Bidder: bidInput.Bidder,
|
||||
Type: bidKeyType,
|
||||
Price: bidInput.Price,
|
||||
Org: bidInput.Org,
|
||||
Bidder: bidInput.Bidder,
|
||||
}
|
||||
|
||||
// check 4: make sure that the transaction is being submitted is the bidder
|
||||
|
|
@ -359,7 +359,7 @@ func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, a
|
|||
func (s *SmartContract) CloseAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx,auctionID)
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
|
@ -399,7 +399,7 @@ func (s *SmartContract) CloseAuction(ctx contractapi.TransactionContextInterface
|
|||
func (s *SmartContract) EndAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
|
||||
|
||||
// get auction from public state
|
||||
auction, err := s.QueryAuction(ctx,auctionID)
|
||||
auction, err := s.QueryAuction(ctx, auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction from public state %v", err)
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
- job: CommercialPaper_Go
|
||||
displayName: Commercial Paper (Go)
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- template: templates/install-deps.yml
|
||||
- template: templates/commercial-paper/azure-pipelines-go.yml
|
||||
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
- job: CommercialPaper_Java
|
||||
displayName: Commercial Paper (Java)
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- template: templates/install-deps.yml
|
||||
- template: templates/commercial-paper/azure-pipelines-java.yml
|
||||
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
- job: CommercialPaper_JavaScript
|
||||
displayName: Commercial Paper (JavaScript)
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- template: templates/install-deps.yml
|
||||
- template: templates/commercial-paper/azure-pipelines-javascript.yml
|
||||
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
- job: FabCar_Go
|
||||
displayName: FabCar (Go)
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- template: templates/install-deps.yml
|
||||
- template: templates/fabcar/azure-pipelines-go.yml
|
||||
|
|
@ -49,7 +49,7 @@ jobs:
|
|||
- job: FabCar_Java
|
||||
displayName: FabCar (Java)
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- template: templates/install-deps.yml
|
||||
- template: templates/fabcar/azure-pipelines-java.yml
|
||||
|
|
@ -57,7 +57,7 @@ jobs:
|
|||
- job: FabCar_JavaScript
|
||||
displayName: FabCar (JavaScript)
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- template: templates/install-deps.yml
|
||||
- template: templates/fabcar/azure-pipelines-javascript.yml
|
||||
|
|
@ -65,7 +65,7 @@ jobs:
|
|||
- job: Fabcar_TypeScript
|
||||
displayName: FabCar (TypeScript)
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- template: templates/install-deps.yml
|
||||
- template: templates/fabcar/azure-pipelines-typescript.yml
|
||||
|
|
@ -73,7 +73,7 @@ jobs:
|
|||
- job: Lint
|
||||
displayName: Lint
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
|
|
@ -92,7 +92,7 @@ jobs:
|
|||
- job: TestNetworkBasic
|
||||
displayName: Test Network
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
Basic-Go:
|
||||
|
|
@ -109,6 +109,8 @@ jobs:
|
|||
CHAINCODE_LANGUAGE: typescript
|
||||
steps:
|
||||
- template: templates/install-deps.yml
|
||||
- script: sudo apt-get install softhsm2
|
||||
displayName: Install SoftHSM
|
||||
- script: ../ci/scripts/run-test-network-basic.sh
|
||||
workingDirectory: test-network
|
||||
displayName: Run Test Network Basic Chaincode
|
||||
|
|
@ -116,7 +118,7 @@ jobs:
|
|||
- job: TestNetworkLedger
|
||||
displayName: Test Network
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
Ledger-Go:
|
||||
|
|
@ -134,7 +136,7 @@ jobs:
|
|||
- job: TestNetworkPrivate
|
||||
displayName: Test Network
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
Private-Go:
|
||||
|
|
@ -149,7 +151,7 @@ jobs:
|
|||
- job: TestNetworkSBE
|
||||
displayName: Test Network
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
SBE-Typescript:
|
||||
|
|
@ -167,7 +169,7 @@ jobs:
|
|||
- job: TestNetworkSecured
|
||||
displayName: Test Network
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
Secured-Go:
|
||||
|
|
@ -182,7 +184,7 @@ jobs:
|
|||
- job: TestNetworkEvents
|
||||
displayName: Test Network
|
||||
pool:
|
||||
vmImage: ubuntu-18.04
|
||||
vmImage: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
Events-Javascript:
|
||||
|
|
|
|||
|
|
@ -62,3 +62,19 @@ print "Running the output app"
|
|||
node dist/app.js
|
||||
popd
|
||||
stopNetwork
|
||||
|
||||
# Run typescript HSM application
|
||||
createNetwork
|
||||
print "Initializing Typescript HSM application"
|
||||
pushd ../asset-transfer-basic/application-typescript-hsm
|
||||
print "Setup SoftHSM"
|
||||
export SOFTHSM2_CONF=$PWD/softhsm2.conf
|
||||
softhsm2-util --init-token --slot 0 --label "ForFabric" --pin 98765432 --so-pin 1234
|
||||
print "install dependencies"
|
||||
npm install
|
||||
print "Building app.ts"
|
||||
npm run build
|
||||
print "Running the output app"
|
||||
node dist/app.js
|
||||
popd
|
||||
stopNetwork
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
|
||||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const State = require('../ledger-api/state.js');
|
||||
|
|
@ -212,4 +214,4 @@ class QueryUtils {
|
|||
}
|
||||
|
||||
}
|
||||
module.exports = QueryUtils;
|
||||
module.exports = QueryUtils;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
TMPFILE=`mktemp`
|
||||
shopt -s extglob
|
||||
|
||||
function _exit(){
|
||||
|
|
@ -19,7 +20,7 @@ DIR=${PWD}
|
|||
|
||||
# Locate the test network
|
||||
cd "${DIR}/../../../test-network"
|
||||
env | sort > /tmp/env.orig
|
||||
env | sort > $TMPFILE
|
||||
|
||||
OVERRIDE_ORG="1"
|
||||
. ./scripts/envVar.sh
|
||||
|
|
@ -30,8 +31,8 @@ parsePeerConnectionParameters 1 2
|
|||
export FABRIC_CFG_PATH="${DIR}/../../../config"
|
||||
export PATH="${DIR}/../../../bin:${PWD}:$PATH"
|
||||
|
||||
env | sort | comm -1 -3 /tmp/env.orig - | sed -E 's/(.*)=(.*)/export \1="\2"/'
|
||||
env | sort | comm -1 -3 $TMPFILE - | sed -E 's/(.*)=(.*)/export \1="\2"/'
|
||||
|
||||
rm /tmp/env.orig
|
||||
rm $TMPFILE
|
||||
|
||||
cd "${DIR}"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const yaml = require('js-yaml');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
|
@ -212,4 +213,4 @@ class QueryUtils {
|
|||
}
|
||||
|
||||
}
|
||||
module.exports = QueryUtils;
|
||||
module.exports = QueryUtils;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
TMPFILE=`mktemp`
|
||||
shopt -s extglob
|
||||
|
||||
function _exit(){
|
||||
|
|
@ -17,9 +18,9 @@ function _exit(){
|
|||
# Where am I?
|
||||
DIR=${PWD}
|
||||
|
||||
# Locate the test-network
|
||||
# Locate the test-network
|
||||
cd "${DIR}/../../../test-network"
|
||||
env | sort > /tmp/env.orig
|
||||
env | sort > $TMPFILE
|
||||
|
||||
OVERRIDE_ORG="2"
|
||||
. ./scripts/envVar.sh
|
||||
|
|
@ -31,7 +32,7 @@ parsePeerConnectionParameters 1 2
|
|||
export FABRIC_CFG_PATH="${DIR}/../../../config"
|
||||
export PATH="${DIR}/../../../bin:${PWD}:$PATH"
|
||||
|
||||
env | sort | comm -1 -3 /tmp/env.orig - | sed -E 's/(.*)=(.*)/export \1="\2"/'
|
||||
rm /tmp/env.orig
|
||||
env | sort | comm -1 -3 $TMPFILE - | sed -E 's/(.*)=(.*)/export \1="\2"/'
|
||||
rm $TMPFILE
|
||||
|
||||
cd "${DIR}"
|
||||
|
|
|
|||
|
|
@ -116,20 +116,13 @@ async function main() {
|
|||
// Get the network (channel) our contract is deployed to.
|
||||
const network = await gateway.getNetwork('mychannel');
|
||||
|
||||
const listener = await network.addBlockListener(
|
||||
async (err, blockNum, block) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
// Add the block to the processing map by block number
|
||||
await ProcessingMap.set(block.header.number, block);
|
||||
|
||||
console.log(`Added block ${blockNum} to ProcessingMap`)
|
||||
},
|
||||
// set the starting block for the listener
|
||||
{ filtered: false, startBlock: parseInt(nextBlock, 10) }
|
||||
);
|
||||
const listener = async (event) => {
|
||||
// Add the block to the processing map by block number
|
||||
await ProcessingMap.set(event.blockNumber, event.blockData);
|
||||
console.log(`Added block ${event.blockNumber} to ProcessingMap`);
|
||||
};
|
||||
const options = { filtered: false, startBlock: parseInt(nextBlock, 10) };
|
||||
await network.addBlockListener(listener, options);
|
||||
|
||||
console.log(`Listening for block events, nextblock: ${nextBlock}`);
|
||||
|
||||
|
|
|
|||
4
test-network-nano-bash/.gitignore
vendored
Normal file
4
test-network-nano-bash/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
channel-artifacts/
|
||||
crypto-config/
|
||||
data/
|
||||
*.gz
|
||||
85
test-network-nano-bash/README.md
Normal file
85
test-network-nano-bash/README.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Test network - Nano bash
|
||||
|
||||
Test network Nano bash provides a set of minimal bash scripts to run a Fabric network on your local machine.
|
||||
The network is functionally equivalent to the docker-based Test Network, you can therefore run all the tutorials and samples that target the Test Network with minimal changes.
|
||||
The Fabric release binaries are utilized rather than using docker containers to avoid all unnecessary layers. Only the chaincode and chaincode builder runs in a docker container behind the scenes.
|
||||
Using the Fabric binaries also makes it simple for Fabric developers to iteratively and quickly modify Fabric code and test a Fabric network as a user.
|
||||
|
||||
As the name `nano` implies, the scripts provide the smallest minimal setup possible for a Fabric network while still offering a multi-node TLS-enabled network:
|
||||
- Minimal set of dependencies
|
||||
- Minimal requirements on Fabric version (any v2.x orderer and peer nodes should work)
|
||||
- Minimal set of environment variable overrides of the default orderer orderer.yaml and peer core.yaml configurations
|
||||
- Minimal scripting with minimal set of reference commands to get a Fabric network up and running
|
||||
- Minimal channel configuration for an orderer organization (3 ordering nodes) and two peer organizations (with two peers each)
|
||||
- Minimal endorsement policy to allow a single organization to approve and commit a chaincode (unlike Test Network which requires both organizations to endorse)
|
||||
|
||||
# Prereqs
|
||||
|
||||
- Follow the Fabric documentation for the [Prereqs](https://hyperledger-fabric.readthedocs.io/en/latest/prereqs.html)
|
||||
- Follow the Fabric documentation for [downloading the Fabric samples and binaries](https://hyperledger-fabric.readthedocs.io/en/latest/install.html). You can skip the docker image downloads by using `curl -sSL https://bit.ly/2ysbOFE | bash -s -- -d`
|
||||
|
||||
# Instructions for starting network
|
||||
|
||||
Open terminal windows for 3 ordering nodes, 4 peer nodes, and 4 peer admins as seen in the following terminal setup. The first two peers and peer admins belong to Org1, the latter two peer and peer admins belong to Org2.
|
||||
Note, you can start with two ordering nodes and a single Org1 peer node and single Org1 peer admin terminal if you would like to keep things even more minimal (two ordering nodes are required to achieve consensus (2 of 3), while a single peer from Org1 can be utilized since the endorsement policy is set as any single organization).
|
||||

|
||||
|
||||
The following instructions will have you run simple bash scripts that set environment variable overrides for a component and then runs the component.
|
||||
The scripts contain only simple single-line commands so that they are easy to read and understand.
|
||||
If you have trouble running bash scripts in your environment, you can just as easily copy and paste the individual commands from the script files instead of running the script files.
|
||||
|
||||
- cd to the `test-network-nano-bash` directory in each terminal window
|
||||
- In the first orderer terminal, run `./generate_artifacts.sh` to generate crypto material (calls cryptogen) and system and application channel genesis block and configuration transactions (calls configtxgen). The artifacts will be created in the `crypto-config` and `channel-artifacts` directories.
|
||||
- In the three orderer terminals, run `./orderer1.sh`, `./orderer2.sh`, `./orderer3.sh` respectively
|
||||
- In the four peer terminals, run `./peer1.sh`, `./peer2.sh`, `./peer3.sh`, `./peer4.sh` respectively
|
||||
- Note that each orderer and peer write their data (including their ledgers) to their own subdirectory under the `data` directory
|
||||
- In the four peer admin terminals, run `source peer1admin.sh`, `source peer2admin.sh`, `source peer3admin.sh`, `source peer4admin.sh` respectively
|
||||
|
||||
Note the syntax of running the scripts. The peer admin scripts run with the `source` command in order to source the script files in the respective shells. This is important so that the exported environment variables can be utilized by any subsequent user commands.
|
||||
|
||||
The `peer1admin.sh` script sets the peer1 admin environment variables, creates the application channel `mychannel`, updates the channel configuration for the org1 gossip anchor peer, and joins peer1 to `mychannel`.
|
||||
The remaining peer admin scripts join their respective peers to `mychannel`.
|
||||
|
||||
# Instructions for deploying and running the basic asset transfer sample chaincode
|
||||
|
||||
To deploy and invoke the chaincode, utilize the peer1 admin terminal that you have created in the prior steps.
|
||||
|
||||
Package and install the chaincode on peer1:
|
||||
|
||||
```
|
||||
peer lifecycle chaincode package basic.tar.gz --path ../asset-transfer-basic/chaincode-go --lang golang --label basic_1
|
||||
|
||||
peer lifecycle chaincode install basic.tar.gz
|
||||
```
|
||||
|
||||
The chaincode install may take a minute since the `fabric-ccenv` chaincode builder docker image will be downloaded if not already available on your machine.
|
||||
Copy the returned chaincode package ID into an environment variable for use in subsequent commands (your ID may be different):
|
||||
|
||||
```
|
||||
export CC_PACKAGE_ID=basic_1:faaa38f2fc913c8344986a7d1617d21f6c97bc8d85ee0a489c90020cd57af4a5
|
||||
```
|
||||
|
||||
Approve and commit the chaincode (only a single approver is required based on the lifecycle endorsement policy of any organization):
|
||||
|
||||
```
|
||||
peer lifecycle chaincode approveformyorg -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile ${PWD}/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
|
||||
|
||||
peer lifecycle chaincode commit -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --sequence 1 --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
|
||||
```
|
||||
|
||||
Invoke the chaincode to create an asset (only a single endorser is required based on the default endorsement policy of any organization).
|
||||
Then query the asset, update it, and query again to see the resulting asset changes on the ledger.
|
||||
|
||||
```
|
||||
peer chaincode invoke -o 127.0.0.1:6050 -C mychannel -n basic -c '{"Args":["CreateAsset","1","blue","35","tom","1000"]}' --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
|
||||
|
||||
peer chaincode query -C mychannel -n basic -c '{"Args":["ReadAsset","1"]}'
|
||||
|
||||
peer chaincode invoke -o 127.0.0.1:6050 -C mychannel -n basic -c '{"Args":["UpdateAsset","1","blue","35","jerry","1000"]}' --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
|
||||
|
||||
peer chaincode query -C mychannel -n basic -c '{"Args":["ReadAsset","1"]}'
|
||||
```
|
||||
|
||||
Congratulations, you have deployed a minimal Fabric network! Inspect the scripts if you would like to see the minimal set of commands that were required to deploy the network.
|
||||
|
||||
Utilize `Ctrl-C` in the orderer and peer terminal windows to kill the orderer and peer processes. You can run the scripts again to restart the components with their existing data, or run `./generate_artifacts` again to clean up the existing artifacts and data if you would like to restart with a clean environment.
|
||||
389
test-network-nano-bash/configtx.yaml
Normal file
389
test-network-nano-bash/configtx.yaml
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
# Copyright IBM Corp. All Rights Reserved.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
---
|
||||
################################################################################
|
||||
#
|
||||
# Section: Organizations
|
||||
#
|
||||
# - This section defines the different organizational identities which will
|
||||
# be referenced later in the configuration.
|
||||
#
|
||||
################################################################################
|
||||
Organizations:
|
||||
|
||||
# SampleOrg defines an MSP using the sampleconfig. It should never be used
|
||||
# in production but may be used as a template for other definitions
|
||||
- &OrdererOrg
|
||||
# DefaultOrg defines the organization which is used in the sampleconfig
|
||||
# of the fabric.git development environment
|
||||
Name: OrdererOrg
|
||||
|
||||
# ID to load the MSP definition as
|
||||
ID: OrdererMSP
|
||||
|
||||
# MSPDir is the filesystem path which contains the MSP configuration
|
||||
MSPDir: crypto-config/ordererOrganizations/example.com/msp
|
||||
|
||||
# Policies defines the set of policies at this level of the config tree
|
||||
# For organization policies, their canonical path is usually
|
||||
# /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
|
||||
Policies:
|
||||
Readers:
|
||||
Type: Signature
|
||||
Rule: "OR('OrdererMSP.member')"
|
||||
Writers:
|
||||
Type: Signature
|
||||
Rule: "OR('OrdererMSP.member')"
|
||||
Admins:
|
||||
Type: Signature
|
||||
Rule: "OR('OrdererMSP.admin')"
|
||||
|
||||
OrdererEndpoints:
|
||||
- 127.0.0.1:6050
|
||||
- 127.0.0.1:6051
|
||||
- 127.0.0.1:6052
|
||||
|
||||
- &Org1
|
||||
# DefaultOrg defines the organization which is used in the sampleconfig
|
||||
# of the fabric.git development environment
|
||||
Name: Org1MSP
|
||||
|
||||
# ID to load the MSP definition as
|
||||
ID: Org1MSP
|
||||
|
||||
MSPDir: crypto-config/peerOrganizations/org1.example.com/msp
|
||||
|
||||
# Policies defines the set of policies at this level of the config tree
|
||||
# For organization policies, their canonical path is usually
|
||||
# /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
|
||||
Policies:
|
||||
Readers:
|
||||
Type: Signature
|
||||
Rule: "OR('Org1MSP.admin', 'Org1MSP.peer', 'Org1MSP.client')"
|
||||
Writers:
|
||||
Type: Signature
|
||||
Rule: "OR('Org1MSP.admin', 'Org1MSP.client')"
|
||||
Admins:
|
||||
Type: Signature
|
||||
Rule: "OR('Org1MSP.admin')"
|
||||
Endorsement:
|
||||
Type: Signature
|
||||
Rule: "OR('Org1MSP.peer')"
|
||||
|
||||
# leave this flag set to true.
|
||||
AnchorPeers:
|
||||
# AnchorPeers defines the location of peers which can be used
|
||||
# for cross org gossip communication. Note, this value is only
|
||||
# encoded in the genesis block in the Application section context
|
||||
- Host: 127.0.0.1
|
||||
Port: 7051
|
||||
|
||||
- &Org2
|
||||
# DefaultOrg defines the organization which is used in the sampleconfig
|
||||
# of the fabric.git development environment
|
||||
Name: Org2MSP
|
||||
|
||||
# ID to load the MSP definition as
|
||||
ID: Org2MSP
|
||||
|
||||
MSPDir: crypto-config/peerOrganizations/org2.example.com/msp
|
||||
|
||||
# Policies defines the set of policies at this level of the config tree
|
||||
# For organization policies, their canonical path is usually
|
||||
# /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
|
||||
Policies:
|
||||
Readers:
|
||||
Type: Signature
|
||||
Rule: "OR('Org2MSP.admin', 'Org2MSP.peer', 'Org2MSP.client')"
|
||||
Writers:
|
||||
Type: Signature
|
||||
Rule: "OR('Org2MSP.admin', 'Org2MSP.client')"
|
||||
Admins:
|
||||
Type: Signature
|
||||
Rule: "OR('Org2MSP.admin')"
|
||||
Endorsement:
|
||||
Type: Signature
|
||||
Rule: "OR('Org2MSP.peer')"
|
||||
|
||||
AnchorPeers:
|
||||
# AnchorPeers defines the location of peers which can be used
|
||||
# for cross org gossip communication. Note, this value is only
|
||||
# encoded in the genesis block in the Application section context
|
||||
- Host: 127.0.0.1
|
||||
Port: 7055
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# SECTION: Capabilities
|
||||
#
|
||||
# - This section defines the capabilities of fabric network. This is a new
|
||||
# concept as of v1.1.0 and should not be utilized in mixed networks with
|
||||
# v1.0.x peers and orderers. Capabilities define features which must be
|
||||
# present in a fabric binary for that binary to safely participate in the
|
||||
# fabric network. For instance, if a new MSP type is added, newer binaries
|
||||
# might recognize and validate the signatures from this type, while older
|
||||
# binaries without this support would be unable to validate those
|
||||
# transactions. This could lead to different versions of the fabric binaries
|
||||
# having different world states. Instead, defining a capability for a channel
|
||||
# informs those binaries without this capability that they must cease
|
||||
# processing transactions until they have been upgraded. For v1.0.x if any
|
||||
# capabilities are defined (including a map with all capabilities turned off)
|
||||
# then the v1.0.x peer will deliberately crash.
|
||||
#
|
||||
################################################################################
|
||||
Capabilities:
|
||||
# Channel capabilities apply to both the orderers and the peers and must be
|
||||
# supported by both.
|
||||
# Set the value of the capability to true to require it.
|
||||
Channel: &ChannelCapabilities
|
||||
# V2_0 capability ensures that orderers and peers behave according
|
||||
# to v2.0 channel capabilities. Orderers and peers from
|
||||
# prior releases would behave in an incompatible way, and are therefore
|
||||
# not able to participate in channels at v2.0 capability.
|
||||
# Prior to enabling V2.0 channel capabilities, ensure that all
|
||||
# orderers and peers on a channel are at v2.0.0 or later.
|
||||
V2_0: true
|
||||
|
||||
# Orderer capabilities apply only to the orderers, and may be safely
|
||||
# used with prior release peers.
|
||||
# Set the value of the capability to true to require it.
|
||||
Orderer: &OrdererCapabilities
|
||||
# V2_0 orderer capability ensures that orderers behave according
|
||||
# to v2.0 orderer capabilities. Orderers from
|
||||
# prior releases would behave in an incompatible way, and are therefore
|
||||
# not able to participate in channels at v2.0 orderer capability.
|
||||
# Prior to enabling V2.0 orderer capabilities, ensure that all
|
||||
# orderers on channel are at v2.0.0 or later.
|
||||
V2_0: true
|
||||
|
||||
# Application capabilities apply only to the peer network, and may be safely
|
||||
# used with prior release orderers.
|
||||
# Set the value of the capability to true to require it.
|
||||
Application: &ApplicationCapabilities
|
||||
# V2_0 application capability ensures that peers behave according
|
||||
# to v2.0 application capabilities. Peers from
|
||||
# prior releases would behave in an incompatible way, and are therefore
|
||||
# not able to participate in channels at v2.0 application capability.
|
||||
# Prior to enabling V2.0 application capabilities, ensure that all
|
||||
# peers on channel are at v2.0.0 or later.
|
||||
V2_0: true
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# SECTION: Application
|
||||
#
|
||||
# - This section defines the values to encode into a config transaction or
|
||||
# genesis block for application related parameters
|
||||
#
|
||||
################################################################################
|
||||
Application: &ApplicationDefaults
|
||||
|
||||
# Organizations is the list of orgs which are defined as participants on
|
||||
# the application side of the network
|
||||
Organizations:
|
||||
|
||||
# Policies defines the set of policies at this level of the config tree
|
||||
# For Application policies, their canonical path is
|
||||
# /Channel/Application/<PolicyName>
|
||||
Policies:
|
||||
Readers:
|
||||
Type: ImplicitMeta
|
||||
Rule: "ANY Readers"
|
||||
Writers:
|
||||
Type: ImplicitMeta
|
||||
Rule: "ANY Writers"
|
||||
Admins:
|
||||
Type: ImplicitMeta
|
||||
Rule: "MAJORITY Admins"
|
||||
LifecycleEndorsement:
|
||||
Type: Signature
|
||||
Rule: "OR('Org1MSP.peer','Org2MSP.peer')"
|
||||
Endorsement:
|
||||
Type: Signature
|
||||
Rule: "OR('Org1MSP.peer','Org2MSP.peer')"
|
||||
|
||||
Capabilities:
|
||||
<<: *ApplicationCapabilities
|
||||
################################################################################
|
||||
#
|
||||
# SECTION: Orderer
|
||||
#
|
||||
# - This section defines the values to encode into a config transaction or
|
||||
# genesis block for orderer related parameters
|
||||
#
|
||||
################################################################################
|
||||
Orderer: &OrdererDefaults
|
||||
|
||||
# Orderer Type: The orderer implementation to start
|
||||
OrdererType: etcdraft
|
||||
|
||||
EtcdRaft:
|
||||
Consenters:
|
||||
- Host: 127.0.0.1
|
||||
Port: 6050
|
||||
ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
|
||||
ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
|
||||
- Host: 127.0.0.1
|
||||
Port: 6051
|
||||
ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/server.crt
|
||||
ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/server.crt
|
||||
- Host: 127.0.0.1
|
||||
Port: 6052
|
||||
ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/server.crt
|
||||
ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/server.crt
|
||||
|
||||
# Options to be specified for all the etcd/raft nodes. The values here
|
||||
# are the defaults for all new channels and can be modified on a
|
||||
# per-channel basis via configuration updates.
|
||||
Options:
|
||||
# TickInterval is the time interval between two Node.Tick invocations.
|
||||
#TickInterval: 500ms default
|
||||
TickInterval: 2500ms
|
||||
|
||||
# ElectionTick is the number of Node.Tick invocations that must pass
|
||||
# between elections. That is, if a follower does not receive any
|
||||
# message from the leader of current term before ElectionTick has
|
||||
# elapsed, it will become candidate and start an election.
|
||||
# ElectionTick must be greater than HeartbeatTick.
|
||||
# ElectionTick: 10 default
|
||||
ElectionTick: 5
|
||||
|
||||
# HeartbeatTick is the number of Node.Tick invocations that must
|
||||
# pass between heartbeats. That is, a leader sends heartbeat
|
||||
# messages to maintain its leadership every HeartbeatTick ticks.
|
||||
HeartbeatTick: 1
|
||||
|
||||
# MaxInflightBlocks limits the max number of in-flight append messages
|
||||
# during optimistic replication phase.
|
||||
MaxInflightBlocks: 5
|
||||
|
||||
# SnapshotIntervalSize defines number of bytes per which a snapshot is taken
|
||||
SnapshotIntervalSize: 16 MB
|
||||
|
||||
# Batch Timeout: The amount of time to wait before creating a batch
|
||||
BatchTimeout: 2s
|
||||
|
||||
# Batch Size: Controls the number of messages batched into a block
|
||||
BatchSize:
|
||||
|
||||
# Max Message Count: The maximum number of messages to permit in a batch
|
||||
MaxMessageCount: 10
|
||||
|
||||
# Absolute Max Bytes: The absolute maximum number of bytes allowed for
|
||||
# the serialized messages in a batch.
|
||||
AbsoluteMaxBytes: 99 MB
|
||||
|
||||
# Preferred Max Bytes: The preferred maximum number of bytes allowed for
|
||||
# the serialized messages in a batch. A message larger than the preferred
|
||||
# max bytes will result in a batch larger than preferred max bytes.
|
||||
PreferredMaxBytes: 512 KB
|
||||
|
||||
# Organizations is the list of orgs which are defined as participants on
|
||||
# the orderer side of the network
|
||||
Organizations:
|
||||
|
||||
# Policies defines the set of policies at this level of the config tree
|
||||
# For Orderer policies, their canonical path is
|
||||
# /Channel/Orderer/<PolicyName>
|
||||
Policies:
|
||||
Readers:
|
||||
Type: ImplicitMeta
|
||||
Rule: "ANY Readers"
|
||||
Writers:
|
||||
Type: ImplicitMeta
|
||||
Rule: "ANY Writers"
|
||||
Admins:
|
||||
Type: ImplicitMeta
|
||||
Rule: "MAJORITY Admins"
|
||||
# BlockValidation specifies what signatures must be included in the block
|
||||
# from the orderer for the peer to validate it.
|
||||
BlockValidation:
|
||||
Type: ImplicitMeta
|
||||
Rule: "ANY Writers"
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# CHANNEL
|
||||
#
|
||||
# This section defines the values to encode into a config transaction or
|
||||
# genesis block for channel related parameters.
|
||||
#
|
||||
################################################################################
|
||||
Channel: &ChannelDefaults
|
||||
# Policies defines the set of policies at this level of the config tree
|
||||
# For Channel policies, their canonical path is
|
||||
# /Channel/<PolicyName>
|
||||
Policies:
|
||||
# Who may invoke the 'Deliver' API
|
||||
Readers:
|
||||
Type: ImplicitMeta
|
||||
Rule: "ANY Readers"
|
||||
# Who may invoke the 'Broadcast' API
|
||||
Writers:
|
||||
Type: ImplicitMeta
|
||||
Rule: "ANY Writers"
|
||||
# By default, who may modify elements at this config level
|
||||
Admins:
|
||||
Type: ImplicitMeta
|
||||
Rule: "MAJORITY Admins"
|
||||
|
||||
# Capabilities describes the channel level capabilities, see the
|
||||
# dedicated Capabilities section elsewhere in this file for a full
|
||||
# description
|
||||
Capabilities:
|
||||
<<: *ChannelCapabilities
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Profile
|
||||
#
|
||||
# - Different configuration profiles may be encoded here to be specified
|
||||
# as parameters to the configtxgen tool
|
||||
#
|
||||
################################################################################
|
||||
Profiles:
|
||||
|
||||
TwoOrgsOrdererGenesis:
|
||||
<<: *ChannelDefaults
|
||||
Orderer:
|
||||
<<: *OrdererDefaults
|
||||
Organizations:
|
||||
- *OrdererOrg
|
||||
Capabilities:
|
||||
<<: *OrdererCapabilities
|
||||
Consortiums:
|
||||
SampleConsortium:
|
||||
Organizations:
|
||||
- *Org1
|
||||
- *Org2
|
||||
TwoOrgsChannel:
|
||||
Consortium: SampleConsortium
|
||||
<<: *ChannelDefaults
|
||||
Application:
|
||||
<<: *ApplicationDefaults
|
||||
Organizations:
|
||||
- *Org1
|
||||
- *Org2
|
||||
Capabilities:
|
||||
<<: *ApplicationCapabilities
|
||||
Org1Channel:
|
||||
Consortium: SampleConsortium
|
||||
<<: *ChannelDefaults
|
||||
Application:
|
||||
<<: *ApplicationDefaults
|
||||
Organizations:
|
||||
- *Org1
|
||||
Capabilities:
|
||||
<<: *ApplicationCapabilities
|
||||
Org2Channel:
|
||||
Consortium: SampleConsortium
|
||||
<<: *ChannelDefaults
|
||||
Application:
|
||||
<<: *ApplicationDefaults
|
||||
Organizations:
|
||||
- *Org2
|
||||
Capabilities:
|
||||
<<: *ApplicationCapabilities
|
||||
54
test-network-nano-bash/crypto-config.yaml
Normal file
54
test-network-nano-bash/crypto-config.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Copyright IBM Corp. All Rights Reserved.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# "OrdererOrgs" - Definition of organizations managing orderer nodes
|
||||
# ---------------------------------------------------------------------------
|
||||
OrdererOrgs:
|
||||
- Name: Orderer
|
||||
Domain: example.com
|
||||
EnableNodeOUs: true
|
||||
|
||||
Specs:
|
||||
- Hostname: orderer
|
||||
SANS:
|
||||
- 127.0.0.1
|
||||
- Hostname: orderer2
|
||||
SANS:
|
||||
- 127.0.0.1
|
||||
- Hostname: orderer3
|
||||
SANS:
|
||||
- 127.0.0.1
|
||||
- Hostname: orderer4
|
||||
SANS:
|
||||
- 127.0.0.1
|
||||
- Hostname: orderer5
|
||||
SANS:
|
||||
- 127.0.0.1
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# "PeerOrgs" - Definition of organizations managing peer nodes
|
||||
# ---------------------------------------------------------------------------
|
||||
PeerOrgs:
|
||||
- Name: Org1
|
||||
Domain: org1.example.com
|
||||
EnableNodeOUs: true
|
||||
|
||||
Template:
|
||||
Count: 2
|
||||
SANS:
|
||||
- 127.0.0.1
|
||||
Users:
|
||||
Count: 1
|
||||
|
||||
- Name: Org2
|
||||
Domain: org2.example.com
|
||||
EnableNodeOUs: true
|
||||
Template:
|
||||
Count: 2
|
||||
SANS:
|
||||
- 127.0.0.1
|
||||
Users:
|
||||
Count: 1
|
||||
28
test-network-nano-bash/generate_artifacts.sh
Executable file
28
test-network-nano-bash/generate_artifacts.sh
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# remove existing artifacts, or proceed on if the directories don't exist
|
||||
rm -r "${PWD}"/channel-artifacts || true
|
||||
rm -r "${PWD}"/crypto-config || true
|
||||
rm -r "${PWD}"/data || true
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
|
||||
echo "Generating MSP certificates using cryptogen tool"
|
||||
cryptogen generate --config="${PWD}"/crypto-config.yaml
|
||||
|
||||
# set FABRIC_CFG_PATH to configtx.yaml directory that contains the profiles
|
||||
export FABRIC_CFG_PATH="${PWD}"
|
||||
|
||||
echo "Generating orderer genesis block"
|
||||
configtxgen -profile TwoOrgsOrdererGenesis -channelID test-system-channel-name -outputBlock channel-artifacts/genesis.block
|
||||
|
||||
echo "Generating channel create config transaction"
|
||||
configtxgen -channelID mychannel -outputCreateChannelTx channel-artifacts/mychannel.tx -profile TwoOrgsChannel
|
||||
|
||||
echo "Generating anchor peer update transaction for Org1"
|
||||
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP
|
||||
|
||||
echo "Generating anchor peer update transaction for Org2"
|
||||
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP
|
||||
26
test-network-nano-bash/orderer1.sh
Executable file
26
test-network-nano-bash/orderer1.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,common.configtx,common.channelconfig=info
|
||||
export ORDERER_GENERAL_LISTENPORT=6050
|
||||
export ORDERER_GENERAL_LOCALMSPID=OrdererMSP
|
||||
export ORDERER_GENERAL_LOCALMSPDIR="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp
|
||||
export ORDERER_GENERAL_TLS_ENABLED=true
|
||||
export ORDERER_GENERAL_TLS_PRIVATEKEY="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key
|
||||
export ORDERER_GENERAL_TLS_CERTIFICATE="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
|
||||
# following setting is not really needed at runtime since channel config has ca root certs, but we need to override the default in orderer.yaml
|
||||
export ORDERER_GENERAL_TLS_ROOTCAS="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
|
||||
export ORDERER_GENERAL_BOOTSTRAPMETHOD=file
|
||||
export ORDERER_GENERAL_BOOTSTRAPFILE="${PWD}"/channel-artifacts/genesis.block
|
||||
export ORDERER_FILELEDGER_LOCATION="${PWD}"/data/orderer
|
||||
export ORDERER_CONSENSUS_WALDIR="${PWD}"/data/orderer/etcdraft/wal
|
||||
export ORDERER_CONSENSUS_SNAPDIR="${PWD}"/data/orderer/etcdraft/wal
|
||||
export ORDERER_OPERATIONS_LISTENADDRESS=127.0.0.1:8443
|
||||
export ORDERER_ADMIN_LISTENADDRESS=127.0.0.1:9443
|
||||
|
||||
# start orderer
|
||||
orderer
|
||||
26
test-network-nano-bash/orderer2.sh
Executable file
26
test-network-nano-bash/orderer2.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,common.configtx,common.channelconfig=info
|
||||
export ORDERER_GENERAL_LISTENPORT=6051
|
||||
export ORDERER_GENERAL_LOCALMSPID=OrdererMSP
|
||||
export ORDERER_GENERAL_LOCALMSPDIR="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/msp
|
||||
export ORDERER_GENERAL_TLS_ENABLED=true
|
||||
export ORDERER_GENERAL_TLS_PRIVATEKEY="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/server.key
|
||||
export ORDERER_GENERAL_TLS_CERTIFICATE="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/server.crt
|
||||
# following setting is not really needed at runtime since channel config has ca root certs, but we need to override the default in orderer.yaml
|
||||
export ORDERER_GENERAL_TLS_ROOTCAS="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/tls/ca.crt
|
||||
export ORDERER_GENERAL_BOOTSTRAPMETHOD=file
|
||||
export ORDERER_GENERAL_BOOTSTRAPFILE="${PWD}"/channel-artifacts/genesis.block
|
||||
export ORDERER_FILELEDGER_LOCATION="${PWD}"/data/orderer2
|
||||
export ORDERER_CONSENSUS_WALDIR="${PWD}"/data/orderer2/etcdraft/wal
|
||||
export ORDERER_CONSENSUS_SNAPDIR="${PWD}"/data/orderer2/etcdraft/wal
|
||||
export ORDERER_OPERATIONS_LISTENADDRESS=127.0.0.1:8444
|
||||
export ORDERER_ADMIN_LISTENADDRESS=127.0.0.1:9444
|
||||
|
||||
# start orderer
|
||||
orderer
|
||||
26
test-network-nano-bash/orderer3.sh
Executable file
26
test-network-nano-bash/orderer3.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,common.configtx,common.channelconfig=info
|
||||
export ORDERER_GENERAL_LISTENPORT=6052
|
||||
export ORDERER_GENERAL_LOCALMSPID=OrdererMSP
|
||||
export ORDERER_GENERAL_LOCALMSPDIR="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/msp
|
||||
export ORDERER_GENERAL_TLS_ENABLED=true
|
||||
export ORDERER_GENERAL_TLS_PRIVATEKEY="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/server.key
|
||||
export ORDERER_GENERAL_TLS_CERTIFICATE="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/server.crt
|
||||
# following setting is not really needed at runtime since channel config has ca root certs, but we need to override the default in orderer.yaml
|
||||
export ORDERER_GENERAL_TLS_ROOTCAS="${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer3.example.com/tls/ca.crt
|
||||
export ORDERER_GENERAL_BOOTSTRAPMETHOD=file
|
||||
export ORDERER_GENERAL_BOOTSTRAPFILE="${PWD}"/channel-artifacts/genesis.block
|
||||
export ORDERER_FILELEDGER_LOCATION="${PWD}"/data/orderer3
|
||||
export ORDERER_CONSENSUS_WALDIR="${PWD}"/data/orderer3/etcdraft/wal
|
||||
export ORDERER_CONSENSUS_SNAPDIR="${PWD}"/data/orderer3/etcdraft/wal
|
||||
export ORDERER_OPERATIONS_LISTENADDRESS=127.0.0.1:8445
|
||||
export ORDERER_ADMIN_LISTENADDRESS=127.0.0.1:9445
|
||||
|
||||
# start orderer
|
||||
orderer
|
||||
35
test-network-nano-bash/peer1.sh
Executable file
35
test-network-nano-bash/peer1.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_TLS_CERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
|
||||
export CORE_PEER_TLS_KEY_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
|
||||
export CORE_PEER_ID=peer0.org1.example.com
|
||||
export CORE_PEER_ADDRESS=127.0.0.1:7051
|
||||
export CORE_PEER_LISTENADDRESS=127.0.0.1:7051
|
||||
export CORE_PEER_CHAINCODEADDRESS=host.docker.internal:7052
|
||||
export CORE_PEER_CHAINCODELISTENADDRESS=127.0.0.1:7052
|
||||
# bootstrap peer is the other peer in the same org
|
||||
export CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7053
|
||||
export CORE_PEER_GOSSIP_EXTERNALENDPOINT=127.0.0.1:7051
|
||||
export CORE_PEER_LOCALMSPID=Org1MSP
|
||||
export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp
|
||||
export CORE_OPERATIONS_LISTENADDRESS=127.0.0.1:8446
|
||||
export CORE_PEER_FILESYSTEMPATH="${PWD}"/data/peer0.org1.example.com
|
||||
export CORE_LEDGER_SNAPSHOTS_ROOTDIR="${PWD}"/data/peer0.org1.example.com/snapshots
|
||||
|
||||
# uncomment the lines below to utilize couchdb state database, when done with the environment you can stop the couchdb container with "docker rm -f couchdb1"
|
||||
# export CORE_LEDGER_STATE_STATEDATABASE=CouchDB
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=127.0.0.1:5984
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=password
|
||||
# docker run --publish 5984:5984 --detach -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --name couchdb1 couchdb:3.1.1
|
||||
|
||||
# start peer
|
||||
peer node start
|
||||
19
test-network-nano-bash/peer1admin.sh
Executable file
19
test-network-nano-bash/peer1admin.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=INFO
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
|
||||
export CORE_PEER_ADDRESS=127.0.0.1:7051
|
||||
export CORE_PEER_LOCALMSPID=Org1MSP
|
||||
export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
|
||||
|
||||
# peer1 admin will be responsible for creating channel and adding anchor peer
|
||||
peer channel create -c mychannel -o 127.0.0.1:6050 -f "${PWD}"/channel-artifacts/mychannel.tx --outputBlock "${PWD}"/channel-artifacts/mychannel.block --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
|
||||
peer channel update -o 127.0.0.1:6050 -c mychannel -f "${PWD}"/channel-artifacts/Org1MSPanchors.tx --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
|
||||
|
||||
# join peer to channel
|
||||
peer channel join -b "${PWD}"/channel-artifacts/mychannel.block
|
||||
35
test-network-nano-bash/peer2.sh
Executable file
35
test-network-nano-bash/peer2.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_TLS_CERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.crt
|
||||
export CORE_PEER_TLS_KEY_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.key
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt
|
||||
export CORE_PEER_ID=peer1.org1.example.com
|
||||
export CORE_PEER_ADDRESS=127.0.0.1:7053
|
||||
export CORE_PEER_LISTENADDRESS=127.0.0.1:7053
|
||||
export CORE_PEER_CHAINCODEADDRESS=host.docker.internal:7054
|
||||
export CORE_PEER_CHAINCODELISTENADDRESS=127.0.0.1:7054
|
||||
# bootstrap peer is the other peer in the same org
|
||||
export CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7051
|
||||
export CORE_PEER_GOSSIP_EXTERNALENDPOINT=127.0.0.1:7053
|
||||
export CORE_PEER_LOCALMSPID=Org1MSP
|
||||
export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/msp
|
||||
export CORE_OPERATIONS_LISTENADDRESS=127.0.0.1:8447
|
||||
export CORE_PEER_FILESYSTEMPATH="${PWD}"/data/peer1.org1.example.com
|
||||
export CORE_LEDGER_SNAPSHOTS_ROOTDIR="${PWD}"/data/peer1.org1.example.com/snapshots
|
||||
|
||||
# uncomment the lines below to utilize couchdb state database, when done with the environment you can stop the couchdb container with "docker rm -f couchdb2"
|
||||
# export CORE_LEDGER_STATE_STATEDATABASE=CouchDB
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=127.0.0.1:5985
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=password
|
||||
# docker run --publish 5985:5984 --detach -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --name couchdb2 couchdb:3.1.1
|
||||
|
||||
# start peer
|
||||
peer node start
|
||||
15
test-network-nano-bash/peer2admin.sh
Executable file
15
test-network-nano-bash/peer2admin.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=INFO
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt
|
||||
export CORE_PEER_ADDRESS=127.0.0.1:7053
|
||||
export CORE_PEER_LOCALMSPID=Org1MSP
|
||||
export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
|
||||
|
||||
# join peer to channel
|
||||
peer channel join -b "${PWD}"/channel-artifacts/mychannel.block
|
||||
35
test-network-nano-bash/peer3.sh
Executable file
35
test-network-nano-bash/peer3.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_TLS_CERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.crt
|
||||
export CORE_PEER_TLS_KEY_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.key
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
|
||||
export CORE_PEER_ID=peer0.org2.example.com
|
||||
export CORE_PEER_ADDRESS=127.0.0.1:7055
|
||||
export CORE_PEER_LISTENADDRESS=127.0.0.1:7055
|
||||
export CORE_PEER_CHAINCODEADDRESS=host.docker.internal:7056
|
||||
export CORE_PEER_CHAINCODELISTENADDRESS=127.0.0.1:7056
|
||||
# bootstrap peer is the other peer in the same org
|
||||
export CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7057
|
||||
export CORE_PEER_GOSSIP_EXTERNALENDPOINT=127.0.0.1:7055
|
||||
export CORE_PEER_LOCALMSPID=Org2MSP
|
||||
export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp
|
||||
export CORE_OPERATIONS_LISTENADDRESS=127.0.0.1:8448
|
||||
export CORE_PEER_FILESYSTEMPATH="${PWD}"/data/peer0.org2.example.com
|
||||
export CORE_LEDGER_SNAPSHOTS_ROOTDIR="${PWD}"/data/peer0.org2.example.com/snapshots
|
||||
|
||||
# uncomment the lines below to utilize couchdb state database, when done with the environment you can stop the couchdb container with "docker rm -f couchdb3"
|
||||
# export CORE_LEDGER_STATE_STATEDATABASE=CouchDB
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=127.0.0.1:5986
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=password
|
||||
# docker run --publish 5986:5984 --detach -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --name couchdb3 couchdb:3.1.1
|
||||
|
||||
# start peer
|
||||
peer node start
|
||||
15
test-network-nano-bash/peer3admin.sh
Executable file
15
test-network-nano-bash/peer3admin.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=INFO
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
|
||||
export CORE_PEER_ADDRESS=127.0.0.1:7055
|
||||
export CORE_PEER_LOCALMSPID=Org2MSP
|
||||
export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
|
||||
|
||||
# join peer to channel
|
||||
peer channel join -b "${PWD}"/channel-artifacts/mychannel.block
|
||||
35
test-network-nano-bash/peer4.sh
Executable file
35
test-network-nano-bash/peer4.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# look for binaries in local dev environment /build/bin directory and then in local samples /bin directory
|
||||
export PATH="${PWD}"/../../fabric/build/bin:"${PWD}"/../bin:"$PATH"
|
||||
export FABRIC_CFG_PATH="${PWD}"/../config
|
||||
|
||||
export FABRIC_LOGGING_SPEC=debug:cauthdsl,policies,msp,grpc,peer.gossip.mcs,gossip,leveldbhelper=info
|
||||
export CORE_PEER_TLS_ENABLED=true
|
||||
export CORE_PEER_TLS_CERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/server.crt
|
||||
export CORE_PEER_TLS_KEY_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/server.key
|
||||
export CORE_PEER_TLS_ROOTCERT_FILE="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt
|
||||
export CORE_PEER_ID=peer0.org2.example.com
|
||||
export CORE_PEER_ADDRESS=127.0.0.1:7057
|
||||
export CORE_PEER_LISTENADDRESS=127.0.0.1:7057
|
||||
export CORE_PEER_CHAINCODEADDRESS=host.docker.internal:7058
|
||||
export CORE_PEER_CHAINCODELISTENADDRESS=127.0.0.1:7058
|
||||
# bootstrap peer is the other peer in the same org
|
||||
export CORE_PEER_GOSSIP_BOOTSTRAP=127.0.0.1:7055
|
||||
export CORE_PEER_GOSSIP_EXTERNALENDPOINT=127.0.0.1:7057
|
||||
export CORE_PEER_LOCALMSPID=Org2MSP
|
||||
export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/msp
|
||||
export CORE_OPERATIONS_LISTENADDRESS=127.0.0.1:8449
|
||||
export CORE_PEER_FILESYSTEMPATH="${PWD}"/data/peer1.org2.example.com
|
||||
export CORE_LEDGER_SNAPSHOTS_ROOTDIR="${PWD}"/data/peer1.org2.example.com/snapshots
|
||||
|
||||
# uncomment the lines below to utilize couchdb state database, when done with the environment you can stop the couchdb container with "docker rm -f couchdb4"
|
||||
# export CORE_LEDGER_STATE_STATEDATABASE=CouchDB
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=127.0.0.1:5987
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
|
||||
# export CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=password
|
||||
# docker run --publish 5987:5984 --detach -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password --name couchdb4 couchdb:3.1.1
|
||||
|
||||
# start peer
|
||||
peer node start
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue