Merge branch 'main' into vlaca

This commit is contained in:
Matthew B White 2021-09-02 09:05:46 +01:00 committed by GitHub
commit d192984b62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 2123 additions and 17 deletions

View file

@ -10,7 +10,6 @@ fabric-samples uses a non-author code review policy, requiring a single approval
| Chris Ferris | christo4ferris | cbf | chris.ferris@gmail.com |
| Dave Enyeart | denyeart | dave.enyeart | enyeart@us.ibm.com |
| Gari Singh | mastersingh24 | mastersingh24 | gari.r.singh@gmail.com |
| Jason Yellick | jyellick | jyellick | jyellick@us.ibm.com |
| Matthew B White | mbwhite | mbwhite | whitemat@uk.ibm.com |
| Nikhil Gupta | nikhil550 | negupta | nikhilg550@gmail.com |

View file

@ -18,6 +18,7 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.9'
implementation 'io.vertx:vertx-core:3.5.3'
testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
testImplementation 'org.assertj:assertj-core:3.11.1'
testImplementation 'org.mockito:mockito-core:2.+'

View file

@ -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.+'
}

View file

@ -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'

View file

@ -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'

4
test-network-nano-bash/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
channel-artifacts/
crypto-config/
data/
*.gz

View 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).
![Terminal setup](terminal_setup.png)
The following instructions will have you run simple bash scripts that set environment variable overrides for a component and then runs the component.
The scripts contain only simple single-line commands so that they are easy to read and understand.
If you have trouble running bash scripts in your environment, you can just as easily copy and paste the individual commands from the script files instead of running the script files.
- cd to the `test-network-nano-bash` directory in each terminal window
- In the first orderer terminal, run `./generate_artifacts.sh` to generate crypto material (calls cryptogen) and system and application channel genesis block and configuration transactions (calls configtxgen). The artifacts will be created in the `crypto-config` and `channel-artifacts` directories.
- In the three orderer terminals, run `./orderer1.sh`, `./orderer2.sh`, `./orderer3.sh` respectively
- In the four peer terminals, run `./peer1.sh`, `./peer2.sh`, `./peer3.sh`, `./peer4.sh` respectively
- Note that each orderer and peer write their data (including their ledgers) to their own subdirectory under the `data` directory
- In the four peer admin terminals, run `source peer1admin.sh`, `source peer2admin.sh`, `source peer3admin.sh`, `source peer4admin.sh` respectively
Note the syntax of running the scripts. The peer admin scripts run with the `source` command in order to source the script files in the respective shells. This is important so that the exported environment variables can be utilized by any subsequent user commands.
The `peer1admin.sh` script sets the peer1 admin environment variables, creates the application channel `mychannel`, updates the channel configuration for the org1 gossip anchor peer, and joins peer1 to `mychannel`.
The remaining peer admin scripts join their respective peers to `mychannel`.
# Instructions for deploying and running the basic asset transfer sample chaincode
To deploy and invoke the chaincode, utilize the peer1 admin terminal that you have created in the prior steps.
Package and install the chaincode on peer1:
```
peer lifecycle chaincode package basic.tar.gz --path ../asset-transfer-basic/chaincode-go --lang golang --label basic_1
peer lifecycle chaincode install basic.tar.gz
```
The chaincode install may take a minute since the `fabric-ccenv` chaincode builder docker image will be downloaded if not already available on your machine.
Copy the returned chaincode package ID into an environment variable for use in subsequent commands (your ID may be different):
```
export CC_PACKAGE_ID=basic_1:faaa38f2fc913c8344986a7d1617d21f6c97bc8d85ee0a489c90020cd57af4a5
```
Approve and commit the chaincode (only a single approver is required based on the lifecycle endorsement policy of any organization):
```
peer lifecycle chaincode approveformyorg -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile ${PWD}/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
peer lifecycle chaincode commit -o 127.0.0.1:6050 --channelID mychannel --name basic --version 1 --sequence 1 --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
```
Invoke the chaincode to create an asset (only a single endorser is required based on the default endorsement policy of any organization).
Then query the asset, update it, and query again to see the resulting asset changes on the ledger.
```
peer chaincode invoke -o 127.0.0.1:6050 -C mychannel -n basic -c '{"Args":["CreateAsset","1","blue","35","tom","1000"]}' --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
peer chaincode query -C mychannel -n basic -c '{"Args":["ReadAsset","1"]}'
peer chaincode invoke -o 127.0.0.1:6050 -C mychannel -n basic -c '{"Args":["UpdateAsset","1","blue","35","jerry","1000"]}' --tls --cafile "${PWD}"/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt
peer chaincode query -C mychannel -n basic -c '{"Args":["ReadAsset","1"]}'
```
Congratulations, you have deployed a minimal Fabric network! Inspect the scripts if you would like to see the minimal set of commands that were required to deploy the network.
Utilize `Ctrl-C` in the orderer and peer terminal windows to kill the orderer and peer processes. You can run the scripts again to restart the components with their existing data, or run `./generate_artifacts` again to clean up the existing artifacts and data if you would like to restart with a clean environment.

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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
View 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

View 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
View 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

View 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
View 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

View 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/peer1.org2.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=127.0.0.1:7057
export CORE_PEER_LOCALMSPID=Org2MSP
export CORE_PEER_MSPCONFIGPATH="${PWD}"/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
# join peer to channel
peer channel join -b "${PWD}"/channel-artifacts/mychannel.block

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

