外部鏈碼構建與運行
官方文檔
在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
只能包含常規文件和目錄條目,並且這些條目不能包含會導致文件寫入鏈碼包根路徑邏輯外。