外部鏈碼構建與運行
官方文檔
在Hyperledger Fabric 2.0版本之前,鏈碼的構建和運行是節點實現的一部分,並且定制化是困難的。所有鏈碼在節點上實例化是通過”構建“即根據語言指定的邏輯在節點上硬編碼。構建過程將生成Docker容器鏡像作為客戶端連接節點用來運行可執行的鏈碼。
這種方法將鏈代碼實現限制為只能使用幾種語言實現,要求Docker成為部署環境的一部分,並阻止將鏈代碼作為長時間運行的服務器進程運行。
外部構建模式
Hyperledger Fabric外部構建器和啟動器大致基於Heroku Buildpacks。buildpack實現只是將程序歸檔轉換為可以運行的程序或腳本的集合。buildpack模型已針對鏈碼包進行了調整,並擴展為支持鏈碼執行和發現。
外部構建和運行API
外部構建和運行由4個腳本程序組成:
bin/detect:確定是否應使用此buildpack來構建chaincode程序包並啟動它bin/build:轉換鏈碼包為可執行的鏈碼bin/release(可選):為peer節點提供關於鏈碼的元數據bin/run(可選):運行鏈碼
bin/detect
bin/detect腳本決定是否應使用此buildpack來構建chaincode程序包並啟動它,peer節點通過兩個參數調用detect:
bin/detect CHAINCOD_SOURCE_DIR CHAINCODE_METADATA_DIR
當detect被調用,CHAINCOD_SOURCE_DIR包含的鏈碼資源以及CHAINCODE_METADATA_DIR包含的metadata.json文件將從鏈碼包中安裝到節點。CHAINCOD_SOURCE_DIR和CHAINCODE_METADATA_DIR應該被作為只讀輸入。如果將buildpack應用於chaincode源程序包,detect必須返回退出碼0;否則,其他任何退出代碼都將指示buildpack不應用內於chaincode源程序包。
下面是一個簡單的用於go語言鏈碼的detect腳本例子:
#!/bin/bash
CHAINCODE_METADATA_DIR="$2"
# 使用jq工具從metadata.json中獲取鏈碼類型,如果鏈碼類型為golang,則成功退出
if [ "$(jq -r .type "$CHAINCODE_METADATA_DIR/metadata.json" | tr '[:upper:]' '[:lower:]')" = "golang" ]; then
exit 0
fi
exit 1
bin/build
bin/build腳本用於構建,編譯,或者轉換鏈碼包的內容到可以被release和run使用的類型。節點通過三個參數調用build:
bin/build CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR BUILD_OUTPUT_DIR
當build被調用,CHAINCOD_SOURCE_DIR包含的鏈碼資源以及CHAINCODE_METADATA_DIR包含的metadata.json文件將從鏈碼包中安裝到節點。BUILD_OUTPUT_DIR是一個文件夾用於存放release和run需要的文件。build腳本應該將CHAINCOD_SOURCE_DIR和CHAINCODE_METADATA_DIR作為只讀輸入,BUILD_OUTPUT_DIR作為可寫輸出。
下面是一個簡單的用於go語言鏈碼的build腳本例子:
#!/bin/bash
CHAINCODE_SOURCE_DIR="$1"
CHAINCODE_METADATA_DIR="$2"
BUILD_OUTPUT_DIR="$3"
# 從 metadata.json獲取包內容
GO_PACKAGE_PATH="$(jq -r .path "$CHAINCODE_METADATA_DIR/metadata.json")"
if [ -f "$CHAINCODE_SOURCE_DIR/src/go.mod" ]; then
cd "$CHAINCODE_SOURCE_DIR/src"
go build -v -mod=readonly -o "$BUILD_OUTPUT_DIR/chaincode" "$GO_PACKAGE_PATH"
else
GO111MODULE=off go build -v -o "$BUILD_OUTPUT_DIR/chaincode" "$GO_PACKAGE_PATH"
fi
# 存儲狀態數據庫索引元數據提供給release
if [ -d "$CHAINCODE_SOURCE_DIR/META-INF" ]; then
cp -a "$CHAINCODE_SOURCE_DIR/META-INF" "$BUILD_OUTPUT_DIR/"
fi
bin/release
bin/release腳本為節點提供鏈碼元數據。該腳本是可選的。如果沒有提供,這一步將會跳過。節點通過兩個參數調用release:
bin/release BUILD_OUTPUT_DIR RELEASE_OUTPUT_DIR
調用release時,BUILD_OUTPUT_DIR包含構建程序填充的歸檔,應將其視為只讀輸入。RELEASE_OUTPUT_DIR是release必須放置歸檔以供節點使用的目錄。
當release執行完成,節點將會從RELEASE_OUTPUT_DIR消費兩種類型的元數據:
- CouchDB定義的狀態數據庫索引。
- 外部鏈碼服務連接信息(
chaincode/server/connection.json)
如果鏈碼要求CouchDB索引定義,release需要將索引放置在RELEASE_OUTPUT_DIR下的state/couchdb/indexes文件夾內。索引必須含有.json擴展名。
在使用鏈碼服務器實現的情況下,release負責使用鏈碼服務器的地址以及與鏈碼通信所需的任何TLS資產來填充chaincode/server/connection.json。將服務器連接信息提供給節點時,將不會調用run。
下面是一個簡單的用於go語言鏈碼的release腳本例子:
#!/bin/bash
BUILD_OUTPUT_DIR="$1"
RELEASE_OUTPUT_DIR="$2"
# 從 META-INF/* 拷貝索引文件到輸出文件夾
if [ -d "$BUILD_OUTPUT_DIR/META-INF" ] ; then
cp -a "$BUILD_OUTPUT_DIR/META-INF/"* "$RELEASE_OUTPUT_DIR/"
fi
bin/run
bin/run腳本用於鏈碼的運行。節點通過兩個參數調用run:
bin/run BUILD_OUTPUT_DIR RUN_METADATA_DIR
當BUILD_OUTPUT_DIR包含build程序填充的歸檔,而RUN_METADATA_DIR包含有一個名為chaincode.json的文件,該文件包含鏈碼連接和注冊到節點所需的信息,run將被調用。bin/run腳本對於BUILD_OUTPUT_DIR以及RUN_METADATA_DIR文件夾應為只讀輸入。chaincode.json文件包含的關鍵信息有:
chaincode_id:連接到鏈碼包的唯一IDpeer_address:``peer節點的ChaincodeSupport中的gRPC服務端點主機地址,格式為host:port.client_cert:由peer生成的PEM編碼的TLS客戶端證書。當鏈碼與節點建立連接時將會被使用。client_key:由peer生成的PEM編碼的客戶端秘鑰。當鏈碼與節點建立連接時將會被使用。root_cert:由peer節點的ChaincodeSupport中的gRPC服務端點主機使用的PEM編碼的TLS根證書。
當run停止時,與peer連接的鏈碼也會終止。如果另一個請求訪問鏈碼,節點將會嘗試通過調用run啟動鏈碼的另一個實例。在調用鏈碼時,chaincode.json文件內容不能夠被緩存。
下面是一個簡單的用於go語言鏈碼的run腳本例子:
#!/bin/bash
BUILD_OUTPUT_DIR="$1"
RUN_METADATA_DIR="$2"
# 為go語言鏈碼shim包配置環境變量
export CORE_CHAINCODE_ID_NAME="$(jq -r .chaincode_id "$RUN_METADATA_DIR/chaincode.json")"
export CORE_PEER_TLS_ENABLED="true"
export CORE_TLS_CLIENT_CERT_FILE="$RUN_METADATA_DIR/client.crt"
export CORE_TLS_CLIENT_KEY_FILE="$RUN_METADATA_DIR/client.key"
export CORE_PEER_TLS_ROOTCERT_FILE="$RUN_METADATA_DIR/root.crt"
# 為go語言鏈碼shim包獲取秘鑰和證書材料
jq -r .client_cert "$RUN_METADATA_DIR/chaincode.json" > "$CORE_TLS_CLIENT_CERT_FILE"
jq -r .client_key "$RUN_METADATA_DIR/chaincode.json" > "$CORE_TLS_CLIENT_KEY_FILE"
jq -r .root_cert "$RUN_METADATA_DIR/chaincode.json" > "$CORE_PEER_TLS_ROOTCERT_FILE"
if [ -z "$(jq -r .client_cert "$RUN_METADATA_DIR/chaincode.json")" ]; then
export CORE_PEER_TLS_ENABLED="false"
fi
# 執行鏈碼並使用鏈碼進程替代腳本
exec "$BUILD_OUTPUT_DIR/chaincode" -peer.address="$(jq -r .peer_address "$ARTIFACTS/chaincode.json")"
外部構建和運行的配置
在core.yaml的chaincode配置區域下添加一個externalBuilder元素配置節點以使用外部構建器.每一個外部構建器的定義必須包含名字(用於日志)和包含構建器腳本的bin文件夾的上一級路徑。
調用外部構建器腳本時還可以從節點獲取環境變量名稱的可選列表。
下面的示例定義了兩個外部構建器:
chaincode:
externalBuilders:
- name: my-golang-builder
path: /builders/golang
environmentWhitelist:
- GOPROXY
- GONOPROXY
- GOSUMDB
- GONOSUMDB
- name: noop-builder
path: /builders/binary
在這個示例中,實現的構建器my-golang-builder被包含在/builders/golang文件夾內,它的腳本文件位於/builders/golang/bin.當節點調用任何與my-golang-builder相關的構建腳本時,將只會傳播白名單內的環境變量的值。
這些環境變量總是傳播到外部構建器:
- LD_LIBRARY_PATH
- LIBPATH
- PATH
- TMPDIR
當externalBuilder配置存在時,節點將會迭代按順序排列的構建器的列表。調用/bin/detect直到其中的一個成功執行。
如果沒有構建器成功執行detect腳本,節點將會回滾使用初始的Docker通過節點實現構建進程。這說明外部的構建器是完全可選的。
在上面的示例中,節點將試圖使用my-golang-builder,如果無效的話則使用noop-builder,還是無效的話最后使用節點內部構建進程。
鏈碼包
Fabric 2.0引入了新的生命周期鏈碼。鏈碼包的格式從序列號協議緩沖消息變為了由gzip壓縮的POSIX tape歸檔。鏈碼包通過使用peer lifecycle chaincode package創建新的格式。
Lifecycle鏈碼包內容
lifecycle鏈碼包包含兩個文件,第一個文件code.tar.gz是一個使用gzip壓縮的POSIX tape歸檔。這個文件包括鏈碼的源歸檔。由節點CLI創建並將鏈碼的實現源碼放置在src文件夾下,鏈碼的元數據(如CouchDB索引文件)放置在META-INF文件夾。
第二個文件metadata.json是一個JSON格式的文檔包含三個鍵:
type:鏈碼的類型(例如GOLANG,JAVA,NODE)path:對於go語言鏈碼,則是GOPATH或者GOMOD到主鏈碼包的相對路徑,其他類型的鏈碼未定義。label:用於生成包ID的鏈碼標簽,在新的鏈碼lifecycle過程中用於標識包的身份。
type和path字段僅由Docker平台構建使用。
鏈碼包以及外部構建器
當鏈碼包安裝在節點上后,code.tar.gz和metadata.json的內容將不能調用外部構建器處理。除了label字段用於新的lifecycle對包ID進行計算。為用戶提供了很大的靈活性,使他們可以打包將由外部構建者和啟動者處理的源和元數據。
例如,可以構造一個自定義的鏈碼包,該代碼包在code.tar.gz中包含一個預編譯的鏈碼實現,並帶有一個metadata.json文件,允許二進制構建包檢測該自定義包,驗證哈希值並作為鏈碼運行。
另一個示例是chaincode程序包,其中僅包含狀態數據庫索引定義以及外部啟動程序連接到正在運行的chaincode服務器所需的數據。在這種情況下,build過程將僅從過程中提取元數據,然后將其release給節點。
唯一的要求是code.tar.gz只能包含常規文件和目錄條目,並且這些條目不能包含會導致文件寫入鏈碼包根路徑邏輯外。
