# Copyright IBM Corp. All Rights Reserved. # # SPDX-License-Identifier: Apache-2.0 # # 注意这个文件里一点指定 MSP 的 ID 的地方都没有。 # --------------------------------------------------------------------------- # "OrdererOrgs" - Definition of organizations managing orderer nodes # --------------------------------------------------------------------------- OrdererOrgs: # --------------------------------------------------------------------------- # Orderer # --------------------------------------------------------------------------- # 这里的 Name 是 MemberShip 的 ID,大致就是一个法律实体作为会员。 -Name:Orderer Domain:ORDERER_DOMAIN # --------------------------------------------------------------------------- # "Specs" - See PeerOrgs below for complete description # --------------------------------------------------------------------------- Specs: # orderer docker 容器的名字 -Hostname:orderer # --------------------------------------------------------------------------- # "PeerOrgs" - Definition of organizations managing peer nodes # --------------------------------------------------------------------------- PeerOrgs: # --------------------------------------------------------------------------- # Org1 # --------------------------------------------------------------------------- -Name:Org1 # 组织1的完整 domain Domain:ORG1_DOMAIN # --------------------------------------------------------------------------- # "Specs" # --------------------------------------------------------------------------- # Uncomment this section to enable the explicit definition of hosts in your # configuration. Most users will want to use Template, below # # Specs is an array of Spec entries. Each Spec entry consists of two fields: # - Hostname: (Required) The desired hostname, sans the domain. # - CommonName: (Optional) Specifies the template or explicit override for # the CN. By default, this is the template: # # "{{.Hostname}}.{{.Domain}}" # 这个 CommonName 搞不好是拼出来的完整容器名,比如peer0.org1.com 之类的。 # # which obtains its values from the Spec.Hostname and # Org.Domain, respectively. # --------------------------------------------------------------------------- # Specs: # - Hostname: foo # implicitly "foo.ORG1_DOMAIN" # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above # - Hostname: bar # - Hostname: baz # --------------------------------------------------------------------------- # "Template" # --------------------------------------------------------------------------- # Allows for the definition of 1 or more hosts that are created sequentially # from a template. By default, this looks like "peer%d" from 0 to Count-1. # You may override the number of nodes (Count), the starting index (Start) # or the template used to construct the name (Hostname). # # Note: Template and Specs are not mutually exclusive. You may define both # sections and the aggregate nodes will be created for you. Take care with # name collisions # --------------------------------------------------------------------------- # 这里的容器名就不是用 Hostname 指定出来的,而是用 spec 推导出来的了。具体还是看文档,这里就是规定这个组织里有多少个 peer,peer 用什么名字。现在这个名字就是 peer0,peer1 的形式。当然都可以改。 Template: Count:2 # Start: 5 # Hostname: {{.Prefix}}{{.Index}} # default # --------------------------------------------------------------------------- # "Users" # --------------------------------------------------------------------------- # Count: The number of user accounts _in addition_ to Admin # --------------------------------------------------------------------------- # 这个users 本身就是指的非 admin 的 user 要创建出多少套 identity 证书来。 Users: Count:1 # --------------------------------------------------------------------------- # Org2: See "Org1" for full specification # --------------------------------------------------------------------------- -Name:Org2 Domain:ORG2_DOMAIN Template: Count:2 Users: Count:1 -Name:Org3 Domain:ORG3_DOMAIN Template: Count:2 Users: Count:1
################################################################################ # # 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/ORDERER_DOMAIN/msp
-&Org1 # DefaultOrg defines the organization which is used in the sampleconfig # of the fabric.git development environment Name:Org1MSP
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:peer0.ORG1_DOMAIN Port:7051
-&Org2 # DefaultOrg defines the organization which is used in the sampleconfig # of the fabric.git development environment Name:Org2MSP
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:peer0.ORG2_DOMAIN Port:7051
################################################################################ # # SECTION: Orderer # # - This section defines the values to encode into a config transaction or # genesis block for orderer related parameters # ################################################################################ # 这里就是 anchor 了。 Orderer:&OrdererDefaults
# Orderer Type: The orderer implementation to start # Available types are "solo" and "kafka" OrdererType:solo
Addresses: -orderer.ORDERER_DOMAIN:7050
# 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:99MB
# 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:512KB
Kafka: # Brokers: A list of Kafka brokers to which the orderer connects # NOTE: Use IP:port notation Brokers: -127.0.0.1:9092
# Organizations is the list of orgs which are defined as participants on # the orderer side of the network Organizations:
################################################################################ # # 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:
# Copyright IBM Corp. All Rights Reserved. # # SPDX-License-Identifier: Apache-2.0 # # 当前 compose 语法已经升级到了 version 3了。 version:'2'
# 官方文档的释义,大义是顶级的 volume 元素是为了在不同的 service 之间共享 volume 而准备的。 # You can mount a host path as part of a definition for a single service, and there is no need to define it in the top level volumes key. # But, if you want to reuse a volume across multiple services, then define a named volume in the top-level volumes key. Use named volumes with services, swarms, and stack files. volumes: # 这个语法相当于声明了若干个命名卷。不同的容器之间把相同的卷挂载到自己本地的路径里,就相当于打通了两个容器的数据共享。 # 注意,命名卷的开头并不是路径形式的,所以不要求当前的 host 有这个名字的绝对路径或者相对路径。 orderer.ORDERER_DOMAIN: peer0.org1.example.com: peer1.org1.example.com: peer0.org2.example.com: peer1.org2.example.com:
# # Copyright IBM Corp All Rights Reserved # # SPDX-License-Identifier: Apache-2.0 #
# This script will orchestrate a sample end-to-end execution of the Hyperledger # Fabric network. # # The end-to-end verification provisions a sample Fabric network consisting of # two organizations, each maintaining two peers, and a “solo” ordering service. # # This verification makes use of two fundamental tools, which are necessary to # create a functioning transactional network with digital signature validation # and access control: # # * cryptogen - generates the x509 certificates used to identify and # authenticate the various components in the network. # * configtxgen - generates the requisite configuration artifacts for orderer # bootstrap and channel creation. # # Each tool consumes a configuration yaml file, within which we specify the topology # of our network (cryptogen) and the location of our certificates for various # configuration operations (configtxgen). Once the tools have been successfully run, # we are able to launch our network. More detail on the tools and the structure of # the network will be provided later in this document. For now, let's get going...
# prepending $PWD/../bin to PATH to ensure we are picking up the correct binaries # this may be commented out to resolve installed version of tools if desired export PATH=${PWD}/../bin:${PWD}:$PATH # 这个环境变量很重要,这里的 export 保证这个变量是在 environment 里。可以被子进程(另一个 shell 脚本继承)。 # 并不是所有的父进程里的 variable 都会被子进程看到的。 export FABRIC_CFG_PATH=${PWD}
# Print the usage message functionprintHelp () { echo"Usage: " echo" byfn.sh -m up|down|restart|generate [-c <channel name>] [-t <timeout>] [-d <delay>] [-f <docker-compose-file>] [-s <dbtype>] [-i <imagetag>]" echo" byfn.sh -h|--help (print this message)" echo" -m <mode> - one of 'up', 'down', 'restart' or 'generate'" echo" - 'up' - bring up the network with docker-compose up" echo" - 'down' - clear the network with docker-compose down" echo" - 'restart' - restart the network" echo" - 'generate' - generate required certificates and genesis block" echo" -c <channel name> - channel name to use (defaults to \"mychannel\")" echo" -t <timeout> - CLI timeout duration in microseconds (defaults to 10000)" echo" -d <delay> - delay duration in seconds (defaults to 3)" echo" -f <docker-compose-file> - specify which docker-compose file use (defaults to docker-compose-cli.yaml)" echo" -s <dbtype> - the database backend to use: goleveldb (default) or couchdb" echo" -i <imagetag> - pass the image tag to launch the network using the tag: 1.0.1, 1.0.2, 1.0.3, 1.0.4 (defaults to latest)" echo echo"Typically, one would first generate the required certificates and " echo"genesis block, then bring up the network. e.g.:" echo echo" byfn.sh -m generate -c mychannel" echo" byfn.sh -m up -c mychannel -s couchdb" echo" byfn.sh -m up -c mychannel -s couchdb -i 1.0.6" echo" byfn.sh -m down -c mychannel" echo echo"Taking all defaults:" echo" byfn.sh -m generate" echo" byfn.sh -m up" echo" byfn.sh -m down" }
# Ask user for confirmation to proceed functionaskProceed () { read -p "Continue (y/n)? " ans case"$ans"in y|Y ) # 只有这个地方会正常走下去 echo"proceeding ..." # 这个双分号是 case 的休止符 ;; n|N ) echo"exiting..." exit 1 ;; * ) # 这里会递归 echo"invalid response" askProceed ;; esac }
# Obtain CONTAINER_IDS and remove them # TODO Might want to make this optional - could clear other containers functionclearContainers () { # -a 表示输出全部容器,-q 表示只输出容器 id,这样就可以迭代使用了 # 结果大概是这样 # 8a25b455f05e # d2d84545a902 # c67d337e5bd9 # e5cbd8457caf # 9ee6a1cb33c1 # a0df580d4633 # cb6b441b083f # 0c87d55710c8 # c24f3eb2e0e0 CONTAINER_IDS=$(docker ps -aq) if [ -z "$CONTAINER_IDS" -o "$CONTAINER_IDS" == " " ]; then echo"---- No containers available for deletion ----" else # 直接拿这个列表来 rm,而不用再 xargs 了。 docker rm -f $CONTAINER_IDS fi }
# Delete any images that were generated as a part of this setup # specifically the following images are often left behind: # TODO list generated image naming patterns functionremoveUnwantedImages() { # 用 awk 来截取多段输出的好例子。 # 注意在这里我们可以看到不要乱改 peer 的名字,不然不能在这里搞到所有的 image_id。 # $()的形式可以把命令求和为一个匿名变量。 DOCKER_IMAGE_IDS=$(docker images | grep "dev\|none\|test-vp\|peer[0-9]-" | awk '{print $3}') if [ -z "$DOCKER_IMAGE_IDS" -o "$DOCKER_IMAGE_IDS" == " " ]; then echo"---- No images available for deletion ----" else docker rmi -f $DOCKER_IMAGE_IDS fi }
# Generate the needed certificates, the genesis block and start the network. functionnetworkUp () { # generate artifacts if they don't exist # 确认一个目录是否存在的命令 if [ ! -d "crypto-config" ]; then # 第一步,生成证书。其实不只是生成证书,还生成证书相关的东西。 generateCerts replacePrivateKey generateChannelArtifacts fi if [ "${IF_COUCHDB}" == "couchdb" ]; then IMAGE_TAG=$IMAGETAG CHANNEL_NAME=$CHANNEL_NAME TIMEOUT=$CLI_TIMEOUT DELAY=$CLI_DELAY docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_COUCH up -d 2>&1 else # 这一行让 Docker-Compose 来按照配置文件装配容器配置,生成由容器组成的项目网络 # cli 容器起来以后,会自己去创建频道、一个一个地把容器加入网络中。一个一个地初始化链码,一个一个地测试链码。 IMAGE_TAG=$IMAGETAG CHANNEL_NAME=$CHANNEL_NAME TIMEOUT=$CLI_TIMEOUT DELAY=$CLI_DELAY docker-compose -f $COMPOSE_FILE up -d 2>&1 fi if [ $? -ne 0 ]; then echo"ERROR !!!! Unable to start network" # 这个命令就是去读一个容器启动的 command 在 console 里的输出。 docker logs -f cli exit 1 fi docker logs -f cli }
# Tear down running network functionnetworkDown () { docker-compose -f $COMPOSE_FILE down --volumes docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_COUCH down --volumes # Don't remove the generated artifacts -- note, the ledgers are always removed if [ "$MODE" != "restart" ]; then # Bring the containers down deleting their volumes #Cleanup the chaincode containers clearContainers #Cleanup images removeUnwantedImages # remove orderer block and other channel configuration transactions and certs rm -rf channel-artifacts/*.block channel-artifacts/*.tx crypto-config # remove the docker-compose yaml file that was customized to the example rm -f docker-compose-e2e.yaml fi }
# 但这个文件有什么用呢?docker-compose-e2e-template.yaml # Using docker-compose-e2e-template.yaml, replace constants with private key file names # generated by the cryptogen tool and output a docker-compose.yaml specific to this # configuration functionreplacePrivateKey () { # sed on MacOSX does not support -i flag with a null extension. We will use # 't' for our back-up's extension and depete it at the end of the function ARCH=`uname -s | grep Darwin` if [ "$ARCH" == "Darwin" ]; then OPTS="-it" else OPTS="-i" fi
# Copy the template to the file that will be modified to add the private key cp docker-compose-e2e-template.yaml docker-compose-e2e.yaml
# The next steps will replace the template's contents with the # actual values of the private key file names for the two CAs. # 把当前的父文件夹记住 CURRENT_DIR=$PWD cd crypto-config/peerOrganizations/org1.example.com/ca/ PRIV_KEY=$(ls *_sk) cd"$CURRENT_DIR" sed $OPTS"s/CA1_PRIVATE_KEY/${PRIV_KEY}/g" docker-compose-e2e.yaml cd crypto-config/peerOrganizations/org2.example.com/ca/ PRIV_KEY=$(ls *_sk) cd"$CURRENT_DIR" sed $OPTS"s/CA2_PRIVATE_KEY/${PRIV_KEY}/g" docker-compose-e2e.yaml # If MacOSX, remove the temporary backup of the docker-compose file if [ "$ARCH" == "Darwin" ]; then rm docker-compose-e2e.yamlt fi }
# X.509只是 PKI 密码学架构的一个实现。 # ca-cert 是根证书。 # 为什么 count 必须放在这个文件里面。 # 这个函数不止生成证书,还有 keystore。 # 私钥签署,公钥验证!而不是公钥签署,私钥验证! # We will use the cryptogen tool to generate the cryptographic material (x509 certs) # for our various network entities. The certificates are based on a standard PKI # implementation where validation is achieved by reaching a common trust anchor. # # Cryptogen consumes a file - ``crypto-config.yaml`` - that contains the network # topology and allows us to generate a library of certificates for both the # Organizations and the components that belong to those Organizations. Each # Organization is provisioned a unique root certificate (``ca-cert``), that binds # specific components (peers and orderers) to that Org. Transactions and communications # within Fabric are signed by an entity's private key (``keystore``), and then verified # by means of a public key (``signcerts``). You will notice a "count" variable within # this file. We use this to specify the number of peers per Organization; in our # case it's two peers per Org. The rest of this template is extremely # self-explanatory. # # After we run the tool, the certs will be parked in a folder titled ``crypto-config``.
# Generates Org certs using cryptogen tool functiongenerateCerts (){ which cryptogen if [ "$?" -ne 0 ]; then echo"cryptogen tool not found. exiting" exit 1 fi echo echo"##########################################################" echo"##### Generate certificates using cryptogen tool #########" echo"##########################################################" if [ -d "crypto-config" ]; then rm -Rf crypto-config fi cryptogen generate --config=./crypto-config.yaml if [ "$?" -ne 0 ]; then echo"Failed to generate certificates..." exit 1 fi echo }
# 每一个新的 org 都要有一个 anchor 节点。这也就意味着不仅配置文件里要有相关的配置,也意味着初始化步骤里 # 要有相关的配置命令。 # 把三件事做起来: # 1 生成创世区块。 2 生成频道配置 3 配置锚节点。 # # The `configtxgen tool is used to create four artifacts: orderer **bootstrap # block**, fabric **channel configuration transaction**, and two **anchor # peer transactions** - one for each Peer Org. # # The orderer block is the genesis block for the ordering service, and the # channel transaction file is broadcast to the orderer at channel creation # time. The anchor peer transactions, as the name might suggest, specify each # Org's anchor peer on this channel. # # Configtxgen consumes a file - ``configtx.yaml`` - that contains the definitions # for the sample network. There are three members - one Orderer Org (``OrdererOrg``) # and two Peer Orgs (``Org1`` & ``Org2``) each managing and maintaining two peer nodes. # This file also specifies a consortium - ``SampleConsortium`` - consisting of our # two Peer Orgs. Pay specific attention to the "Profiles" section at the top of # this file. You will notice that we have two unique headers. One for the orderer genesis # block - ``TwoOrgsOrdererGenesis`` - and one for our channel - ``TwoOrgsChannel``. # These headers are important, as we will pass them in as arguments when we create # our artifacts. This file also contains two additional specifications that are worth # noting. Firstly, we specify the anchor peers for each Peer Org # (``peer0.org1.example.com`` & ``peer0.org2.example.com``). Secondly, we point to # the location of the MSP directory for each member, in turn allowing us to store the # root certificates for each Org in the orderer genesis block. This is a critical # concept. Now any network entity communicating with the ordering service can have # its digital signature verified. # # This function will generate the crypto material and our four configuration # artifacts, and subsequently output these files into the ``channel-artifacts`` # folder. # # If you receive the following warning, it can be safely ignored: # # [bccsp] GetDefault -> WARN 001 Before using BCCSP, please call InitFactories(). Falling back to bootBCCSP. # # You can ignore the logs regarding intermediate certs, we are not using them in # this crypto implementation.
# Generate orderer genesis block, channel configuration transaction and # anchor peer update transactions functiongenerateChannelArtifacts() { # 检测一个命令是不是存在 which configtxgen if [ "$?" -ne 0 ]; then echo"configtxgen tool not found. exiting" exit 1 fi
echo"##########################################################" echo"######### Generating Orderer Genesis block ##############" echo"##########################################################" # Note: For some unknown reason (at least for now) the block file can't be # named orderer.genesis.block or the orderer will fail to launch! # 1 生成创世区块 configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block if [ "$?" -ne 0 ]; then echo"Failed to generate orderer genesis block..." exit 1 fi echo echo"#################################################################" echo"### Generating channel configuration transaction 'channel.tx' ###" echo"#################################################################" # 2 生成频道配置,实际上就是 channel 事务。 configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID $CHANNEL_NAME if [ "$?" -ne 0 ]; then echo"Failed to generate channel configuration transaction..." exit 1 fi
echo echo"#################################################################" echo"####### Generating anchor peer update for Org1MSP ##########" echo"#################################################################" # configtxgen -help 可以看到它的各种 options。其中 outputAnchorPeersUpdate 只能在创建缺省频道(实际上自定义频道也可以),和最初建 anchor 时才可以使用。 # 这个 channel 名和 asOrg 参数共同决定 Org1MSPanchors.tx 的内容。 configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP if [ "$?" -ne 0 ]; then echo"Failed to generate anchor peer update for Org1MSP..." exit 1 fi
echo echo"#################################################################" echo"####### Generating anchor peer update for Org2MSP ##########" echo"#################################################################" configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate \ ./channel-artifacts/Org2MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org2MSP if [ "$?" -ne 0 ]; then echo"Failed to generate anchor peer update for Org2MSP..." exit 1 fi echo }
# 定义完函数以后,这里才是程序的起点 # Obtain the OS and Architecture string that will be used to select the correct # native binaries for your platform OS_ARCH=$(echo"$(uname -s|tr '[:upper:]' '[:lower:]'|sed 's/mingw64_nt.*/windows/')-$(uname -m | sed 's/x86_64/amd64/g')" | awk '{print tolower($0)}') # timeout duration - the duration the CLI should wait for a response from # another container before giving up CLI_TIMEOUT=10 #default for delay CLI_DELAY=3 # channel name defaults to "mychannel" CHANNEL_NAME="mychannel" # use this as the default docker-compose yaml definition COMPOSE_FILE=docker-compose-cli.yaml # COMPOSE_FILE_COUCH=docker-compose-couch.yaml # default image tag IMAGETAG="latest" # Parse commandline args whilegetopts"h?m:c:t:d:f:s:i:" opt; do case"$opt"in h|\?) printHelp exit 0 ;; m) MODE=$OPTARG ;; c) CHANNEL_NAME=$OPTARG ;; t) CLI_TIMEOUT=$OPTARG ;; d) CLI_DELAY=$OPTARG ;; f) COMPOSE_FILE=$OPTARG ;; s) IF_COUCHDB=$OPTARG ;; i) IMAGETAG=`uname -m`"-"$OPTARG ;; esac done
# Determine whether starting, stopping, restarting or generating for announce if [ "$MODE" == "up" ]; then EXPMODE="Starting" elif [ "$MODE" == "down" ]; then EXPMODE="Stopping" elif [ "$MODE" == "restart" ]; then EXPMODE="Restarting" elif [ "$MODE" == "generate" ]; then EXPMODE="Generating certs and genesis block for" else printHelp exit 1 fi
# Announce what was requested
if [ "${IF_COUCHDB}" == "couchdb" ]; then echo echo"${EXPMODE} with channel '${CHANNEL_NAME}' and CLI timeout of '${CLI_TIMEOUT}' using database '${IF_COUCHDB}'" else echo"${EXPMODE} with channel '${CHANNEL_NAME}' and CLI timeout of '${CLI_TIMEOUT}'" fi # ask for confirmation to proceed askProceed
#Create the network using docker compose if [ "${MODE}" == "up" ]; then networkUp elif [ "${MODE}" == "down" ]; then## Clear the network networkDown elif [ "${MODE}" == "generate" ]; then## Generate Artifacts generateCerts replacePrivateKey generateChannelArtifacts elif [ "${MODE}" == "restart" ]; then## Restart the network networkDown networkUp else printHelp exit 1 fi
# verify the result of the end-to-end test verifyResult () { if [ $1 -ne 0 ] ; then echo"!!!!!!!!!!!!!!! "$2" !!!!!!!!!!!!!!!!" echo"========= ERROR !!! FAILED to execute End-2-End Scenario ===========" echo exit 1 fi }
setGlobals () {
if [ $1 -eq 0 -o $1 -eq 1 ] ; then CORE_PEER_LOCALMSPID="Org1MSP" CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp # 传进来不同的命令,更改的核心环境变量指标是容器的 endpoint 地址。也就是CORE_PEER_ADDRESS。相同组织的 enpoint 使用的是相同的 MSP 密码学目录。 if [ $1 -eq 0 ]; then CORE_PEER_ADDRESS=peer0.org1.example.com:7051 else CORE_PEER_ADDRESS=peer1.org1.example.com:7051 # 此处有重复,疑为错误。但 github 上原版的代码就是这样写的。 CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp fi else CORE_PEER_LOCALMSPID="Org2MSP" CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp if [ $1 -eq 2 ]; then CORE_PEER_ADDRESS=peer0.org2.example.com:7051 else CORE_PEER_ADDRESS=peer1.org2.example.com:7051 fi fi
## Sometimes Join takes time hence RETRY atleast for 5 times joinWithRetry () { # peer channel join 实际上是消耗四个环境变量作为把 peer 加入 channel 的依据,所以外部传进来的环境变量到此几乎可说是无用的。 # CORE_PEER_MSPCONFIGPATH # CORE_PEER_ADDRESS # CORE_PEER_LOCALMSPID # CORE_PEER_TLS_ROOTCERT_FILE peer channel join -b $CHANNEL_NAME.block >&log.txt res=$? cat log.txt if [ $res -ne 0 -a $COUNTER -lt $MAX_RETRY ]; then COUNTER=` expr$COUNTER + 1` # 这的 peer 是顺序 peer,和实际的容器名是不一样的。 echo"PEER$1 failed to join the channel, Retry after 2 seconds" # bash sleep 的妙用 sleep$DELAY # 递归重试 joinWithRetry $1 else COUNTER=1 fi verifyResult $res"After $MAX_RETRY attempts, PEER$ch has failed to Join the Channel" }
joinChannel () { for ch in 0 1 2 3; do setGlobals $ch joinWithRetry $ch echo"===================== PEER$ch joined on the channel \"$CHANNEL_NAME\" ===================== " sleep$DELAY echo done }
installChaincode () { PEER=$1 setGlobals $PEER # 这里这个 p 就是在cli 容器内可以看到的 chaincode 的 go 文件路径了。n 则是链码的合约名字。 # 这个链码为什么不需要经过编译,真是奇也怪哉。 peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 >&log.txt res=$? cat log.txt verifyResult $res"Chaincode installation on remote peer PEER$PEER has Failed" echo"===================== Chaincode is installed on remote peer PEER$PEER ===================== " echo }
instantiateChaincode () { PEER=$1 setGlobals $PEER # 用硬编码的方式好过用接口的方式来读写 orderer endpoint 的位置。 # while 'peer chaincode' command can get the orderer endpoint from the peer (if join was successful), # lets supply it directly as we know it using the "-o" option if [ -z "$CORE_PEER_TLS_ENABLED" -o "$CORE_PEER_TLS_ENABLED" = "false" ]; then # 在链码初始化的时候,制定背书策略。 # 供反射调用 peer chaincode instantiate -o orderer.ORDERER_DOMAIN:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')" >&log.txt else peer chaincode instantiate -o orderer.ORDERER_DOMAIN:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')" >&log.txt fi res=$? cat log.txt verifyResult $res"Chaincode instantiation on PEER$PEER on channel '$CHANNEL_NAME' failed" echo"===================== Chaincode Instantiation on PEER$PEER on channel '$CHANNEL_NAME' is successful ===================== " echo }
chaincodeQuery () { PEER=$1 echo"===================== Querying on PEER$PEER on channel '$CHANNEL_NAME'... ===================== " setGlobals $PEER local rc=1 local starttime=$(date +%s)
# continue to poll # we either get a successful response, or reach TIMEOUT whiletest"$(($(date +%s)-starttime))" -lt "$TIMEOUT" -a $rc -ne 0 do sleep$DELAY echo"Attempting to Query PEER$PEER ...$(($(date +%s)-starttime)) secs" peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}' >&log.txt test $? -eq 0 && VALUE=$(cat log.txt | awk '/Query Result/ {print $NF}') test"$VALUE" = "$2" && let rc=0 done echo cat log.txt iftest$rc -eq 0 ; then echo"===================== Query on PEER$PEER on channel '$CHANNEL_NAME' is successful ===================== " else echo"!!!!!!!!!!!!!!! Query result on PEER$PEER is INVALID !!!!!!!!!!!!!!!!" echo"================== ERROR !!! FAILED to execute End-2-End Scenario ==================" echo exit 1 fi }
chaincodeInvoke () { PEER=$1 setGlobals $PEER # while 'peer chaincode' command can get the orderer endpoint from the peer (if join was successful), # lets supply it directly as we know it using the "-o" option if [ -z "$CORE_PEER_TLS_ENABLED" -o "$CORE_PEER_TLS_ENABLED" = "false" ]; then # -c 是调用的消息的构造函数的参数。后台基本上是用反射什么的来执行调用的。 peer chaincode invoke -o orderer.ORDERER_DOMAIN:7050 -C $CHANNEL_NAME -n mycc -c '{"Args":["invoke","a","b","10"]}' >&log.txt else peer chaincode invoke -o orderer.ORDERER_DOMAIN:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -c '{"Args":["invoke","a","b","10"]}' >&log.txt fi res=$? cat log.txt verifyResult $res"Invoke execution on PEER$PEER failed " echo"===================== Invoke transaction on PEER$PEER on channel '$CHANNEL_NAME' is successful ===================== " echo }
## Join all the peers to the channel echo"Having all peers join the channel..." joinChannel
## Set the anchor peers for each org in the channel echo"Updating anchor peers for org1..." # 这两个函数调用,要仔细更改当前的核心环境变量为两个组织预先生成好的 anchor 节点的 endpoint。 updateAnchorPeers 0 echo"Updating anchor peers for org2..." updateAnchorPeers 2
# 只在 peer0 和 peer2上安装合约 ## Install chaincode on Peer0/Org1 and Peer2/Org2 echo"Installing chaincode on org1/peer0..." installChaincode 0 echo"Install chaincode on org2/peer2..." installChaincode 2
# 只在 peer2上初始化合约 #Instantiate chaincode on Peer2/Org2 echo"Instantiating chaincode on org2/peer2..." instantiateChaincode 2
# 只在 peer0 上查询合约 #Query on chaincode on Peer0/Org1 echo"Querying chaincode on org1/peer0..." chaincodeQuery 0 100
# 只在 peer0 上调用合约 #Invoke on chaincode on Peer0/Org1 echo"Sending invoke transaction on org1/peer0..." chaincodeInvoke 0
# 在 peer3 上追加安装合约 ## Install chaincode on Peer3/Org2 echo"Installing chaincode on org2/peer3..." installChaincode 3
# 在 peer3 上追加查询合约。可见追加进来的合约调用结果可以被明确查询到。 #Query on chaincode on Peer3/Org2, check if the result is 90 echo"Querying chaincode on org2/peer3..." chaincodeQuery 3 90
echo echo"========= All GOOD, BYFN execution completed =========== " echo
For example, John Doe of Accounting division in FOO Corporation in Detroit, Michigan might have a digital certificate with a SUBJECT attribute of C=US, ST=Michigan, L=Detroit, O=FOO Corporation, OU=Accounting, CN=John Doe /UID=123456.
注意,这些属性都是这个 party 的 subject attribute 的一部分。
注意看,这个 party 的公钥是这个证书的一部分,而私钥则不是。
因为有密码学加持,所以这个证书是抗篡改的,里面的 attribute 可以被认为是真的,只要别人信任这个证书的签发方的话。因为这个签发方用自己的私钥的方法把这些 attribute 加密写入(secrete writing)了这个证书。注意,签发证书的时候,csr 的请求者不需要提供自己的私钥。在 CA 那里,是证书公钥 + CA 私钥 + CA 证书(当然也包含了 CA 的公钥)来生成新的证书。
5.1.2 质询和公私钥
质询和消息完整性是安全通信的重要概念。私钥签名,公钥验证签名。
5.1.3 CA
现实中的 CA 是一系列权威。用自己的私钥给一个 party 的属性和公钥签名。因为 CA 是周知的权威,它的签名可以被周知的 CA 公钥验证。
在区块链里,我们当然希望一个或多个 CA 可以帮我们定义一个组织里有哪几个成员。
因为根 CA 的证书,可以帮我们验证某个证书是不是它签发的,所以我们可以通过根 CA 签发一个中间 CA 的证书,而这个中间 CA 一旦被验证了,它也可以产生它签发的证书。这就是 CA 构成的信任链的本质了。
换言之,任何一个证书,都可以拿来当 CA 证书来用。
做区块链世界里,理论上不同的组织可以由不同的根 CA 签发证书。Fabric 的 CA 不能拿来提供 SSL 证书,这点来看就不如 openssl 这样的工具了。而且,实际上我们也可以使用公共的权威 CA 来生成相关的组织证书。
CA 有证书撤销列表(Certificate Revocation Lists),验证的 party 要验证证书的时候,还要查看撤销列表(在线查看?离线怎么办?):
每个组织还可以再分成不同的 OU(organizational units),用不同的 OU 来区分不同的业务线。这个 OU 就是 X.509 证书里的那个 attribute。我们可以靠 OU 这个属性来做权限控制(实际上我们应该可以通过任何属性来做权限控制)。Fabric 的原话是,我们可以用同一套 CA 体系,靠不同的 OU 来区分不同的组织成员(为什么不直接用不同的 O ?)。
这里没有把 user 也列出来,基本上除了本节点或者 user 私有的证书(包含了公钥)和 keystore(包含了私钥)以外,有大量的 ca 证书,管理员证书和 tls 证书是整个 org 共享的。注意,不同组织的 ca 证书是完全不同的,足以证明 cryptogen 这个工具生成的不同的组织的组织根 ca 还是不一样的。
正如我们前面看到的,orderer 在启动的时候,需要得到一个"系统频道"的创世区块,这个创世区块必须包含全网络中出现的所有 MSP 的验证参数。这些验证参数包括:“verification parameters consist of the MSP identifier, the root of trust certificates, intermediate CA and admin certificates, as well as OU specifications and CRLs”。这样 orderer 才能处理频道创建请求(channel creation requests)。
// Define the insurance structure, with 2 properties. Structure tags are used by encoding/json library type Insurance struct { Owner string`json:"owner"` Money string`json:"money"` }
// Init is called during chaincode instantiation to initialize any // data. Note that chaincode upgrade also calls this function to reset // or to migrate data. func(t *Insurance) Init(stub shim.ChaincodeStubInterface) peer.Response { // Get the args from the transaction proposal args := stub.GetStringArgs() iflen(args) != 2 { return shim.Error("Incorrect arguments. Expecting owner and money") }
// create new insurance object and marshal it into json. insuranceAsBytes, _ := json.Marshal(Insurance{args[0], args[1]}) // Set up any variables or assets here by calling stub.PutState()
// We store the key and the value on the ledger err := stub.PutState(args[0], insuranceAsBytes) if err != nil { return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0])) } return shim.Success(nil) }
// Invoke is called per transaction on the chaincode. Each transaction is // either a 'get' or a 'set' on the asset created by Init function. The Set // method may create a new asset by specifying a new key-value pair. func(t *Insurance) Invoke(stub shim.ChaincodeStubInterface) peer.Response { // Extract the function and args from the transaction proposal fn, args := stub.GetFunctionAndParameters()
var result string var err error if fn == "set" { result, err = set(stub, args) } elseif fn == "getHistory" { result, err = getHistory(stub, args) } else { // assume 'get' even if fn is nil result, err = get(stub, args) } if err != nil { return shim.Error(err.Error()) }
// Return the result as success payload return shim.Success([]byte(result)) }
// Set stores the asset (both key and value) on the ledger. If the key exists, // it will override the value with the new one funcset(stub shim.ChaincodeStubInterface, args []string) (string, error) { iflen(args) != 2 { return"", fmt.Errorf("Incorrect arguments. Expecting a key and a value") }
insuranceAsBytes, _ := json.Marshal(Insurance{args[0], args[1]}) err := stub.PutState(args[0], insuranceAsBytes) if err != nil { return"", fmt.Errorf("Failed to set asset: %s", args[0]) } return args[1], nil }
// Get returns the value of the specified asset key funcget(stub shim.ChaincodeStubInterface, args []string) (string, error) { iflen(args) != 1 { return"", fmt.Errorf("Incorrect arguments. Expecting a key") }
value, err := stub.GetState(args[0]) if err != nil { return"", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err) } if value == nil { return"", fmt.Errorf("Asset not found: %s", args[0]) }
returnstring(value), nil }
// Get returns the value of the specified asset key funcgetHistory(stub shim.ChaincodeStubInterface, args []string) (string, error) { iflen(args) != 1 { return"", fmt.Errorf("Incorrect arguments. Expecting a key") }
resultsIterator, err := stub.GetHistoryForKey(args[0]) if err != nil { return"", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err) } if resultsIterator == nil { return"", fmt.Errorf("history not found: %s", args[0]) }
defer resultsIterator.Close()
// buffer is a JSON array containing historic values for the marble var buffer bytes.Buffer buffer.WriteString("[")
bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { response, err := resultsIterator.Next() if err != nil { return"iterating error", err } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { buffer.WriteString(",") } buffer.WriteString("{\"TxId\":") buffer.WriteString("\"") buffer.WriteString(response.TxId) buffer.WriteString("\"")
buffer.WriteString(", \"Value\":") // if it was a delete operation on given key, then we need to set the //corresponding value null. Else, we will write the response.Value //as-is (as the Value itself a JSON marble) if response.IsDelete { buffer.WriteString("null") } else { buffer.WriteString(string(response.Value)) }
// main function starts up the chaincode in the container during instantiate funcmain() { if err := shim.Start(new(Insurance)); err != nil { fmt.Printf("Error starting Insurance chaincode: %s", err) } }
然后修改 vi .//src/agent/docker/docker_swarm.py,把 COMPOSE_PROJECT_PATH 改为:'COMPOSE_PROJECT_PATH': '/opt/cello2/fabric-1.0/local'。以保证 ./src/agent/docker/_compose_files/fabric-1.0/local/docker-compose-base.yaml 和 vi ./src/agent/docker/_compose_files/fabric-1.0/local/fabric-solo-4.yaml 可以正确地重新 mount 上该目录。
修改防火墙打开 ip 转发:
1
sudo sysctl -w net.inet.ip.forwarding=1
通常,Cello 网络的 channel id 是 businesschannel 或者 testchainid
composer network deploy -a charity-network.bna -p explorer -i PeerAdmin -s randomString
开启 compose-rest-server:
1 2 3 4 5 6 7 8 9
$ composer-rest-server ? Enter your Connection Profile Name: explorer ? Enter your Business Network name : charity-network ? Enter your enrollment ID : admin ? Enter your enrollment secret : adminpw ? Specify if you want namespaces in the generated REST API: never use namespaces ? Specify if you want toenable authentication for the REST API using Passport: No ? Specify if you want toenable event publicationover WebSockets: Yes ? Specify if you want toenable TLS securityfor the REST API: No
九、其他要点
9.1 CA 问题
CA 是可选组件。
但如果 CA server 跑起来,我们可以向它发送 REST 请求给组织的 CA 来完成用户注册和登记。
CA 是 PKI 的实现。PKI 可以抗女巫攻击。
CA 可以像 cryptogen 一样,先生成组织里的节点证书和私钥(组织里一定会有 root-ca),然后生成成员(admin 和普通成员)的证书和私钥。
9.2 常见端口号
这些端口号有先后顺序,适合在同一个 host 编排一系列容器。
orderer:7050
peer:7051
peer event:7053
ca:7054
9.3 SDK 问题
区块链天然可以用 gRPC 来通信。
因为各种语言都支持 Protocol Buffers,基于 Protocol Buffers over gRPC,我们可以制造各种语言的 SDK。