(十二)Fabric2.0-實現外部構建啓動合約

總目錄:
(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目錄下面。

具體操作如下:

  1. 複製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 有可能的報錯及解決方案

  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解析規範爲止。

  1. 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

  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,不需要雙引號。

  1. 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合約。

源碼: https://github.com/llzz9595/fabric-learning

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章