上一章我们启动了区块链所需要的所有节点,接下来我们将开始部署我们的区块链。
创建Channel
之前,我们使用configtxgen工具生成了配置交易channel.tx,我们将会传递这个配置交易到orderer作为创建channel请求的一部分,现在进入cli节点容器:
docker exec -it cli /bin/bash
创建channel:
export CHANNEL_NAME=mychannel
peer channel create -o orderer0.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/channel.tx --tls $CORE_PEER_TLS_ENABLED --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
输出结果:
我理解此命令会在orderer创建channel,并生成一个创世块
注意,因为我们有3个orderer节点,所以还需要对orderer1和orderer2节点用上面的命令创建channel。
加入Channel
上一步我们在orderer中根据channel.tx配置文件创建了我们的channel,接下来我们需要把每个peer节点加入到channel中:
peer channel join -b mychannel.block
2017-12-21 02:09:15.225 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2017-12-21 02:09:15.225 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2017-12-21 02:09:15.234 UTC [channelCmd] InitCmdFactory -> INFO 003 Endorser and orderer connections initialized
2017-12-21 02:09:15.235 UTC [msp/identity] Sign -> DEBU 004 Sign: plaintext: 0A9F070A5B08011A0B08CBAFECD10510...C6B619CDF6DA1A080A000A000A000A00
2017-12-21 02:09:15.235 UTC [msp/identity] Sign -> DEBU 005 Sign: digest: 796A612EDDAC69FAF3E174FA4EE08302E19FE0916BCD63688BB041B9B859C39E
2017-12-21 02:09:15.348 UTC [channelCmd] executeJoin -> INFO 006 Peer joined the channel!
2017-12-21 02:09:15.348 UTC [main] main -> INFO 007 Exiting.....
因为目前我们用的cli容器,环境变量指定的是peer0.org1节点,所以我们还需要修改环境变量,让所有其他的peer节点容器都加入channel,要注意,切换到org2的时候对应的所有证书环境变量都要换,不然会报证书验证的错误:
export CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.crt
export CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/server.key
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=peer1.org1.example.com:7051
peer channel join -b mychannel.block
export CORE_PEER_LOCALMSPID=Org2MSP
export CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.crt
export CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.key
export 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
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=peer0.org2.example.com:7051
peer channel join -b mychannel.block
export CORE_PEER_ADDRESS=peer1.org2.example.com:7051
export CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/server.crt
export CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/server.key
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=peer1.org2.example.com:7051
peer channel join -b mychannel.block
现在,orderer节点已经创建了对应的channel,并且所有的peer节点都加入了该channel。
记得再把环境变量全部切回org1~
export CORE_PEER_LOCALMSPID=Org1MSP
export CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
export CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
export 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
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=peer0.org1.example.com:7051
更新AnchorPeer
我们通过传递之前生产的Org1MSPanchor.tx和Org2MSPanchor.tx以及channel的名字到orderer来把channel信息更新到每个anchorPeer上:
export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
peer channel update -o orderer0.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/${CORE_PEER_LOCALMSPID}anchors.tx --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA
像之前那样切到org2的环境变量,然后再执行一遍上面的代码,再把环境切回org1。
安装chaincode
区块链上唯一的业务逻辑就是chaincode了,他是我们业务的核心部分,想要在区块链上运行我们自己的业务,就需要把chaincode安装到每个peer节点:
peer chaincode install -n mycc -v 1.0 -p github.com/chaincode/chaincode_example02/go/
mycc是链码的名字,1.0是版本号,后面的是链码文件夹所在路径。
2017-12-21 06:09:42.465 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2017-12-21 06:09:42.465 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2017-12-21 06:09:42.465 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2017-12-21 06:09:42.465 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2017-12-21 06:09:42.465 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2017-12-21 06:09:42.515 UTC [golang-platform] getCodeFromFS -> DEBU 006 getCodeFromFS github.com/chaincode/chaincode_example02/go/
2017-12-21 06:09:42.912 UTC [golang-platform] func1 -> DEBU 007 Discarding GOROOT package fmt
2017-12-21 06:09:42.912 UTC [golang-platform] func1 -> DEBU 008 Discarding provided package github.com/hyperledger/fabric/core/chaincode/shim
2017-12-21 06:09:42.912 UTC [golang-platform] func1 -> DEBU 009 Discarding provided package github.com/hyperledger/fabric/protos/peer
2017-12-21 06:09:42.912 UTC [golang-platform] func1 -> DEBU 00a Discarding GOROOT package strconv
2017-12-21 06:09:42.915 UTC [golang-platform] GetDeploymentPayload -> DEBU 00b done
2017-12-21 06:09:42.921 UTC [msp/identity] Sign -> DEBU 00c Sign: plaintext: 0AA0070A5C08031A0C08A6A0EDD10510...F619FF0E0000FFFFACD4020D001C0000
2017-12-21 06:09:42.921 UTC [msp/identity] Sign -> DEBU 00d Sign: digest: F65DC46F817A94C31FC270CB0B7C1ECDEA662B5B421C0F383A4FED0D9B4BBC0E
2017-12-21 06:09:42.927 UTC [chaincodeCmd] install -> DEBU 00e Installed remotely response:
2017-12-21 06:09:42.927 UTC [main] main -> INFO 00f Exiting.....
接下来修改环境变量在其他所有Peer节点都安装上该链码。
实例化chaincode
接下来,我们需要在channel上实例化chaincode,我们只需要在一个peer节点上实例化channel即可:
peer chaincode instantiate -o orderer0.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
这里-c是实例化的参数,-P是指定背书策略,示例中的策略是Org1和Org2任何一个member节点验证过就可以通过。
2017-12-21 08:11:34.074 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2017-12-21 08:11:34.075 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2017-12-21 08:11:34.085 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2017-12-21 08:11:34.086 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2017-12-21 08:11:34.087 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2017-12-21 08:11:34.088 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0AAA070A6608031A0B08B6D9EDD10510...324D53500A04657363630A0476736363
2017-12-21 08:11:34.088 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: 97DBB3BDAD5AEC4690D45965199DA60ED7134CFF13B4F272C50980DEDBF4987A
2017-12-21 08:11:34.905 UTC [msp/identity] Sign -> DEBU 008 Sign: plaintext: 0AAA070A6608031A0B08B6D9EDD10510...BE5D7377C426346015BE844429A8593E
2017-12-21 08:11:34.905 UTC [msp/identity] Sign -> DEBU 009 Sign: digest: EA1AACD22982F05403BFBF5FBC088B332EF409D3B3C6C5BFA0CFBE8B42CD10B1
该操作会在docker新创建一个容器:dev-peer0.org1.example.com-mycc-1.0,以后对于我们在链上的操作,如果是那个节点第一次访问,都将创建一个自己的新容器。
查询Query
现在我们可以在区块链操作一些业务了,比如查询:
peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'
2017-12-21 08:36:27.925 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2017-12-21 08:36:27.925 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2017-12-21 08:36:27.925 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2017-12-21 08:36:27.926 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2017-12-21 08:36:27.926 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2017-12-21 08:36:27.927 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0AAB070A6708031A0C088BE5EDD10510...6D7963631A0A0A0571756572790A0161
2017-12-21 08:36:27.927 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: 9A8CC8B5A4EAAB6027E7365335CAFEA0E2B5B16BBBC8618AD7833EE4615ACED3
Query Result: 100
2017-12-21 08:36:27.959 UTC [main] main -> INFO 008 Exiting.....
参数和方法名是chaincode中定义的,Query Result: 100是返回的结果。
同理,我们可以连接到任何peer节点查询,得到相同的结果。
调用Invoke
调用和查询有点区别,查询不需要访问orderer节点和其他peer节点,只需要查询当前peer结点账本中的信息即可返回给客户端,不需要修改账本,效率很高。但调用是需要修改账本数据的,所以需要其他节点的验证,以及orderer节点的共识入链,性能会低一些:
peer chaincode invoke -o orderer0.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc -c '{"Args":["invoke","a","b","10"]}'
2017-12-21 08:42:21.721 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2017-12-21 08:42:21.721 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2017-12-21 08:42:21.731 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2017-12-21 08:42:21.731 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2017-12-21 08:42:21.731 UTC [chaincodeCmd] getChaincodeSpec -> DEBU 005 java chaincode disabled
2017-12-21 08:42:21.732 UTC [msp/identity] Sign -> DEBU 006 Sign: plaintext: 0AAB070A6708031A0C08EDE7EDD10510...696E766F6B650A01610A01620A023130
2017-12-21 08:42:21.732 UTC [msp/identity] Sign -> DEBU 007 Sign: digest: 4294413C4D0C3EF5F5FA1926E148847AADD9B92FA8CB5748ED97F22265820307
2017-12-21 08:42:21.787 UTC [msp/identity] Sign -> DEBU 008 Sign: plaintext: 0AAB070A6708031A0C08EDE7EDD10510...1C2479651CD96F9DEA3357AF48E207AE
2017-12-21 08:42:21.787 UTC [msp/identity] Sign -> DEBU 009 Sign: digest: 8BB4476D2D9C834890EB10420952A76C8FA6EAD91F11D567334CC25EF9DCA232
2017-12-21 08:42:21.789 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> DEBU 00a ESCC invoke result: version:1 response: payload:"\n \250v(\333+\220m@R\260\026\254\215YH\334\010\353\233\261\202\034\0261\263r\201\251\215\035i\345\022Y\nE\022\024\n\004lscc\022\014\n\n\n\004mycc\022\002\010\003\022-\n\004mycc\022%\n\007\n\001a\022\002\010\003\n\007\n\001b\022\002\010\003\032\007\n\001a\032\00290\032\010\n\001b\032\003210\032\003\010\310\001\"\013\022\004mycc\032\0031.0" endorsement:
2017-12-21 08:42:21.789 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 00b Chaincode invoke successful. result: status:200
2017-12-21 08:42:21.790 UTC [main] main -> INFO 00c Exiting.....
通过下面的命令,能查到对应操作的日志:
$ docker logs dev-peer0.org1.example.com-mycc-1.0
ex02 Init
Aval = 100, Bval = 200
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}
ex02 Invoke
Aval = 90, Bval = 210
总结
为了在账本上成功的执行读写操作,chaincode必须安装在peer上。另外,chaincode容器直到实例化或者传统交易-读写执行的时候(例:查询a账户的值),chaincode容器才会启动。channel中的每个节点都维护了账本的完全复制,存储了不可改变的、序列化的记录区块以及state database用于保存当前的fabric状态。即便是那些在最初没有安装chaincode的节点,在安装chaincode之后,也会自动同步账本。