Fabric开发(四) 基于Fabirc1.4.3编写你的第一个应用

   日期:2020-09-27     浏览:101    评论:0    
核心提示:写在前面(到到底什么是Fabric?)在第一节的最后,我们粗略地说了说到底什么是Fabirc。在这里,我得问我们一遍。到底什么是Fabirc?我这个人比较喜欢望文生义,见文知意,顾名思义。。。找Gu爸问问,给如下结果:具体是什么意思,我们暂且按下不表。来看看,我们的另外一个角儿,Hyperledger(超级账本)。对,我们这里讲Fabric确切的说只是Hyperledger下的一个项目而已。中文官网描述如下:到这里,我们应该对Hyperledger Fabirc(以下简称Fabric)有了进一步

写在前面(到到底什么是Fabric?)

在第一节的最后,我粗略地说了说到底什么是Fabirc。在这里,我得再问自己一遍。到底什么是Fabirc?
我这个人比较喜欢望文生义,见文知意,顾名思义。。。找Gu爸问问,给如下结果:

具体是什么意思,我们暂且按下不表。来看看,我们的另外一个角儿,Hyperledger(超级账本)。对,我们这里讲Fabric确切的说只是Hyperledger下的一个项目而已。中文官网描述如下:

到这里,我们应该对Hyperledger Fabirc(以下简称Fabric)有了进一步的认识,他本质就是一项分布式账本技术,结合上文的翻译,Fabric就是一个分布式账本技术框架。至于对该技术的分析,我们后续文章会进行连载,再问亿遍什么是Fabirc。 这里,我们先来编写一个应用,看看他到底在做什么,能做什么吧。

基于Fabric编写第一个应用程序

我们写这个程序要干什么呢?
最基本的功能就是要给用户提供查询账本(包含特定记录)和更新账本(添加记录)。

而这个程序会基于javascript,通过Node.js SDK与账本所在的网络进行交互。

Hyperledger中文文档给出了以下步骤,我们可以照猫画虎来试试。(毕竟是入门文章,删除线覆盖部分可以先不看)

  1. 启动一个Hyperledger Fabric区块链测试网络。
    在我们的网络中,我们需要一些最基本的组件来查询和更新账本。这些组件<peer节点、ordering节点以及证书管理>是我们网络的基础。而CLI容器则用来发送一些管理命令。有个简单的脚本将下载并启动这个测试网络。
  2. 学习应用程序中所用到的智能合约例子的参数。
    智能合约包含的各种功能让我们可以用多种方式和账本进行交互。如,我们可以读取整体的数据或者某一部分详尽的数据。
  3. 开发能够查询以及更新记录的应用程序。
    我们提供两个程序例子 —— 一个用于查询账本,另一个用户更新账本。我们的程序将使用SDK APIs来和网络进行交互,并最终调用这些功能。

好了,废话不多说,正文开始

1、首先,启动测试网络

上节我们已经下载了的fabric中已经包含了fabric-samples,执行下面操作

cd fabric-samples/fabcar ; ls

可以看到fabcar样例包含的脚本和程序代码

2、下载并解压Hyperledger Fabric Docker images

下载镜像

./startFabric.sh

执行过程如下:

halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar$ ./startFabric.sh 
Stopping for channel 'mychannel' with CLI timeout of '10' seconds and CLI delay of '3' seconds
proceeding ...
WARNING: The BYFN_CA2_PRIVATE_KEY variable is not set. Defaulting to a blank string.
WARNING: The BYFN_CA1_PRIVATE_KEY variable is not set. Defaulting to a blank string.
Stopping cli                    ... done
Stopping peer0.org2.example.com ... done
Stopping orderer.example.com    ... done
Stopping peer1.org2.example.com ... done
Stopping peer1.org1.example.com ... done
Stopping peer0.org1.example.com ... done
Removing cli                    ... done
Removing peer0.org2.example.com ... done
Removing orderer.example.com    ... done
Removing peer1.org2.example.com ... done
Removing peer1.org1.example.com ... done
Removing peer0.org1.example.com ... done
Removing network net_byfn
Removing volume net_peer0.org3.example.com
WARNING: Volume net_peer0.org3.example.com not found.
Removing volume net_peer1.org3.example.com
WARNING: Volume net_peer1.org3.example.com not found.
Removing volume net_orderer2.example.com
WARNING: Volume net_orderer2.example.com not found.
Removing volume net_orderer.example.com
Removing volume net_peer0.org2.example.com
Removing volume net_peer0.org1.example.com
Removing volume net_peer1.org1.example.com
Removing volume net_peer1.org2.example.com
Removing volume net_orderer5.example.com
WARNING: Volume net_orderer5.example.com not found.
Removing volume net_orderer4.example.com
WARNING: Volume net_orderer4.example.com not found.
Removing volume net_orderer3.example.com
WARNING: Volume net_orderer3.example.com not found.
57da35e62075
7727893af4a0
b1fab6da4a8c
Untagged: dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab:latest
Deleted: sha256:a361bdbae52fd3897a325606f2c3f1d7a23fb737d8b12f987a0d927f1e7ddfb2
Deleted: sha256:41880d8787311ec41d2162219382b44ce4c656513c4d8cc5ea8bdb6a1379c51f
Deleted: sha256:d8c907db9d43ebefb2f1c76d89ad11546ca186b55bd3f1cb2ed9ec99eed2d26e
Deleted: sha256:2c4c8355d0acd14eff6fa105568de64b27a9b5cce3523c31a57a32a1afa28891
Untagged: dev-peer0.org1.example.com-mycc-1.0-384f11f484b9302df90b453200cfb25174305fce8f53f4e94d45ee3b6cab0ce9:latest
Deleted: sha256:3425e36582cdcb3d0da871f71976622ab353b9c8a512e0555a73b790bec1ee22
Deleted: sha256:e98f3c309f14bc6306be86a137e11166ee83abdd96ecac89698e658f225be899
Deleted: sha256:cbfe5ddde265d8294e77bdde9314e9bb6a384dae9085922ecab85cb6ceae4547
Deleted: sha256:e962aafc8595469a2b3ec55f05e3e042b0bda3dca52f1e600d0f7311c21f4732
Untagged: dev-peer0.org2.example.com-mycc-1.0-15b571b3ce849066b7ec74497da3b27e54e0df1345daff3951b94245ce09c42b:latest
Deleted: sha256:714e8f05a4ffaf0f544cc710e865022e8c513e95c060a55eacb7a2b818540c64
Deleted: sha256:0a43b52a03c8d649886ad62bb923494c3fc9c7badf65977d816bd7994ecebb0c
Deleted: sha256:2d4758a2f6ea8c70a747bf7b4beb31b199b05f2b7f09ddef3a097e2844151f41
Deleted: sha256:1fae417e6afb0a2e4178d2b43ab673997d66cad0cb55ced26b07e092b5ec8642

Starting for channel 'mychannel' with CLI timeout of '10' seconds and CLI delay of '3' seconds and using database 'couchdb'
proceeding ...
./byfn.sh: line 124: configtxlator: command not found
LOCAL_VERSION=
DOCKER_IMAGE_VERSION=1.4.3
=================== WARNING ===================
  Local fabric binaries and docker images are  
  out of  sync. This may cause problems.       
===============================================
/home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/../bin/cryptogen

##########################################################
##### Generate certificates using cryptogen tool #########
##########################################################
+ cryptogen generate --config=./crypto-config.yaml
org1.example.com
org2.example.com
+ res=0
+ set +x

