總目錄:
(0) 如何利用區塊鏈保護知識產權
(一)HyperLedger Fabric 2.0-release測試網絡部署
(二)Fabric2.0 first-network 生成配置說明
(三)Fabric2.0啓動網絡腳本配置剖析
(四)Fabric2.0通道實踐
(五)Fabric2.0 智能合約實踐- 安裝以及定義智能合約
(六)Fabric2.0 智能合約實踐- 升級智能合約
(七)Fabric2.0智能合約實踐-設置背書策略
(八)Fabric2.0Java SDK實踐-合約交易
(九)Fabric2.0 通道實踐-更新通道配置
(十)Fabric2.0-動態添加組織
(十一) Fabric2.0-使用編輯器調試go智能合約
(十二)Fabric2.0-實現外部構建啓動合約
工具人大膽試探raft共識-你沒見過的raft算法解釋
Fabric2.0的重大變化之一就是支持外部構建智能合約,在1.x版本的Fabric中,合約是由Peer以容器的方式進行啓動和維護,依賴於Docker。這在一定程度上違反了安全準則,並且在管理運維中帶來了麻煩。Fabric 2.0支持用戶自行啓動合約容器。
文章目錄
1. 合約外部構建器啓動器介紹
要實現不通過Peer直接構建合約,需要適用的工具就是外部構建器和啓動器,外部構建器和啓動器按照官方的說法是基於buildpacks實現將源代碼轉化成可執行程序的工具。構建器負責準備要執行的應用程序,啓動器負責啓動應用程序,並定期調用運行狀況檢查以監視啓動的應用程序的活躍性。
1.1 構建器與啓動器組成
外部構建器和啓動器由四個程序或腳本組成:
• bin/detect:確定是否應使用此buildpack來構建chaincode程序包並啓動它。
• bin/build:將chaincode包轉換爲可執行的chaincode。
• bin/release (可選):向peer提供有關鏈碼的元數據。
• bin/run (可選):運行鏈碼。
到這裏大家瞭解這幾個腳本是什麼用處的就好,我們在後面實踐過程中會逐步解釋。
2 外部構建啓動合約
2.1 環境準備
系統工具 | 版本 | 備註 |
---|---|---|
CentOS | 7 | |
Docker | 18.09.4 | |
Docker-compose | 1.23.2 | 參考:CentOS7安裝Docker-compose推薦方案 |
GO | 1.13.4 | 參考:CentOS7安裝Go |
2.2 構建器啓動器腳本準備
根據上面對構建器啓動器的介紹,我們可以只需要創建bin/detect、bin/build、bin/release
三個腳本。
首先我們在 ~/first-network 下新建一個external/bin目錄用於存放上面三個腳本
cd ~/first-network
mkdir external && mkdir external/bin
2.2.1 檢測腳本
檢測腳本爲bin/detect
,它用於檢測當前install的合約是否適用外部構建器構建,假如exit 0
就會繼續執行構建發佈外部合約的操作,exit 1
就會按照原本適用節點安裝部署合約的方式。
該腳本是自定義的,我這邊提供一個模板
#!/bin/sh
# The bin/detect script is responsible for determining whether or not a buildpack
# should be used to build a chaincode package and launch it.
#
# The peer invokes detect with two arguments:
# bin/detect CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR
#
# When detect is invoked, CHAINCODE_SOURCE_DIR contains the chaincode source and
# CHAINCODE_METADATA_DIR contains the metadata.json file from the chaincode package installed to the peer.
# The CHAINCODE_SOURCE_DIR and CHAINCODE_METADATA_DIR should be treated as read only inputs.
# If the buildpack should be applied to the chaincode source package, detect must return an exit code of 0;
# any other exit code will indicate that the buildpack should not be applied.
CHAINCODE_METADATA_DIR="$2"
set -euo pipefail
# use jq to extract the chaincode type from metadata.json and exit with
# success if the chaincode type is golang
# 判斷目錄下有沒有metadata.json文件 有就exit 0
if [ "$(cat "$CHAINCODE_METADATA_DIR/metadata.json" | sed -e 's/[{}]/''/g' | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/'type'\042/){print $(i+1)}}}' | tr -d '"')" = "external" ]; then
exit 0
fi
exit 1
這裏跟官方模板不一樣在於,適用 sh
執行腳本,適用sed
替換jq,儘量少安裝東西到節點容器。
2.2.2 構建腳本
構建腳本bin/build
,是將我們提交的合約配置文件按照構建規則放到它指定的目錄。
#!/bin/sh
# The bin/build script is responsible for building, compiling, or transforming the contents
# of a chaincode package into artifacts that can be used by release and run.
#
# The peer invokes build with three arguments:
# bin/build CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR BUILD_OUTPUT_DIR
#
# When build is invoked, CHAINCODE_SOURCE_DIR contains the chaincode source and
# CHAINCODE_METADATA_DIR contains the metadata.json file from the chaincode package installed to the peer.
# BUILD_OUTPUT_DIR is the directory where build must place artifacts needed by release and run.
# The build script should treat the input directories CHAINCODE_SOURCE_DIR and
# CHAINCODE_METADATA_DIR as read only, but the BUILD_OUTPUT_DIR is writeable.
CHAINCODE_SOURCE_DIR="$1"
CHAINCODE_METADATA_DIR="$2"
BUILD_OUTPUT_DIR="$3"
set -euo pipefail
#external chaincodes expect connection.json file in the chaincode package
if [ ! -f "$CHAINCODE_SOURCE_DIR/connection.json" ]; then
>&2 echo "$CHAINCODE_SOURCE_DIR/connection.json not found"
exit 1
fi
#simply copy the endpoint information to specified output location
cp $CHAINCODE_SOURCE_DIR/connection.json $BUILD_OUTPUT_DIR/connection.json
if [ -d "$CHAINCODE_SOURCE_DIR/metadata" ]; then
cp -a $CHAINCODE_SOURCE_DIR/metadata $BUILD_OUTPUT_DIR/metadata
fi
exit 0
2.2.3 發佈腳本
在完成合約基礎構建之後,剩下只需要發佈就行。
發佈腳本是bin/release
https://github.com/llzz9595/fabric-learning/blob/master/first-network/external/bin/release
2.3 修改節點配置
2.3.1 core.yaml
core.yaml
文件有的朋友可能不太熟悉,還是簡單科普一下,core.yaml是節點的默認配置文件,詳細的配置項可以在源碼~/fabric/sample-config/core.yaml
裏面找到,一般來說,假如我們沒有定義core.yaml,節點內部/etc/hyperledger目錄下也會有這個文件,他的參數跟環境變量是基本一致的,但是環境變量的優先級別高於core.yam
,這裏我們設置的參數由於是一個數組對象,我們將創建core.yaml並且將core.yaml
映射到節點的/etc/hyperledger/fabric
目錄下面。
具體操作如下:
- 複製sample-config目錄下的core.yaml到~first-network
cd ~/first-network
cp ../../sample-config/core.yaml .
2.修改core.yaml合約相關配置
定位:524
行
externalBuilders:
- path: /opt/gopath/src/github.com/hyperledger/fabric/peer/external
name: builder
# environmentWhitelist:
# - ENVVAR_NAME_TO_PROPAGATE_FROM_PEER
# - GOPROXY
path
: externalBuilders路徑 ,填寫節點容器內存放externalBuilder腳本(1.2.2寫的3個腳本)的目錄,目前定義在節點內部的/opt/gopath/src/github.com/hyperledger/fabric/peer/external,各位可自定義路徑,後面映射。
name:
外部構建器名稱,自定義。
environmentWhitelist:
環境變量白名單,節點在調用構建器腳本的時候可以採用的環境變量。
然後保存文件就行了。
2.3.2 節點容器配置
節點容器配置我們只需要完成2個事情,把external的3個腳本映射到externalBuilders.path以及將core.yaml掛載到容器的/etc/hyperledger/fabric目錄
以first-network的配置爲例
cd ~/first-network
vi base/peer-base.yaml
編輯peer-base服務的掛載參數
services:
peer-base:
image: hyperledger/fabric-peer:$IMAGE_TAG
environment:
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
# the following setting starts chaincode containers on the same
# bridge network as the peers
# https://docs.docker.com/compose/networking/
- CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=${COMPOSE_PROJECT_NAME}_byfn
- FABRIC_LOGGING_SPEC=INFO
#- FABRIC_LOGGING_SPEC=DEBUG
- CORE_PEER_TLS_ENABLED=true
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_PROFILE_ENABLED=true
- CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
- CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
- CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
# Allow more time for chaincode container to build on install.
- CORE_CHAINCODE_EXECUTETIMEOUT=300s
#掛載外部啓動智能合約相關腳本文件以及core.yaml
volumes:
# 掛載目錄
- ../external:/opt/gopath/src/github.com/hyperledger/fabric/peer/external
- ../core.yaml:/etc/hyperledger/fabric/core.yaml
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
command: peer node start
然後就可以快樂地適用./byfn.sh up
啓動網絡了~,具體可參考(一)HyperLedger Fabric 2.0-release編譯鏡像二進制文件+測試網絡部署。
2.4 部署合約到網絡
要實現外部部署合約,對於要install到fabric的合約包跟之前(五)Fabric2.0 智能合約實踐- 安裝以及定義智能合約的包是不一樣的,這個包裏面不需要合約的源代碼。
按照定義來說,這個合約包含有:
metadata.json : 合約的屬性,與原本的合約屬性不一致的是他的path可以爲空,他的type可以自定義。
connection.json :這個文件是用於節點去連接合約的相關配置信息,雙方交互基於grpc服務。
metadata文件夾:應該是跟couchdb存放私有數據相關的,本實踐不做詳細說明。
根據我這邊的情況,目前只需要有metadata.json跟connection.json就行了。
我們在 ~/fabric-samples/chaincode/abstore/go 下面創建一個packing目錄用於存放打包文件
cd ~/fabric-samples/chaincode/abstore/go && mkdir packing && cd packing
2.4.1 編寫metadata.json
在packing目錄下,編輯metadata.json文件如下:
{"path":"","type":"external","label":"mycc2"}
path: 不需要填寫內容。
type: 自定義,用於bin/detect
檢測。
label: 自定義。
2.4.2 編寫connection.json
例子:
{
"address": "192.168.2.103:7052",
"dial_timeout": "10s",
"tls_required": false,
"client_auth_required": false,
"client_key": "-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----",
"client_cert": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----",
"root_cert": "-----BEGIN CERTIFICATE---- ... -----END CERTIFICATE-----"
}
address
: 我們將要在外面運行/部署的合約開出來的grpc服務ip跟端口,最好先定下來(沒定好隨便寫個也行,反正後面一個個節點改就是了)。
dial_timeout
:grpc連接超時時間。指定爲具有時間單位(例如“ 10s”,“ 500ms”,“ 1m”)的字符串。如果未指定,默認值爲“ 3s”。
tls_required
: 是否需要tls,視乎您等會在合約上面編輯的grpc服務端是否需要證書,不需要就false,需要true。假如false的話,下面3項可以就這樣按模板填寫,寫空也行。
client_key
: grpc 客戶端訪問合約服務端需要的私鑰。
client_cert
: grpc 客戶端訪問合約服務端需要的證書。
root_cert
: grpc 客戶端訪問合約服務端需要的根證書。
2.4.2 打包合約
還是在packing目錄
執行一下命令:
tar cfz code.tar.gz connection.json
tar cfz mycc2.tgz metadata.json code.tar.gz
查看當前packing目錄
2.4.3 安裝部署合約
整個2.4.3操作通過cli操作
docker exec -it cli bash
cd ../../fabric-samples/chaincode/abstore/packing
後面從install開始到commit完成合約安裝部署,跟平常沒什麼不一樣
下面命令自行替換參數,具體參考(五)Fabric2.0 智能合約實踐- 安裝以及定義智能合約
install合約,每個背書節點都要install
peer lifecycle chaincode install mycc2.tgz
批准合約定義,必須符合lifecycle策略
peer lifecycle chaincode approveformyorg --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 --channelID mychannel --name mycc2 --version 1 --init-required --package-id mycc_1:84b3aaf583a6632e806a0ff46ad804539797d84ff7826c88a6d6009a9930cfee --sequence 1 --waitForEvent
提交合約定義到網絡
peer lifecycle chaincode commit -o orderer.example.com:7050 --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 --channelID mychannel --name mycc --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 --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --version 1 --sequence 1 --init-required
完全部署成功之後,我們可以看到跟往常部署不一樣的是,合約容器並沒有啓動,在節點容器
/var/hyperledger/production/externalbuilder/builds/
出現構建發佈後的合約文件目錄:
2.4.3.1 可能出現的錯誤與解決方案
could not build chaincode :docker build failed: platform builder failed: Failed to generatea Dockerfile:Unknow Chaincode Type:EXTERNAL
首先錯誤的意思就您install的合約類型不對,合約類型定義是在我們前面定義的metadata.json裏面的type,我們爲了區份原本的部署合約方式填寫了external,一個不屬於fabric支持的合約類型的type。
那是不是說明我們錯了?
是的。
但是錯的地方在外部構建器啓動器的腳本,並不是合約定義metadata.json。必須讓你的bin/detect,bin/build,bin/release腳本是能正常執行,比如說節點容器沒有jq包,但是官方案例裏面是使用jq解析配置,這是detect執行就會報錯,在報錯的情況下,會直接選擇原本的合約部署方式。當然按照我上面給的腳本是不會報錯,大家大膽install。
2.4.4 編寫合約
這一次順序跟之前不一樣,但是其實也是可以一開始編寫的,問題不大。
以abstore合約例子爲例,只需要修改原有的main方法,繼承shim.ChaincodeServer
func main() {
server := &shim.ChaincodeServer{
CCID: os.Getenv("CHAINCODE_CCID"),
Address: os.Getenv("CHAINCODE_ADDRESS"),
CC: new(ABstore),
TLSProps: shim.TLSProperties{
Disabled: true,
},
}
// Start the chaincode external server
err := server.Start()
if err != nil {
fmt.Printf("Error starting Marbles02 chaincode: %s", err)
}
}
CCID
: 部署完成後生產的合約ID,這裏使用環境變量讀取比較方便,如
mycc2:6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454
Address
:定義grpc服務IP端口,如 0.0.0.0:9999,這裏使用環境變量讀取比較方便。
CC
: 合約對象。
TLSProps
: tls配置,與connection.json相呼應。
2.4.5 啓動合約
整個2.4.5操作都在宿主機
其實到這裏,啓動合約可以選很多種方式,你可以編輯器啓動,也可以go build一下二進制啓動,這裏的話,對於測試/生產環境,我希望這個chaincode更加方便遷移環境(所以上面讀取環境變量也是爲此),我會選擇打包合約鏡像,運行合約容器,當然一般開發環境調試可以IDE來弄,但是測試生產環境目前還是偏向docker。
因此我們接下來按照一般的容器構建,來構建我們的合約鏡像以及啓動合約容器
2.4.5.1 構建合約鏡像
在abstore/go 目錄,編輯Dockerfile如下:
#This image is a microservice in golang for the Degree chaincode
FROM golang:1.13.8-alpine AS build
COPY ./ /go/src/github.com/abstore
WORKDIR /go/src/github.com/abstore
# Build application
RUN GOPROXY="https://goproxy.cn" GO111MODULE=on go build -mod vendor -o chaincode -v .
# Production ready image
# Pass the binary to the prod image
FROM alpine:3.11 as prod
COPY --from=build /go/src/github.com/abstore/chaincode /app/chaincode
USER 1000
WORKDIR /app
CMD ./chaincode
這個腳本就是構建合約可執行文件,啓動容器運行合約可執行文件。
開始構建鏡像
cd ~fabric-samples/chaincode/abstore/go
docker build -t chaincode/mycc2:1.0 .
然後完成後,通過docker images |grep mycc2
確認
2.4.5.2 啓動合約容器
使用docker-compose啓動,編輯docker-compose-mycc.yaml如下:
version: '2'
networks:
default:
external:
name: net_byfn
services:
#合約容器
mycc2:
#定義主機名
container_name: mycc2.chiancode.com
#使用的鏡像
image: chaincode/mycc2:1.0
#容器的映射端口
ports:
- 9999:9999
networks:
- default
#環境變量
privileged: true
environment:
- CHAINCODE_CCID=mycc2:6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454
- CHAINCODE_ADDRESS=0.0.0.0:9999
注意CHAINCODE_CCID一定要對應我們之前安裝好的合約id
啓動容器:
docker-compose -f docker-compose-mycc.yaml up -d
使用docker ps
確認容器是否啓動成功
3 測試外部合約
對部署好的合約進行測試,docker exec -it cli bash
進入cli容器。
init合約
peer chaincode invoke -o orderer.example.com:7050 --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 -C mychannel -n mycc201 --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 --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --isInit -c '{"Args":["Init","a","100","b","100"]}'
查詢交易結果:
peer chaincode query -C mychannel -n mycc201 -c '{"Args":["query","a"]}'
控制檯輸出:
結論:十分成功。
當然成功乃失敗之母,我可是跟成功隔了好幾代的小夥子。下面開始一波錯誤總結
3.1 有可能的報錯及解決方案
Error: endorsement failure during invoke. response: status:500 message:"error in simulation: failed to execute transaction 32e56961b2d9720f5e266ad0e61ce8b5ecaa5857e5b68b19a229ee3bd57da86f: could not launch chaincode mycc2:6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454: error building chaincode: malformed chaincode info at '/var/hyperledger/production/externalbuilder/builds/mycc2-6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454/release/chaincode/server/connection.json': invalid character '\\n' in string literal"
簡單來說就是之前在合約定義的connection.json寫得不規範,直接進去所有安裝合約的節點裏面的
/var/hyperledger/production/externalbuilder/builds/mycc2-6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454/release/chaincode/server/connection.json這個目錄就是錯誤提示文件路徑,改你conntion.json改到符合json解析規範爲止。
Error: endorsement failure during invoke. response: status:500 message:"error in simulation: failed to execute transaction 3b30d51113c87ae5279a5a4efc1b20b926afda53b356da5d95026b8ab08a83ff: could not launch chaincode mycc2:6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454: error building chaincode: malformed chaincode info at '/var/hyperledger/production/externalbuilder/builds/mycc2-6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454/release/chaincode/server/connection.json': invalid character '~' after top-level value"
同1
Error: endorsement failure during invoke. response: status:500 message:"error in simulation: failed to execute transaction 6d756de4e462811da9a44aa0acf68c27e2fe66445de04c1083bb257da116ede1: could not launch chaincode mycc2:6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454: error building chaincode: malformed chaincode info at '/var/hyperledger/production/externalbuilder/builds/mycc2-6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454/release/chaincode/server/connection.json': json: cannot unmarshal string into Go struct field ChaincodeServerUserData.tls_required of type bool"
還是connection.json的錯誤,但是不是格式,tls_required參數類型必須是boolean,不需要雙引號。
Error: endorsement failure during invoke. response: status:500 message:"error in simulation: failed to execute transaction 556ddd1378a67cf0bab2e81cce7377a605bfdb8ac9a49b815680148b93b70a88: could not launch chaincode mycc2:6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454: connection to mycc2:6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454 failed: error cannot create connection for mycc2:6be5ebad4dabfda4e055b936658bf29a28079a2a4188915013998591426ac454: error creating grpc connection to 192.168.2.101:9999: failed to create new connection: connection error: desc = \"transport: error while dialing: dial tcp 192.168.2.101:9999: connect: no route to host\""
就是節點連不上您外部啓動的合約服務,確認合約是否正常啓動,再者telnet一下看看ip:port是不是通的,改到通爲止。
4.總結
對實現外部構建啓動合約總結流程如下:
其中構建外部合約對於docker形式來說是構建合約鏡像,如果是本地跑的可以不需要這一步。
實踐完整一個外部構建啓動合約的過程,其實比較痛苦,因爲官方文檔確實描述有點亂,像我這種一般人很容易就會各種迷,但是經過大家討論以及源碼分析後,除了解決問題之外,對fabric這個新特性也有了更深的認識,通過外部構建方式,合約不再是隻能是一個容器形式存在,他可以IDE運行debug,也可以編譯成二進制運行,對於不同環境不同開發者來說有了靈活的選擇,雖然暫時只支持go合約。