View file

@ -119,9 +119,9 @@ function generateOrg3Definition() {
function Org3Up () {
# start org3 nodes
if [ "${DATABASE}" == "couchdb" ]; then
docker-compose -f $COMPOSE_FILE_ORG3 -f $COMPOSE_FILE_COUCH_ORG3 up -d 2>&1
DOCKER_SOCK=${DOCKER_SOCK} docker-compose -f $COMPOSE_FILE_ORG3 -f $COMPOSE_FILE_COUCH_ORG3 up -d 2>&1
else
docker-compose -f $COMPOSE_FILE_ORG3 up -d 2>&1
DOCKER_SOCK=${DOCKER_SOCK} docker-compose -f $COMPOSE_FILE_ORG3 up -d 2>&1
fi
if [ $? -ne 0 ]; then
fatalln "ERROR !!!! Unable to start Org3 network"
@ -183,6 +183,10 @@ COMPOSE_FILE_CA_ORG3=docker/docker-compose-ca-org3.yaml
# database
DATABASE="leveldb"
# Get docker sock path from environment variable
SOCK="${DOCKER_HOST:-/var/run/docker.sock}"
DOCKER_SOCK="${SOCK##unix://}"
# Parse commandline args
## Parse mode

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: Apache-2.0
#
version: '2'
version: '2.4'
networks:
test:

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: Apache-2.0
#
version: '2'
version: '2.4'
networks:
test:

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: Apache-2.0
#
version: '2'
version: '2.4'
volumes:
peer0.org3.example.com:
@ -30,7 +30,7 @@ services:
- CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
- CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
- CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
# Peer specific variabes
# Peer specific variables
- CORE_PEER_ID=peer0.org3.example.com
- CORE_PEER_ADDRESS=peer0.org3.example.com:11051
- CORE_PEER_LISTENADDRESS=0.0.0.0:11051
@ -40,7 +40,7 @@ services:
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org3.example.com:11051
- CORE_PEER_LOCALMSPID=Org3MSP
volumes:
- /var/run/docker.sock:/host/var/run/docker.sock
- ${DOCKER_SOCK}:/host/var/run/docker.sock
- ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp:/etc/hyperledger/fabric/msp
- ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls:/etc/hyperledger/fabric/tls
- peer0.org3.example.com:/var/hyperledger/production

View file

@ -76,7 +76,7 @@ services:
- CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
- CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
- CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
# Peer specific variabes
# Peer specific variables
- CORE_PEER_ID=peer0.org1.example.com
- CORE_PEER_ADDRESS=peer0.org1.example.com:7051
- CORE_PEER_LISTENADDRESS=0.0.0.0:7051
@ -87,7 +87,7 @@ services:
- CORE_PEER_LOCALMSPID=Org1MSP
- CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:17051
volumes:
- /var/run/docker.sock:/host/var/run/docker.sock
- ${DOCKER_SOCK}:/host/var/run/docker.sock
- ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp
- ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls:/etc/hyperledger/fabric/tls
- peer0.org1.example.com:/var/hyperledger/production
@ -115,7 +115,7 @@ services:
- CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
- CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
- CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
# Peer specific variabes
# Peer specific variables
- CORE_PEER_ID=peer0.org2.example.com
- CORE_PEER_ADDRESS=peer0.org2.example.com:9051
- CORE_PEER_LISTENADDRESS=0.0.0.0:9051
@ -126,7 +126,7 @@ services:
- CORE_PEER_LOCALMSPID=Org2MSP
- CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:19051
volumes:
- /var/run/docker.sock:/host/var/run/docker.sock
- ${DOCKER_SOCK}:/host/var/run/docker.sock
- ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp:/etc/hyperledger/fabric/msp
- ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls:/etc/hyperledger/fabric/tls
- peer0.org2.example.com:/var/hyperledger/production

View file

@ -241,7 +241,7 @@ function networkUp() {
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_COUCH}"
fi
docker-compose ${COMPOSE_FILES} up -d 2>&1
DOCKER_SOCK="${DOCKER_SOCK}" docker-compose ${COMPOSE_FILES} up -d 2>&1
docker ps -a
if [ $? -ne 0 ]; then
@ -278,7 +278,7 @@ function deployCC() {
# Tear down running network
function networkDown() {
# stop org3 containers also in addition to org1 and org2, in case we were running sample to add org3
docker-compose -f $COMPOSE_FILE_BASE -f $COMPOSE_FILE_COUCH -f $COMPOSE_FILE_CA down --volumes --remove-orphans
DOCKER_SOCK=$DOCKER_SOCK docker-compose -f $COMPOSE_FILE_BASE -f $COMPOSE_FILE_COUCH -f $COMPOSE_FILE_CA down --volumes --remove-orphans
docker-compose -f $COMPOSE_FILE_COUCH_ORG3 -f $COMPOSE_FILE_ORG3 down --volumes --remove-orphans
# Don't remove the generated artifacts -- note, the ledgers are always removed
if [ "$MODE" != "restart" ]; then
@ -338,6 +338,10 @@ CC_SEQUENCE=1
# default database
DATABASE="leveldb"
# Get docker sock path from environment variable
SOCK="${DOCKER_HOST:-/var/run/docker.sock}"
DOCKER_SOCK="${SOCK##unix://}"
# Parse commandline args
## Parse mode

265
token-erc-1155/README.md Normal file
View file

@ -0,0 +1,265 @@
# ERC-1155 Chaincode
This is a sample ERC-1155 chaincode written in Go.
ERC-20 is the standard for fungible tokens. ERC-721 is the standard for non-fungible tokens. ERC-1155 is the standard for multiple tokens (both fungible and non-fungible). [More information about the ERC-1155 standard can be found here.](https://eips.ethereum.org/EIPS/eip-1155)
## Architecture
This implementation aims for high throughput by minimizing key collisions. The balance of accounts is distributed over multiple keys. The token transfers can be batched using the batched versions of functions (e.g. BatchTransferFrom, BalanceOfBatch). Since ERC-1155 is account-based, the interface of the chaincode is account-based. However, since the balances are distributed over multiple keys, the chaincode has a model similar to a UTXO-based chaincode internally.
In this chaincode, one organization has a minter/burner role just like in the [ERC-20 example in this repository](https://github.com/hyperledger/fabric-samples/tree/main/token-erc-20).
## Functions Implemented
The following required functions of ERC-1155 are implemented:
- safeTransferFrom
- safeBatchTransferFrom
- balanceOf
- balanceOfBatch
- setApprovalForAll
- isApprovedForAll
Note: The "safe" prefix is omitted from "TransferFrom" in the implementation because the prefix is related to some issue about backwards compatibility with older smart contracts in Ethereum.
Note: TransferFrom is used to send a single token type between two users. BatchTransferFrom is used to send multiple tokens between two users. So, BatchTransferFrom is a more general form of TransferFrom. Ethereum defined two functions instead of one because this reduces gas costs. Since there is no gas cost in Fabric, implementing these two functions is unnecessary. Nevertheless, both of them are implemented to to conform the ERC-1155 standard.
## Additional Functions Implemented
The following additional functions are also implemented. The following paragraphs give the reasoning behind adding these functions:
- Optional Metadata URI extension:
Defined in ERC-1155 but not required. Allows one to set a URI for tokens and get the URI.
- SetURI
- URI
- Mint/Burn extension:
Although Mint / Burn are not required, they are necessary to change the supply of tokens, create new fungible or non-fungible tokens. In a real implementation, they will be implemented unless the supply of the tokens is fixed beforehand. MintBatch / BurnBatch is only implemented to complement the TransferFrom/BatchTransferFrom. Actually, using only MintBatch and BurnBatch would be enough.
- Mint
- MintBatch
- Burn
- BurnBatch
- Extra/utility functions
- BatchTransferFromMultiRecipient: This is not defined in the standard. We created this function to solve an issue we encountered. It is only required if a person wants to send tokens to multiple persons in a blockchain block. If a person doesn't use this function and create two transactions in a single block, there will be key conflicts because the chaincode will try to decrement the balance of the sender twice in a block and this causes a key conflict in Fabric [just like explained in here](https://github.com/hyperledger/fabric-samples/tree/main/high-throughput). This problem does not exist in Ethereum because, in Ethereum, the transactions are ordered before they are executed.
- BroadcastTokenExistence: Explained in ERC-1155 but it is not required. It is only used if a token minter wants to announce the existence of a token without minting it.
- ClientAccountID: This function is special for Fabric because we do not have wallet addresses in Fabric and users need to know their account ID to transfer tokens.
- ClientAccountBalance: A shorthand for BalanceOf function.
## Example Usage
### Launch test network
Open a command terminal and navigate to the test network directory.
```bash
cd fabric-samples/test-network
```
Clean up the existing network if you have any.
```bash
./network.sh down
```
Start test network
```bash
./network.sh up createChannel -ca
```
### Deploy chaincode
Deploy ERC-1155 chaincode.
```bash
./network.sh deployCC -ccn erc1155 -ccp ../token-erc-1155/chaincode-go/ -ccl go
```
### Register identities
In this example, there are two organizations (org). We will register new identities using the Org1 and Org2 Certificate Authorities (CA's), and then use the CA's to generate each identity's certificate and private key.
We need to set the following environment variables to use the Fabric CA client (and subsequent commands). The first command sets Fabric config path. The second command adds Fabric CLI utilities to path.
```bash
export FABRIC_CFG_PATH=${PWD}/../config/
export PATH=${PWD}/../bin:$PATH
```
The terminal we have been using will represent Org1. We will use the Org1 CA to create a new identity. Set the Fabric CA client home to the MSP of the Org1 CA admin (this identity was generated by the test network script).
```bash
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
```
Register `Person1` identity to Org1.
```bash
fabric-ca-client register --caname ca-org1 --id.name person1 --id.secret person1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
Generate the identity certificates and MSP folder.
```bash
fabric-ca-client enroll -u https://person1:person1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
Copy the Node OU configuration file into the identity MSP folder.
```bash
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp/config.yaml"
```
Open a new terminal to represent Org2 and navigate to fabric-samples/test-network. We'll use the Org2 CA to create the Org2 identities. Set the Fabric CA client home to the MSP of the Org2 CA admin.
```bash
cd fabric-samples/test-network
export FABRIC_CFG_PATH=${PWD}/../config/
export PATH=${PWD}/../bin:$PATH
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
```
Register `Person2`, `Person3`, `Person4`, `Person5` to Org2.
```bash
fabric-ca-client register --caname ca-org2 --id.name person2 --id.secret person2pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://person2:person2pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp/config.yaml"
fabric-ca-client register --caname ca-org2 --id.name person3 --id.secret person3pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://person3:person3pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person3@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person3@org2.example.com/msp/config.yaml"
fabric-ca-client register --caname ca-org2 --id.name person4 --id.secret person4pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://person4:person4pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person4@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person4@org2.example.com/msp/config.yaml"
fabric-ca-client register --caname ca-org2 --id.name person5 --id.secret person5pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://person5:person5pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person5@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person5@org2.example.com/msp/config.yaml"
```
### Get account ID
Go back to the Org1 terminal, we'll set the following environment variables to operate the peer CLI as the minter identity from Org1.
```bash
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")
```
Get client account ID for Person1.
```bash
peer chaincode query -C mychannel -n erc1155 -c '{"function":"ClientAccountID","Args":[]}'
```
```
eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT
```
Client Account ID is a base64-encoded concatenation of the issuer and subject from the client identity's enrolment certificate.
You can decode it with the following command:
```bash
echo "eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT" | base64 --decode
```
```
x509::CN=person1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US
```
Set the client account IDs as environment variables on both of the terminals to make the subsequent commands more readable.
```bash
export P1="eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT"
export P2="eDUwOTo6Q049cGVyc29uMixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs="
export P3="eDUwOTo6Q049cGVyc29uMyxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs="
export P4="eDUwOTo6Q049cGVyc29uNCxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs="
export P5="eDUwOTo6Q049cGVyc29uNSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs="
```
### Mint tokens
Mint tokens by calling the MintBatch function in order to create 100 token1s, 200 token2s, 300 token3s, 150 token4s, 100 token5s, 100 token6s as Person P1 from organization 1.
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"MintBatch\",\"Args\":[\"$P1\",\"[1,2,3,4,5,6]\",\"[100,200,300,150,100,100]\"]}" --waitForEvent
```
Query the tokens of Person1.
```bash
peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOfBatch\",\"Args\":[\"[\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\"]\",\"[1,2,3,4,5,6]\"]}"
```
```
[100,200,300,150,100,100]
```
Side note: There may seem too many slashes in the previous command. It double escapes the quotes. One escape is to be able to use quotes in the `-c` argument of the command. The second escape is necessary to pass the account IDs as an array. Quote is needed since the elements of the array are strings.
### Transfer tokens
#### TransferFrom
Send Person P2 six token3s by calling TransferFrom as Person P1.
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"TransferFrom\",\"Args\":[\"$P1\",\"$P2\",\"3\",\"6\"]}" --waitForEvent
```
Get the new Balance of Person1 for token3.
```bash
peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOf\",\"Args\":[\"$P1\",\"3\"]}"
```
```
294
```
Switch to the Org2 terminal and set the following environment variables.
```bash
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:9051
export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")
```
Get the new balance of Person2 for token3.
```bash
peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOf\",\"Args\":[\"$P2\",\"3\"]}"
```
```
6
```
#### BatchTransferFrom
Switch to the Org1 terminal.
Send Person P2 six token3s, three token4s, and one token2s by calling BatchTransferFrom as Person P1.
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"BatchTransferFrom\",\"Args\":[\"$P1\",\"$P2\",\"[3,4,2]\",\"[6,3,1]\"]}" --waitForEvent
```
#### BatchTransferFromMultiReceipent
Call BatchTransferFromMultiReceipent as Person1 in order to send:
- six token5s to person P3,
- six token3s to person P4,
- three token4s to person P2,
- two token2s to person P5,
- and three token6s to person P2.
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"BatchTransferFromMultiRecipient\",\"Args\":[\"$P1\",\"[\\\"$P3\\\",\\\"$P4\\\",\\\"$P2\\\",\\\"$P5\\\",\\\"$P2\\\"]\",\"[5,3,4,2,6]\",\"[6,6,3,2,3]\"]}" --waitForEvent
```
### Clean up
When you are finished, you can bring down the test network. This command will bring down the CAs, peers, and ordering node of the network that you created.
```bash
./network.sh down
```

View file

@ -0,0 +1,808 @@
/*
2021 Baran Kılıç <baran.kilic@boun.edu.tr>
SPDX-License-Identifier: Apache-2.0
*/
package chaincode
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
const uriKey = "uri"
const balancePrefix = "account~tokenId~sender"
const approvalPrefix = "account~operator"
const minterMSPID = "Org1MSP"
// SmartContract provides functions for transferring tokens between accounts
type SmartContract struct {
contractapi.Contract
}
// TransferSingle MUST emit when a single token is transferred, including zero
// value transfers as well as minting or burning.
// The operator argument MUST be msg.sender.
// The from argument MUST be the address of the holder whose balance is decreased.
// The to argument MUST be the address of the recipient whose balance is increased.
// The id argument MUST be the token type being transferred.
// The value argument MUST be the number of tokens the holder balance is decreased
// by and match what the recipient balance is increased by.
// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address).
// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address).
type TransferSingle struct {
Operator string `json:"operator"`
From string `json:"from"`
To string `json:"to"`
ID uint64 `json:"id"`
Value uint64 `json:"value"`
}
// TransferBatch MUST emit when tokens are transferred, including zero value
// transfers as well as minting or burning.
// The operator argument MUST be msg.sender.
// The from argument MUST be the address of the holder whose balance is decreased.
// The to argument MUST be the address of the recipient whose balance is increased.
// The ids argument MUST be the list of tokens being transferred.
// The values argument MUST be the list of number of tokens (matching the list
// and order of tokens specified in _ids) the holder balance is decreased by
// and match what the recipient balance is increased by.
// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address).
// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address).
type TransferBatch struct {
Operator string `json:"operator"`
From string `json:"from"`
To string `json:"to"`
IDs []uint64 `json:"ids"`
Values []uint64 `json:"values"`
}
// TransferBatchMultiRecipient MUST emit when tokens are transferred, including zero value
// transfers as well as minting or burning.
// The operator argument MUST be msg.sender.
// The from argument MUST be the address of the holder whose balance is decreased.
// The to argument MUST be the list of the addresses of the recipients whose balance is increased.
// The ids argument MUST be the list of tokens being transferred.
// The values argument MUST be the list of number of tokens (matching the list
// and order of tokens specified in _ids) the holder balance is decreased by
// and match what the recipient balance is increased by.
// When minting/creating tokens, the from argument MUST be set to `0x0` (i.e. zero address).
// When burning/destroying tokens, the to argument MUST be set to `0x0` (i.e. zero address).
type TransferBatchMultiRecipient struct {
Operator string `json:"operator"`
From string `json:"from"`
To []string `json:"to"`
IDs []uint64 `json:"ids"`
Values []uint64 `json:"values"`
}
// ApprovalForAll MUST emit when approval for a second party/operator address
// to manage all tokens for an owner address is enabled or disabled
// (absence of an event assumes disabled).
type ApprovalForAll struct {
Owner string `json:"owner"`
Operator string `json:"operator"`
Approved bool `json:"approved"`
}
// URI MUST emit when the URI is updated for a token ID.
// Note: This event is not used in this contract implementation because in this implementation,
// only the programmatic way of setting URI is used. The URI should contain {id} as part of it
// and the clients MUST replace this with the actual token ID.
type URI struct {
Value string `json:"value"`
ID uint64 `json:"id"`
}
type ToID struct {
To string
ID uint64
}
// Mint creates amount tokens of token type id and assigns them to account.
// This function emits a TransferSingle event.
func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Mint tokens
err = mintHelper(ctx, operator, account, id, amount)
if err != nil {
return err
}
// Emit TransferSingle event
transferSingleEvent := TransferSingle{operator, "0x0", account, id, amount}
return emitTransferSingle(ctx, transferSingleEvent)
}
// MintBatch creates amount tokens for each token type id and assigns them to account.
// This function emits a TransferBatch event.
func (s *SmartContract) MintBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error {
if len(ids) != len(amounts) {
return fmt.Errorf("ids and amounts must have the same length")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Group amount by token id because we can only send token to a recipient only one time in a block. This prevents key conflicts
amountToSend := make(map[uint64]uint64) // token id => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ids[i]] += amounts[i]
}
// Mint tokens
for id, amount := range amountToSend {
err = mintHelper(ctx, operator, account, id, amount)
if err != nil {
return err
}
}
// Emit TransferBatch event
transferBatchEvent := TransferBatch{operator, "0x0", account, ids, amounts}
return emitTransferBatch(ctx, transferBatchEvent)
}
// Burn destroys amount tokens of token type id from account.
// This function triggers a TransferSingle event.
func (s *SmartContract) Burn(ctx contractapi.TransactionContextInterface, account string, id uint64, amount uint64) error {
if account == "0x0" {
return fmt.Errorf("burn to the zero address")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Burn tokens
err = removeBalance(ctx, account, []uint64{id}, []uint64{amount})
if err != nil {
return err
}
transferSingleEvent := TransferSingle{operator, account, "0x0", id, amount}
return emitTransferSingle(ctx, transferSingleEvent)
}
// BurnBatch destroys amount tokens of for each token type id from account.
// This function emits a TransferBatch event.
func (s *SmartContract) BurnBatch(ctx contractapi.TransactionContextInterface, account string, ids []uint64, amounts []uint64) error {
if account == "0x0" {
return fmt.Errorf("burn to the zero address")
}
if len(ids) != len(amounts) {
return fmt.Errorf("ids and amounts must have the same length")
}
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
err = removeBalance(ctx, account, ids, amounts)
if err != nil {
return err
}
transferBatchEvent := TransferBatch{operator, account, "0x0", ids, amounts}
return emitTransferBatch(ctx, transferBatchEvent)
}
// TransferFrom transfers tokens from sender account to recipient account
// recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferSingle event
func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error {
if sender == recipient {
return fmt.Errorf("transfer to self")
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Check whether operator is owner or approved
if operator != sender {
approved, err := _isApprovedForAll(ctx, sender, operator)
if err != nil {
return err
}
if !approved {
return fmt.Errorf("caller is not owner nor is approved")
}
}
// Withdraw the funds from the sender address
err = removeBalance(ctx, sender, []uint64{id}, []uint64{amount})
if err != nil {
return err
}
if recipient == "0x0" {
return fmt.Errorf("transfer to the zero address")
}
// Deposit the fund to the recipient address
err = addBalance(ctx, sender, recipient, id, amount)
if err != nil {
return err
}
// Emit TransferSingle event
transferSingleEvent := TransferSingle{operator, sender, recipient, id, amount}
return emitTransferSingle(ctx, transferSingleEvent)
}
// BatchTransferFrom transfers multiple tokens from sender account to recipient account
// recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferBatch event
func (s *SmartContract) BatchTransferFrom(ctx contractapi.TransactionContextInterface, sender string, recipient string, ids []uint64, amounts []uint64) error {
if sender == recipient {
return fmt.Errorf("transfer to self")
}
if len(ids) != len(amounts) {
return fmt.Errorf("ids and amounts must have the same length")
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Check whether operator is owner or approved
if operator != sender {
approved, err := _isApprovedForAll(ctx, sender, operator)
if err != nil {
return err
}
if !approved {
return fmt.Errorf("caller is not owner nor is approved")
}
}
// Withdraw the funds from the sender address
err = removeBalance(ctx, sender, ids, amounts)
if err != nil {
return err
}
if recipient == "0x0" {
return fmt.Errorf("transfer to the zero address")
}
// Group amount by token id because we can only send token to a recipient only one time in a block. This prevents key conflicts
amountToSend := make(map[uint64]uint64) // token id => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ids[i]] += amounts[i]
}
// Deposit the funds to the recipient address
for id, amount := range amountToSend {
err = addBalance(ctx, sender, recipient, id, amount)
if err != nil {
return err
}
}
transferBatchEvent := TransferBatch{operator, sender, recipient, ids, amounts}
return emitTransferBatch(ctx, transferBatchEvent)
}
// BatchTransferFromMultiRecipient transfers multiple tokens from sender account to multiple recipient accounts
// recipient account must be a valid clientID as returned by the ClientID() function
// This function triggers a TransferBatchMultiRecipient event
func (s *SmartContract) BatchTransferFromMultiRecipient(ctx contractapi.TransactionContextInterface, sender string, recipients []string, ids []uint64, amounts []uint64) error {
if len(recipients) != len(ids) || len(ids) != len(amounts) {
return fmt.Errorf("recipients, ids, and amounts must have the same length")
}
for _, recipient := range recipients {
if sender == recipient {
return fmt.Errorf("transfer to self")
}
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Check whether operator is owner or approved
if operator != sender {
approved, err := _isApprovedForAll(ctx, sender, operator)
if err != nil {
return err
}
if !approved {
return fmt.Errorf("caller is not owner nor is approved")
}
}
// Withdraw the funds from the sender address
err = removeBalance(ctx, sender, ids, amounts)
if err != nil {
return err
}
// Group amount by (recipient, id ) pair because we can only send token to a recipient only one time in a block. This prevents key conflicts
amountToSend := make(map[ToID]uint64) // (recipient, id ) => amount
for i := 0; i < len(amounts); i++ {
amountToSend[ToID{recipients[i], ids[i]}] += amounts[i]
}
// Deposit the funds to the recipient addresses
for key, amount := range amountToSend {
if key.To == "0x0" {
return fmt.Errorf("transfer to the zero address")
}
err = addBalance(ctx, sender, key.To, key.ID, amount)
if err != nil {
return err
}
}
// Emit TransferBatchMultiRecipient event
transferBatchMultiRecipientEvent := TransferBatchMultiRecipient{operator, sender, recipients, ids, amounts}
return emitTransferBatchMultiRecipient(ctx, transferBatchMultiRecipientEvent)
}
// IsApprovedForAll returns true if operator is approved to transfer account's tokens.
func (s *SmartContract) IsApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) {
return _isApprovedForAll(ctx, account, operator)
}
// _isApprovedForAll returns true if operator is approved to transfer account's tokens.
func _isApprovedForAll(ctx contractapi.TransactionContextInterface, account string, operator string) (bool, error) {
approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator})
if err != nil {
return false, fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err)
}
approvalBytes, err := ctx.GetStub().GetState(approvalKey)
if err != nil {
return false, fmt.Errorf("failed to read approval of operator %s for account %s from world state: %v", operator, account, err)
}
if approvalBytes == nil {
return false, nil
}
var approved bool
err = json.Unmarshal(approvalBytes, &approved)
if err != nil {
return false, fmt.Errorf("failed to decode approval JSON of operator %s for account %s: %v", operator, account, err)
}
return approved, nil
}
// SetApprovalForAll returns true if operator is approved to transfer account's tokens.
func (s *SmartContract) SetApprovalForAll(ctx contractapi.TransactionContextInterface, operator string, approved bool) error {
// Get ID of submitting client identity
account, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
if account == operator {
return fmt.Errorf("setting approval status for self")
}
approvalForAllEvent := ApprovalForAll{account, operator, approved}
approvalForAllEventJSON, err := json.Marshal(approvalForAllEvent)
if err != nil {
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
}
err = ctx.GetStub().SetEvent("ApprovalForAll", approvalForAllEventJSON)
if err != nil {
return fmt.Errorf("failed to set event: %v", err)
}
approvalKey, err := ctx.GetStub().CreateCompositeKey(approvalPrefix, []string{account, operator})
if err != nil {
return fmt.Errorf("failed to create the composite key for prefix %s: %v", approvalPrefix, err)
}
approvalJSON, err := json.Marshal(approved)
if err != nil {
return fmt.Errorf("failed to encode approval JSON of operator %s for account %s: %v", operator, account, err)
}
err = ctx.GetStub().PutState(approvalKey, approvalJSON)
if err != nil {
return err
}
return nil
}
// BalanceOf returns the balance of the given account
func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) {
return balanceOfHelper(ctx, account, id)
}
// BalanceOfBatch returns the balance of multiple account/token pairs
func (s *SmartContract) BalanceOfBatch(ctx contractapi.TransactionContextInterface, accounts []string, ids []uint64) ([]uint64, error) {
if len(accounts) != len(ids) {
return nil, fmt.Errorf("accounts and ids must have the same length")
}
balances := make([]uint64, len(accounts))
for i := 0; i < len(accounts); i++ {
var err error
balances[i], err = balanceOfHelper(ctx, accounts[i], ids[i])
if err != nil {
return nil, err
}
}
return balances, nil
}
// ClientAccountBalance returns the balance of the requesting client's account
func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface, id uint64) (uint64, error) {
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return 0, fmt.Errorf("failed to get client id: %v", err)
}
return balanceOfHelper(ctx, clientID, id)
}
// ClientAccountID returns the id of the requesting client's account
// In this implementation, the client account ID is the clientId itself
// Users can use this function to get their own account id, which they can then give to others as the payment address
func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) {
// Get ID of submitting client identity
clientAccountID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return "", fmt.Errorf("failed to get client id: %v", err)
}
return clientAccountID, nil
}
// SetURI set the URI value
// This function triggers URI event for each token id
func (s *SmartContract) SetURI(ctx contractapi.TransactionContextInterface, uri string) error {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
if !strings.Contains(uri, "{id}") {
return fmt.Errorf("failed to set uri, uri should contain '{id}'")
}
err = ctx.GetStub().PutState(uriKey, []byte(uri))
if err != nil {
return fmt.Errorf("failed to set uri: %v", err)
}
return nil
}
// URI returns the URI
func (s *SmartContract) URI(ctx contractapi.TransactionContextInterface, id uint64) (string, error) {
uriBytes, err := ctx.GetStub().GetState(uriKey)
if err != nil {
return "", fmt.Errorf("failed to get uri: %v", err)
}
if uriBytes == nil {
return "", fmt.Errorf("no uri is set: %v", err)
}
return string(uriBytes), nil
}
func (s *SmartContract) BroadcastTokenExistance(ctx contractapi.TransactionContextInterface, id uint64) error {
// Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
err := authorizationHelper(ctx)
if err != nil {
return err
}
// Get ID of submitting client identity
operator, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client id: %v", err)
}
// Emit TransferSingle event
transferSingleEvent := TransferSingle{operator, "0x0", "0x0", id, 0}
return emitTransferSingle(ctx, transferSingleEvent)
}
// Helper Functions
// authorizationHelper checks minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens
func authorizationHelper(ctx contractapi.TransactionContextInterface) error {
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get MSPID: %v", err)
}
if clientMSPID != minterMSPID {
return fmt.Errorf("client is not authorized to mint new tokens")
}
return nil
}
func mintHelper(ctx contractapi.TransactionContextInterface, operator string, account string, id uint64, amount uint64) error {
if account == "0x0" {
return fmt.Errorf("mint to the zero address")
}
if amount <= 0 {
return fmt.Errorf("mint amount must be a positive integer")
}
err := addBalance(ctx, operator, account, id, amount)
if err != nil {
return err
}
return nil
}
func addBalance(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error {
// Convert id to string
idString := strconv.FormatUint(uint64(id), 10)
balanceKey, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{recipient, idString, sender})
if err != nil {
return fmt.Errorf("failed to create the composite key for prefix %s: %v", balancePrefix, err)
}
balanceBytes, err := ctx.GetStub().GetState(balanceKey)
if err != nil {
return fmt.Errorf("failed to read account %s from world state: %v", recipient, err)
}
var balance uint64 = 0
if balanceBytes != nil {
balance, _ = strconv.ParseUint(string(balanceBytes), 10, 64)
}
balance += amount
err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(balance), 10)))
if err != nil {
return err
}
return nil
}
func setBalance(ctx contractapi.TransactionContextInterface, sender string, recipient string, id uint64, amount uint64) error {
// Convert id to string
idString := strconv.FormatUint(uint64(id), 10)
balanceKey, err := ctx.GetStub().CreateCompositeKey(balancePrefix, []string{recipient, idString, sender})
if err != nil {
return fmt.Errorf("failed to create the composite key for prefix %s: %v", balancePrefix, err)
}
err = ctx.GetStub().PutState(balanceKey, []byte(strconv.FormatUint(uint64(amount), 10)))
if err != nil {
return err
}
return nil
}
func removeBalance(ctx contractapi.TransactionContextInterface, sender string, ids []uint64, amounts []uint64) error {
// Calculate the total amount of each token to withdraw
necessaryFunds := make(map[uint64]uint64) // token id -> necessary amount
for i := 0; i < len(amounts); i++ {
necessaryFunds[ids[i]] += amounts[i]
}
// Check whether the sender has the necessary funds and withdraw them from the account
for tokenId, neededAmount := range necessaryFunds {
idString := strconv.FormatUint(uint64(tokenId), 10)
var partialBalance uint64
var selfRecipientKeyNeedsToBeRemoved bool
var selfRecipientKey string
balanceIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(balancePrefix, []string{sender, idString})
if err != nil {
return fmt.Errorf("failed to get state for prefix %v: %v", balancePrefix, err)
}
defer balanceIterator.Close()
// Iterate over keys that store balances and add them to partialBalance until
// either the necessary amount is reached or the keys ended
for balanceIterator.HasNext() && partialBalance < neededAmount {
queryResponse, err := balanceIterator.Next()
if err != nil {
return fmt.Errorf("failed to get the next state for prefix %v: %v", balancePrefix, err)
}
partBalAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64)
partialBalance += partBalAmount
_, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(queryResponse.Key)
if err != nil {
return err
}
if compositeKeyParts[2] == sender {
selfRecipientKeyNeedsToBeRemoved = true
selfRecipientKey = queryResponse.Key
} else {
err = ctx.GetStub().DelState(queryResponse.Key)
if err != nil {
return fmt.Errorf("failed to delete the state of %v: %v", queryResponse.Key, err)
}
}
}
if partialBalance < neededAmount {
return fmt.Errorf("sender has insufficient funds for token %v, needed funds: %v, available fund: %v", tokenId, neededAmount, partialBalance)
} else if partialBalance > neededAmount {
// Send the remainder back to the sender
remainder := partialBalance - neededAmount
if selfRecipientKeyNeedsToBeRemoved {
// Set balance for the key that has the same address for sender and recipient
err = setBalance(ctx, sender, sender, tokenId, remainder)
if err != nil {
return err
}
} else {
err = addBalance(ctx, sender, sender, tokenId, remainder)
if err != nil {
return err
}
}
} else {
// Delete self recipient key
err = ctx.GetStub().DelState(selfRecipientKey)
if err != nil {
return fmt.Errorf("failed to delete the state of %v: %v", selfRecipientKey, err)
}
}
}
return nil
}
func emitTransferSingle(ctx contractapi.TransactionContextInterface, transferSingleEvent TransferSingle) error {
transferSingleEventJSON, err := json.Marshal(transferSingleEvent)
if err != nil {
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
}
err = ctx.GetStub().SetEvent("TransferSingle", transferSingleEventJSON)
if err != nil {
return fmt.Errorf("failed to set event: %v", err)
}
return nil
}
func emitTransferBatch(ctx contractapi.TransactionContextInterface, transferBatchEvent TransferBatch) error {
transferBatchEventJSON, err := json.Marshal(transferBatchEvent)
if err != nil {
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
}
err = ctx.GetStub().SetEvent("TransferBatch", transferBatchEventJSON)
if err != nil {
return fmt.Errorf("failed to set event: %v", err)
}
return nil
}
func emitTransferBatchMultiRecipient(ctx contractapi.TransactionContextInterface, transferBatchMultiRecipientEvent TransferBatchMultiRecipient) error {
transferBatchMultiRecipientEventJSON, err := json.Marshal(transferBatchMultiRecipientEvent)
if err != nil {
return fmt.Errorf("failed to obtain JSON encoding: %v", err)
}
err = ctx.GetStub().SetEvent("TransferBatchMultiRecipient", transferBatchMultiRecipientEventJSON)
if err != nil {
return fmt.Errorf("failed to set event: %v", err)
}
return nil
}
// balanceOfHelper returns the balance of the given account
func balanceOfHelper(ctx contractapi.TransactionContextInterface, account string, id uint64) (uint64, error) {
if account == "0x0" {
return 0, fmt.Errorf("balance query for the zero address")
}
// Convert id to string
idString := strconv.FormatUint(uint64(id), 10)
var balance uint64
balanceIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(balancePrefix, []string{account, idString})
if err != nil {
return 0, fmt.Errorf("failed to get state for prefix %v: %v", balancePrefix, err)
}
defer balanceIterator.Close()
for balanceIterator.HasNext() {
queryResponse, err := balanceIterator.Next()
if err != nil {
return 0, fmt.Errorf("failed to get the next state for prefix %v: %v", balancePrefix, err)
}
balAmount, _ := strconv.ParseUint(string(queryResponse.Value), 10, 64)
balance += balAmount
}
return balance, nil
}

View file

@ -0,0 +1,27 @@
/*
2021 Baran Kılıç <baran.kilic@boun.edu.tr>
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"erc1155/chaincode"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
func main() {
smartContract := new(chaincode.SmartContract)
cc, err := contractapi.NewChaincode(smartContract)
if err != nil {
panic(err.Error())
}
if err := cc.Start(); err != nil {
panic(err.Error())
}
}

View file

@ -0,0 +1,5 @@
module erc1155
go 1.16
require github.com/hyperledger/fabric-contract-api-go v1.1.1

View file

@ -0,0 +1,145 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212 h1:1i4lnpV8BDgKOLi1hgElfBqdHXjXieSuj8629mwBZ8o=
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
github.com/hyperledger/fabric-contract-api-go v1.1.1 h1:gDhOC18gjgElNZ85kFWsbCQq95hyUP/21n++m0Sv6B0=
github.com/hyperledger/fabric-contract-api-go v1.1.1/go.mod h1:+39cWxbh5py3NtXpRA63rAH7NzXyED+QJx1EZr0tJPo=
github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e h1:9PS5iezHk/j7XriSlNuSQILyCOfcZ9wZ3/PiucmSE8E=
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ=
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=