Generate CCP files for Org1 and Org2
/home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/../bin/configtxgen
##########################################################
######### Generating Orderer Genesis block ##############
##########################################################
CONSENSUS_TYPE=solo
+ '[' solo == solo ']'
+ configtxgen -profile TwoOrgsOrdererGenesis -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block
2020-09-25 10:38:25.424 CST [common.tools.configtxgen] main -> INFO 001 Loading configuration
2020-09-25 10:38:25.638 CST [common.tools.configtxgen.localconfig] completeInitialization -> INFO 002 orderer type: solo
2020-09-25 10:38:25.638 CST [common.tools.configtxgen.localconfig] Load -> INFO 003 Loaded configuration: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/configtx.yaml
2020-09-25 10:38:25.768 CST [common.tools.configtxgen.localconfig] completeInitialization -> INFO 004 orderer type: solo
2020-09-25 10:38:25.768 CST [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 005 Loaded configuration: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/configtx.yaml
2020-09-25 10:38:25.770 CST [common.tools.configtxgen] doOutputBlock -> INFO 006 Generating genesis block
2020-09-25 10:38:25.771 CST [common.tools.configtxgen] doOutputBlock -> INFO 007 Writing genesis block
+ res=0
+ set +x

#################################################################
### Generating channel configuration transaction 'channel.tx' ###
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel
2020-09-25 10:38:25.815 CST [common.tools.configtxgen] main -> INFO 001 Loading configuration
2020-09-25 10:38:25.953 CST [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/configtx.yaml
2020-09-25 10:38:26.139 CST [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2020-09-25 10:38:26.139 CST [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/configtx.yaml
2020-09-25 10:38:26.139 CST [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 005 Generating new channel configtx
2020-09-25 10:38:26.141 CST [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 006 Writing new channel tx
+ res=0
+ set +x

#################################################################
####### Generating anchor peer update for Org1MSP ##########
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP
2020-09-25 10:38:26.188 CST [common.tools.configtxgen] main -> INFO 001 Loading configuration
2020-09-25 10:38:26.322 CST [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/configtx.yaml
2020-09-25 10:38:26.451 CST [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2020-09-25 10:38:26.451 CST [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/configtx.yaml
2020-09-25 10:38:26.451 CST [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2020-09-25 10:38:26.452 CST [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
+ res=0
+ set +x

#################################################################
####### Generating anchor peer update for Org2MSP ##########
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP
2020-09-25 10:38:26.491 CST [common.tools.configtxgen] main -> INFO 001 Loading configuration
2020-09-25 10:38:26.632 CST [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/configtx.yaml
2020-09-25 10:38:26.767 CST [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2020-09-25 10:38:26.767 CST [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network/configtx.yaml
2020-09-25 10:38:26.767 CST [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2020-09-25 10:38:26.767 CST [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
+ res=0
+ set +x

Creating network "net_byfn" with the default driver
Creating volume "net_peer0.org2.example.com" with default driver
Creating volume "net_peer1.org2.example.com" with default driver
Creating volume "net_peer1.org1.example.com" with default driver
Creating volume "net_peer0.org1.example.com" with default driver
Creating volume "net_orderer.example.com" with default driver
Creating couchdb1 ... 
Creating ca_peerOrg1 ... 
Creating ca_peerOrg2 ... 
Creating couchdb2 ... 
Creating couchdb3 ... 
Creating orderer.example.com ... 
Creating couchdb0 ... 
Creating couchdb1
Creating ca_peerOrg1
Creating orderer.example.com
Creating ca_peerOrg2
Creating couchdb0
Creating couchdb3
Creating couchdb3 ... done
Creating couchdb1 ... done
Creating peer0.org2.example.com ... 
Creating peer1.org2.example.com ... 
Creating peer1.org1.example.com ... 
Creating peer0.org2.example.com
Creating peer0.org1.example.com
Creating peer1.org2.example.com
Creating peer1.org1.example.com ... done
Creating cli ... 
Creating cli ... done
CONTAINER ID        IMAGE                               COMMAND                  CREATED             STATUS                  PORTS                                        NAMES
25db1f49bd0d        hyperledger/fabric-tools:latest     "/bin/bash"              3 seconds ago       Up Less than a second                                                cli
ffa08fe12f45        hyperledger/fabric-peer:latest      "peer node start"        10 seconds ago      Up 3 seconds            0.0.0.0:10051->10051/tcp                     peer1.org2.example.com
db986456e3f1        hyperledger/fabric-peer:latest      "peer node start"        10 seconds ago      Up 3 seconds            0.0.0.0:8051->8051/tcp                       peer1.org1.example.com
6b58e2f9f0fd        hyperledger/fabric-peer:latest      "peer node start"        10 seconds ago      Up 3 seconds            0.0.0.0:7051->7051/tcp                       peer0.org1.example.com
99f1a6861f29        hyperledger/fabric-peer:latest      "peer node start"        10 seconds ago      Up 5 seconds            0.0.0.0:9051->9051/tcp                       peer0.org2.example.com
083ad91dc52d        hyperledger/fabric-couchdb          "tini -- /docker-ent…"   16 seconds ago      Up 10 seconds           4369/tcp, 9100/tcp, 0.0.0.0:7984->5984/tcp   couchdb2
278bd416710a        hyperledger/fabric-couchdb          "tini -- /docker-ent…"   16 seconds ago      Up 10 seconds           4369/tcp, 9100/tcp, 0.0.0.0:5984->5984/tcp   couchdb0
e027cdca24ae        hyperledger/fabric-ca:latest        "sh -c 'fabric-ca-se…"   16 seconds ago      Up 10 seconds           7054/tcp, 0.0.0.0:8054->8054/tcp             ca_peerOrg2
40ba0e78f3a2        hyperledger/fabric-couchdb          "tini -- /docker-ent…"   16 seconds ago      Up 10 seconds           4369/tcp, 9100/tcp, 0.0.0.0:8984->5984/tcp   couchdb3
302ff3534644        hyperledger/fabric-orderer:latest   "orderer"                16 seconds ago      Up 10 seconds           0.0.0.0:7050->7050/tcp                       orderer.example.com
6875e13d9078        hyperledger/fabric-ca:latest        "sh -c 'fabric-ca-se…"   16 seconds ago      Up 10 seconds           0.0.0.0:7054->7054/tcp                       ca_peerOrg1
dfa539ffb0b5        hyperledger/fabric-couchdb          "tini -- /docker-ent…"   16 seconds ago      Up 10 seconds           4369/tcp, 9100/tcp, 0.0.0.0:6984->5984/tcp   couchdb1

 ____    _____      _      ____    _____ 
/ ___|  |_   _|    / \    |  _ \  |_   _|
\___ \    | |     / _ \   | |_) |   | |  
 ___) |   | |    / ___ \  |  _ <    | |  
|____/    |_|   /_/   \_\ |_| \_\   |_|  

Build your first network (BYFN) end-to-end test

Channel name : mychannel
Creating channel...
+ peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2020-09-25 02:38:45.449 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-09-25 02:38:45.677 UTC [cli.common] readBlock -> INFO 002 Got status: &{ NOT_FOUND}
2020-09-25 02:38:45.679 UTC [channelCmd] InitCmdFactory -> INFO 003 Endorser and orderer connections initialized
2020-09-25 02:38:45.916 UTC [cli.common] readBlock -> INFO 004 Received block: 0
===================== Channel 'mychannel' created ===================== 

Having all peers join the channel...
+ peer channel join -b mychannel.block
+ res=1
+ set +x
Error: error getting endorser client for channel: endorser client failed to connect to peer0.org1.example.com:7051: failed to create new connection: connection error: desc = "transport: error while dialing: dial tcp 172.20.0.11:7051: connect: connection refused"
peer0.org1 failed to join the channel, Retry after 3 seconds
+ peer channel join -b mychannel.block
+ res=1
+ set +x
Error: error getting endorser client for channel: endorser client failed to connect to peer0.org1.example.com:7051: failed to create new connection: connection error: desc = "transport: error while dialing: dial tcp 172.20.0.11:7051: connect: connection refused"
peer0.org1 failed to join the channel, Retry after 3 seconds
+ peer channel join -b mychannel.block
+ res=0
+ set +x
2020-09-25 02:38:52.604 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-09-25 02:38:53.046 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer0.org1 joined channel 'mychannel' ===================== 

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2020-09-25 02:38:56.141 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-09-25 02:38:56.324 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer1.org1 joined channel 'mychannel' ===================== 

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2020-09-25 02:38:59.461 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-09-25 02:38:59.669 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer0.org2 joined channel 'mychannel' ===================== 

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2020-09-25 02:39:02.781 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-09-25 02:39:03.110 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer1.org2 joined channel 'mychannel' ===================== 

Updating anchor peers for org1...
+ peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2020-09-25 02:39:06.210 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-09-25 02:39:06.392 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
===================== Anchor peers updated for org 'Org1MSP' on channel 'mychannel' ===================== 

Updating anchor peers for org2...
+ peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org2MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2020-09-25 02:39:09.469 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-09-25 02:39:09.496 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
===================== Anchor peers updated for org 'Org2MSP' on channel 'mychannel' ===================== 


========= All GOOD, BYFN execution completed =========== 


 _____   _   _   ____   
| ____| | \ | | |  _ \  
|  _|   |  \| | | | | | 
| |___  | |\  | | |_| | 
|_____| |_| \_| |____/  

+ echo 'Installing smart contract on peer0.org1.example.com'
Installing smart contract on peer0.org1.example.com
+ docker exec -e CORE_PEER_LOCALMSPID=Org1MSP -e CORE_PEER_ADDRESS=peer0.org1.example.com:7051 -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp -e 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 cli peer chaincode install -n fabcar -v 1.0 -p github.com/chaincode/fabcar/go -l golang
2020-09-25 02:39:13.810 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2020-09-25 02:39:13.810 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2020-09-25 02:39:16.156 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" > 
+ echo 'Installing smart contract on peer1.org1.example.com'
Installing smart contract on peer1.org1.example.com
+ docker exec -e CORE_PEER_LOCALMSPID=Org1MSP -e CORE_PEER_ADDRESS=peer1.org1.example.com:8051 -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp -e 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 cli peer chaincode install -n fabcar -v 1.0 -p github.com/chaincode/fabcar/go -l golang
2020-09-25 02:39:16.543 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2020-09-25 02:39:16.543 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2020-09-25 02:39:16.793 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" > 
+ echo 'Installing smart contract on peer0.org2.example.com'
Installing smart contract on peer0.org2.example.com
+ docker exec -e CORE_PEER_LOCALMSPID=Org2MSP -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e 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 cli peer chaincode install -n fabcar -v 1.0 -p github.com/chaincode/fabcar/go -l golang
2020-09-25 02:39:17.188 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2020-09-25 02:39:17.188 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2020-09-25 02:39:17.458 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" > 
+ echo 'Installing smart contract on peer1.org2.example.com'
Installing smart contract on peer1.org2.example.com
+ docker exec -e CORE_PEER_LOCALMSPID=Org2MSP -e CORE_PEER_ADDRESS=peer1.org2.example.com:10051 -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e 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 cli peer chaincode install -n fabcar -v 1.0 -p github.com/chaincode/fabcar/go -l golang
2020-09-25 02:39:17.774 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2020-09-25 02:39:17.774 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2020-09-25 02:39:18.016 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" > 
+ echo 'Instantiating smart contract on mychannel'
Instantiating smart contract on mychannel
+ docker exec -e CORE_PEER_LOCALMSPID=Org1MSP -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -l golang -v 1.0 -c '{"Args":[]}' -P 'AND('\''Org1MSP.member'\'','\''Org2MSP.member'\'')' --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt 2020-09-25 02:39:18.397 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc 2020-09-25 02:39:18.398 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc + echo 'Waiting for instantiation request to be committed ...' Waiting for instantiation request to be committed ... + sleep 10 + echo 'Submitting initLedger transaction to smart contract on mychannel' Submitting initLedger transaction to smart contract on mychannel + echo 'The transaction is sent to all of the peers so that chaincode is built before receiving the following requests' The transaction is sent to all of the peers so that chaincode is built before receiving the following requests + docker exec -e CORE_PEER_LOCALMSPID=Org1MSP -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{ "function":"initLedger","Args":[]}' --waitForEvent --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --peerAddresses peer1.org1.example.com:8051 --peerAddresses peer0.org2.example.com:9051 --peerAddresses peer1.org2.example.com:10051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt 2020-09-25 02:41:52.030 UTC [chaincodeCmd] ClientWait -> INFO 002 txid [526e4e9e4df22f304b49bbe0e93b8cdd0b523afc273d7a5277f8ca7e0087e7b2] committed with status (VALID) at peer1.org1.example.com:8051 2020-09-25 02:41:51.979 UTC [chaincodeCmd] ClientWait -> INFO 003 txid [526e4e9e4df22f304b49bbe0e93b8cdd0b523afc273d7a5277f8ca7e0087e7b2] committed with status (VALID) at peer1.org2.example.com:10051 2020-09-25 02:41:51.983 UTC [chaincodeCmd] ClientWait -> INFO 001 txid [526e4e9e4df22f304b49bbe0e93b8cdd0b523afc273d7a5277f8ca7e0087e7b2] committed with status (VALID) at peer0.org2.example.com:9051 2020-09-25 02:41:52.061 UTC [chaincodeCmd] ClientWait -> INFO 004 txid [526e4e9e4df22f304b49bbe0e93b8cdd0b523afc273d7a5277f8ca7e0087e7b2] committed with status (VALID) at peer0.org1.example.com:7051 2020-09-25 02:41:52.100 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 005 Chaincode invoke successful. result: status:200 + set +x Total setup execution time : 225 secs ... Next, use the FabCar applications to interact with the deployed FabCar contract. The FabCar applications are available in multiple programming languages. Follow the instructions for the programming language of your choice: JavaScript: Start by changing into the "javascript" directory: cd javascript Next, install all required packages: npm install Then run the following applications to enroll the admin user, and register a new user called user1 which will be used by the other applications to interact with the deployed FabCar contract: node enrollAdmin node registerUser You can run the invoke application as follows. By default, the invoke application will create a new car, but you can update the application to submit other transactions: node invoke You can run the query application as follows. By default, the query application will return all cars, but you can update the application to evaluate other transactions: node query TypeScript: Start by changing into the "typescript" directory: cd typescript Next, install all required packages: npm install Next, compile the TypeScript code into JavaScript: npm run build Then run the following applications to enroll the admin user, and register a new user called user1 which will be used by the other applications to interact with the deployed FabCar contract: node dist/enrollAdmin node dist/registerUser You can run the invoke application as follows. By default, the invoke application will create a new car, but you can update the application to submit other transactions: node dist/invoke You can run the query application as follows. By default, the query application will return all cars, but you can update the application to evaluate other transactions: node dist/query Java: Start by changing into the "java" directory: cd java Then, install dependencies and run the test using: mvn test The test will invoke the sample client app which perform the following: - Enroll admin and user1 and import them into the wallet (if they don't already exist there)
    - Submit a transaction to create a new car
    - Evaluate a transaction (query) to return details of this car
    - Submit a transaction to change the owner of this car
    - Evaluate a transaction (query) to return the updated details of this car

halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar$ 

分析上面的日志,我们主要做了哪些事情呢?

  • 停止了上次打开的网络
  • 启动peer节点、Ordering节点、证书颁发机构以及CLI容器】
  • 创建一个通道,并将peer加入该通道
  • 将智能合约(即链码)安装到peer节点的文件系统上,并在通道上实例化该链码;实例化会启动链码容器
  • 调用initLedger功能来向通道账本写入10个不同的汽车

最后,还想我们展示了如何来跑这个应用程序 ,以javascript为例,那我们按照命令来试试吧

3、查询账本

查询是指如何从账本中读取数据。您可以查询单个或者多个键的值,如果账本是以类似于JSON这样的数据存储格式写入的,则可以执行更复杂的搜索(如查找包含某些关键字的所有资产)。

1.具体操作,在javascript中执行以下命令

halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript$ npm install
npm WARN fabcar@1.0.0 No repository field.

up to date in 4.15s

4 packages are looking for funding
  run `npm fund` for details

halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript$ node enrollAdmin
Wallet path: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript/wallet
An identity for the admin user "admin" already exists in the wallet
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript$ node registerUser
Wallet path: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript/wallet
Successfully registered and enrolled admin user "user1" and imported it into the wallet
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript$ node invoke
Wallet path: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript/wallet
Transaction has been submitted
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript$ node query
Wallet path: /home/halfape/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is: [{ "Key":"CAR0", "Record":{ "colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},{ "Key":"CAR1", "Record":{ "colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},{ "Key":"CAR12", "Record":{ "colour":"Black","make":"Honda","model":"Accord","owner":"Tom"}},{ "Key":"CAR2", "Record":{ "colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},{ "Key":"CAR3", "Record":{ "colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},{ "Key":"CAR4", "Record":{ "colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},{ "Key":"CAR5", "Record":{ "colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},{ "Key":"CAR6", "Record":{ "colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},{ "Key":"CAR7", "Record":{ "colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},{ "Key":"CAR8", "Record":{ "colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},{ "Key":"CAR9", "Record":{ "colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]

我们可以看到
这里有10辆车,一辆属于Adriana的黑色Tesla Model S、一辆属于Brad的红色Ford Mustang、一辆属于Pari的紫罗兰色Fiat Punto等等。账本是基于Key/Value 的,在这里,关键字是从CAR0到CAR9。这一点特别重要。

2.代码分析

我们可以大佬query.js里面的代码具体做了什么。

const path = require('path');
  
const ccpPath = path.resolve(__dirname, '..', '..', 'first-network', 'connection-org1.json');

async function main() { 
    try { 

        // Create a new file system based wallet for managing identities.
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = new FileSystemWallet(walletPath);
        console.log(`Wallet path: ${ walletPath}`);

        // Check to see if we've already enrolled the user. const userExists = await wallet.exists('user1'); if (!userExists) { console.log('An identity for the user "user1" does not exist in the wallet'); console.log('Run the registerUser.js application before retrying'); return; } // Create a new gateway for connecting to our peer node. const gateway = new Gateway(); await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } }); // Get the network (channel) our contract is deployed to. const network = await gateway.getNetwork('mychannel'); // Get the contract from the network. const contract = network.getContract('fabcar'); // Evaluate the specified transaction. // queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4') // queryAllCars transaction - requires no arguments, ex: ('queryAllCars') const result = await contract.evaluateTransaction('queryAllCars');
        console.log(`Transaction has been evaluated, result is: ${ result.toString()}`);

    } catch (error) { 
        console.error(`Failed to evaluate transaction: ${ error}`);
        process.exit(1);
    }
}

main();

简单分析:

会发现主要做了创建钱包,给钱包中添加用户,获取通道,获取合约,调用合约函数`queryAllCars`的操作完成了查询。

那我们这个查询函数具体实现在哪呢?如下

halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric$ ls
bccsp          CODE_OF_CONDUCT.md  devenv         examples    gotools.mk   LICENSE   peer       release_notes    testingInfo.rst   unit-test
CHANGELOG.md   common              discovery      Gopkg.lock  idemix       Makefile  protos     sampleconfig     test-pyramid.png  vendor
ci.properties  CONTRIBUTING.md     docker-env.mk  Gopkg.toml  images       msp       README.md  scripts          token
cmd            core                docs           gossip      integration  orderer   release    settings.gradle  tox.ini
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric$ cd scripts/
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts$ ls
bootstrap.sh  check_deps.sh     check_spelling.sh        compile_protos.sh  generateHelpDocs.sh  goListFiles.sh  multiarch.sh             run-integration-tests.sh
changelog.sh  check_license.sh  check_trailingspaces.sh  fabric-samples     golinter.sh          metrics_doc.sh  pull_build_artifacts.sh
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts$ cd fabric-samples/
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples$ ls
balance-transfer  bin        chaincode-docker-devmode  CODE_OF_CONDUCT.md  CONTRIBUTING.md  fabcar         high-throughput      Jenkinsfile  MAINTAINERS.md  README.md
basic-network     chaincode  ci.properties             commercial-paper    docs             first-network  interest_rate_swaps  LICENSE      off_chain_data  scripts
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples$ cd chaincode
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode$ ls
abac  chaincode_example02  fabcar  marbles02  marbles02_private  sacc
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode$ cd fabcar/
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode/fabcar$ ls
go  java  javascript  javascript-low-level  typescript
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode/fabcar$ cd javascript
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode/fabcar/javascript$ ls
index.js  lib  package.json
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode/fabcar/javascript$ cd lib/
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode/fabcar/javascript/lib$ ls
fabcar.js
halfape@halfape-VirtualBox:~/go/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode/fabcar/javascript/lib$ 

打开fabric.js,具体代码如下

   

'use strict';

const {  Contract } = require('fabric-contract-api');

class FabCar extends Contract { 

    async initLedger(ctx) { 
        console.info('============= START : Initialize Ledger ===========');
        const cars = [
            { 
                color: 'blue',
                make: 'Toyota',
                model: 'Prius',
                owner: 'Tomoko',
            },
            { 
                color: 'red',
                make: 'Ford',
                model: 'Mustang',
                owner: 'Brad',
            },
            { 
                color: 'green',
                make: 'Hyundai',
                model: 'Tucson',
                owner: 'Jin Soo',
            },
            { 
                color: 'yellow',
                make: 'Volkswagen',
                model: 'Passat',
                owner: 'Max',
            },
            { 
                color: 'black',
                make: 'Tesla',
                model: 'S',
                owner: 'Adriana',
            },
            { 
                color: 'purple',
                make: 'Peugeot',
                model: '205',
                owner: 'Michel',
            },
            { 
                color: 'white',
                make: 'Chery',
                model: 'S22L',
                owner: 'Aarav',
            },
            { 
                color: 'violet',
                make: 'Fiat',
                model: 'Punto',
                owner: 'Pari',
            },
            { 
                color: 'indigo',
                make: 'Tata',
                model: 'Nano',
                owner: 'Valeria',
            },
            { 
                color: 'brown',
                make: 'Holden',
                model: 'Barina',
                owner: 'Shotaro',
            },
        ];

        for (let i = 0; i < cars.length; i++) { 
            cars[i].docType = 'car';
            await ctx.stub.putState('CAR' + i, Buffer.from(JSON.stringify(cars[i])));
            console.info('Added <--> ', cars[i]);
        }
        console.info('============= END : Initialize Ledger ===========');
    }

    async queryCar(ctx, carNumber) { 
        const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state
        if (!carAsBytes || carAsBytes.length === 0) { 
            throw new Error(`${ carNumber} does not exist`);
        }
        console.log(carAsBytes.toString());
        return carAsBytes.toString();
    }

    async createCar(ctx, carNumber, make, model, color, owner) { 
        console.info('============= START : Create Car ===========');

        const car = { 
            color,
            docType: 'car',
            make,
            model,
            owner,
        };

        await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car)));
        console.info('============= END : Create Car ===========');
    }

    async queryAllCars(ctx) { 
        const startKey = 'CAR0';
        const endKey = 'CAR999';

        const iterator = await ctx.stub.getStateByRange(startKey, endKey);

        const allResults = [];
        while (true) { 
            const res = await iterator.next();

            if (res.value && res.value.value.toString()) { 
                console.log(res.value.value.toString('utf8'));

                const Key = res.value.key;
                let Record;
                try { 
                    Record = JSON.parse(res.value.value.toString('utf8'));
                } catch (err) { 
                    console.log(err);
                    Record = res.value.value.toString('utf8');
                }
                allResults.push({  Key, Record });
            }
            if (res.done) { 
                console.log('end of data');
                await iterator.close();
                console.info(allResults);
                return JSON.stringify(allResults);
            }
        }
    }

    async changeCarOwner(ctx, carNumber, newOwner) { 
        console.info('============= START : changeCarOwner ===========');

        const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state
        if (!carAsBytes || carAsBytes.length === 0) { 
            throw new Error(`${ carNumber} does not exist`);
        }
        const car = JSON.parse(carAsBytes.toString());
        car.owner = newOwner;

        await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car)));
        console.info('============= END : changeCarOwner ===========');
    }

}
mdule.exports = FabCar;

会发现,除了我们的queryCar函数 queryAllCars函数,还有createCar函数和changeCarOwner函数。另外还可以看到在初始化的时候出现了10辆汽车的原因。

queryAllCars中有这么一段

const startKey = 'CAR0';
        const endKey = 'CAR999';
        const iterator = await ctx.stub.getStateByRange(startKey, endKey);

该函数调用shim接口函数GetStateByRange来返回参数在startKey和endKey间的账本数据。这两个键值分别定义为CAR0和CAR999。因此,我们理论上可以创建1,000辆汽车(假设Keys都被正确使用),queryAllCars函数将会显示出每一辆汽车的信息。

既然看懂了代码,我们来做点小改动,回到刚才的query.js,编辑如下:

const result = await contract.evaluateTransaction('queryCar','CAR4');

然后再来执行,应当只查出CAR4的信息,我们来看看吧

果然结果也是对的。因为车辆信息是从0开始的,所以这里是黑色特斯拉。

4、更新账本

我们来看看另外一个独立的用于交易的程序invoke.js,进行打开



'use strict';

const {  FileSystemWallet, Gateway } = require('fabric-network');
const path = require('path');

const ccpPath = path.resolve(__dirname, '..', '..', 'first-network', 'connection-org1.json');

const ccpPath = path.resolve(__dirname, '..', '..', 'first-network', 'connection-org1.json');

async function main() { 
    try { 

        // Create a new file system based wallet for managing identities.
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = new FileSystemWallet(walletPath);
        console.log(`Wallet path: ${ walletPath}`);

        // Check to see if we've already enrolled the user. const userExists = await wallet.exists('user1'); if (!userExists) { console.log('An identity for the user "user1" does not exist in the wallet'); console.log('Run the registerUser.js application before retrying'); return; } // Create a new gateway for connecting to our peer node. const gateway = new Gateway(); await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } }); // Get the network (channel) our contract is deployed to. const network = await gateway.getNetwork('mychannel'); // Get the contract from the network. const contract = network.getContract('fabcar'); // Submit the specified transaction. // createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom') // changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave') await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom'); console.log('Transaction has been submitted');

        // Disconnect from the gateway.
        await gateway.disconnect();

    } catch (error) { 
        console.error(`Failed to submit transaction: ${ error}`);
        process.exit(1);
    }
}

main();
简单分析会发现主要做了创建钱包,给钱包中添加用户,调用合约函数`createCar`的操作完成了提交交易的操作。

另外我们做一个简单的修改,然后执行node invoke.js,会显示交易已经被提交。

await contract.submitTransaction('createCar', 'CAR10', 'Aodi', 'Accord', 'Blue', 'halfape');

此时,我们在vim query.js,修改为查询CAR10信息,执行node query.js,则会显示以下信息。

还有一个chanegCarOwner,大家可以自己试试哈。

Fabric交互应用程序原理分析

下图演示了一个应用程序如何在链码(智能合约)中调用不同功能。

从上图以及上面的例子就可以看出:应用程序通过智能合约(在Fabric中叫做链码)从区块链上获取信息或者更新信息。

在来看一张图,展示了应用程序是如何我们的Fabric网络进行交互的。

相信跟着我一步步调试到这里,你已经对Fabric又有了新的认识,对区块链又有了新的认识。Say Goodbye~

参考文章:

https://blog.csdn.net/dalang1010/article/details/78940200
https://cn.hyperledger.org/
https://hyperledgercn.github.io/hyperledgerDocs/build_network_zh/

写在最后

最后,还是得感谢众多前辈所给的案例以及亲身踩过的坑,我才得以成功测试这个小应用。如果你觉得这篇文章对你有所帮助,关注微信公众号半路猿,拉你进我们的学习交流群,一起学习,一同成长。

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服