Fabric 系統(tǒng)鏈碼插件研究

寫在前面

在 fabric 里面,有很多模塊支持 plugin 的形式進(jìn)行替換,實(shí)現(xiàn)了在不影響主程序的情況下自由擴(kuò)展定制自己的關(guān)鍵模塊,實(shí)現(xiàn)可插拔。
本文針對(duì)其中的系統(tǒng)鏈碼插件的功能的部署使用進(jìn)行研究。

理論研究

什么是系統(tǒng)鏈碼

fabric 自 1.0 版本開(kāi)始,將鏈碼分為系統(tǒng)鏈碼和普通鏈碼兩種。普通鏈碼(智能合約)用于實(shí)現(xiàn)業(yè)務(wù)邏輯,而系統(tǒng)鏈碼則是用于系統(tǒng)管理,例如 lscc、qscc等。
與普通鏈碼需要獨(dú)立沙盒環(huán)境運(yùn)行不同,系統(tǒng)鏈碼在 peer 服務(wù)啟動(dòng)時(shí)隨 peer 節(jié)點(diǎn)注冊(cè),同 peer 節(jié)點(diǎn)一起運(yùn)行。
在 fabric 1.0 版本時(shí),系統(tǒng)鏈碼為固定的 5 個(gè):lsccqscc、csccvscc、escc,這 5 個(gè)鏈碼功能固定,分別用于鏈碼生命周期管理、區(qū)塊/交易查詢、通道配置管理、交易背書和交易驗(yàn)證。

什么是系統(tǒng)鏈碼插件

系統(tǒng)鏈碼使用方便,但是由于其功能、邏輯固定,不利于擴(kuò)展。在 1.1 版本開(kāi)始,fabric 允許定制自己的 vsccescc,這樣智能合約所能實(shí)現(xiàn)的交易模式會(huì)更加豐富。
實(shí)現(xiàn)這個(gè)功能就是 fabric 開(kāi)始支持系統(tǒng)鏈碼插件,通過(guò)插件的形式達(dá)到動(dòng)態(tài)注冊(cè)系統(tǒng)鏈碼的目的。

如何開(kāi)發(fā)系統(tǒng)鏈碼插件

一個(gè)系統(tǒng)鏈碼插件(system chaincode plugin)需要使用 go 語(yǔ)言,開(kāi)發(fā)一個(gè) system chaincode 的 plugin。(關(guān)于 go 語(yǔ)言 plugin 的介紹可以參看我之前的文章Golang筆記-Plugin初探

而 system chaincode 和普通 chaincode 一樣,需要實(shí)現(xiàn) Chaincode 接口。

type Chaincode interface {
    Init(stub ChaincodeStubInterface) pb.Response
    Invoke(stub ChaincodeStubInterface) pb.Response
}

與正常形式的鏈碼略微不同的是,由于是 go 的插件,所以這個(gè)系統(tǒng)鏈碼的 go 源碼必須屬于 main 包。

如何部署系統(tǒng)鏈碼插件

peer 服務(wù)中跟系統(tǒng)鏈碼插件相關(guān)的有兩塊配置:

  • chaincode.systemPlugin配置節(jié)中,關(guān)于系統(tǒng)鏈碼的基本信息
  chaincode:
    systemPlugins:
      - enabled: true
        name: mysyscc
        path: /opt/lib/syscc.so
        invokableExternal: true
        invokableCC2CC: true
  • chaincode.system 配置節(jié)中,關(guān)于是否啟用某個(gè)系統(tǒng)鏈碼的配置信息
  chaincode:
    system:
      mysyscc: enable

所以我們需要三步操作來(lái)進(jìn)行系統(tǒng)鏈碼插件的部署:

  1. 準(zhǔn)備系統(tǒng)鏈碼代碼并以 plugin 的形式進(jìn)行編譯;
  2. 在 peer 的配置文件的 chaincode.SystemPlugin 項(xiàng)下配置該插件的基本信息;
  3. 在 peer 的配置文件的 chaincode.system 項(xiàng)下啟用該插件;

系統(tǒng)鏈碼插件有什么用

上面說(shuō)了這么多,對(duì)系統(tǒng)鏈碼插件的開(kāi)發(fā)、部署都有了必要的了解,但是我們似乎忘了一個(gè)問(wèn)題-開(kāi)發(fā)自己的系統(tǒng)鏈碼有什么用呢?

回顧上面對(duì)系統(tǒng)鏈碼的介紹,因?yàn)橄到y(tǒng)鏈碼可以在 peer 節(jié)點(diǎn)中與 peer 進(jìn)程共同運(yùn)行,不需要沙盒環(huán)境,所以它可以訪問(wèn)比普通 chaincode 更多的資源:比如本地?cái)?shù)據(jù)、賬本信息、配置信息,甚至鏈外信息。

是的,我們可以通過(guò)系統(tǒng)鏈碼來(lái)實(shí)現(xiàn)業(yè)務(wù)鏈碼跟鏈外的交互。這將大大擴(kuò)展智能合約的能力,而不在受限于其所運(yùn)行的沙河環(huán)境。

實(shí)踐記錄

目標(biāo)

基于以上認(rèn)識(shí),我想到了使用系統(tǒng)鏈碼解決一個(gè)困擾許久的問(wèn)題:通過(guò)系統(tǒng)鏈碼來(lái)實(shí)現(xiàn)狀態(tài)數(shù)據(jù)的導(dǎo)入導(dǎo)出、備份遷移等。

在這之前,我們的考慮過(guò)得數(shù)據(jù)備份遷移方案有兩種:

  • 通過(guò)鏈碼交易接口,以正常交易的形式進(jìn)行數(shù)據(jù)的導(dǎo)入導(dǎo)出。但是受限于交易數(shù)據(jù)大?。▍^(qū)塊大小)的限制,每次的導(dǎo)入導(dǎo)出數(shù)據(jù)量有限,數(shù)據(jù)量變大之后會(huì)很麻煩;
  • 通過(guò)文件系統(tǒng),將數(shù)據(jù)備份到文件。這種方案由于鏈碼運(yùn)行于沙盒環(huán)境,其維護(hù)難度高,且在 1.0 之后已經(jīng)沒(méi)有途徑可以控制沙盒容器的文件掛載;

而有了系統(tǒng)鏈碼之后,我們可以集合兩個(gè)方案并進(jìn)行改進(jìn),將文件操作的部分放到系統(tǒng)鏈碼中實(shí)現(xiàn),而導(dǎo)入導(dǎo)出的邏輯在業(yè)務(wù)鏈碼中,業(yè)務(wù)鏈碼通過(guò)InvokeChaincode API 調(diào)用系統(tǒng)鏈碼,實(shí)現(xiàn)數(shù)據(jù)導(dǎo)入導(dǎo)出。而系統(tǒng)鏈碼所在的 peer 容器是很方便維護(hù)的。

本次實(shí)踐基于這個(gè)目標(biāo)進(jìn)行模擬探索。

鏈碼開(kāi)發(fā)

首先準(zhǔn)備系統(tǒng)鏈碼插件部分的源碼,一個(gè)實(shí)現(xiàn)了 Chaincode 接口且在 main 包中的 go 程序。核心代碼如下:

func (s *DataBackSCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    logger.Info("Invoke")
    checkExist()
    args := stub.GetStringArgs()
    for _, s := range args {
        logger.Infof("BackUp String: '%s'\n", s)
        filePath := filepath.Join(backpath, backfile)
        f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666)
        if err != nil {
            logger.Error(err)
        }
        _, err = f.WriteString(s + "\n")
        if err != nil {
            logger.Error(err)
        }
    }
    return shim.Success(nil)
}

這個(gè)系統(tǒng)鏈碼主要實(shí)現(xiàn)的功能是將外部傳進(jìn)來(lái)的數(shù)據(jù)寫入一個(gè)備份文件,完整代碼請(qǐng)參考 databackscc.go

然后是普通的業(yè)務(wù)鏈碼,調(diào)用系統(tǒng)鏈碼中的功能實(shí)現(xiàn)數(shù)據(jù)的導(dǎo)出。核心代碼如下:

func (s *DataBackCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    logger.Info("Invoke")
    rsp := stub.InvokeChaincode("databackscc", [][]byte{[]byte(""), []byte(stub.GetTxID())}, stub.GetChannelID())
    return shim.Success(rsp.GetPayload())
}

完整代碼請(qǐng)參考 databackcc.go

鏈碼部署

  1. 系統(tǒng)鏈碼進(jìn)行編譯:
go build -buildmode=plugin
  1. 準(zhǔn)備 peer 的配置文件 core.yaml 系統(tǒng)鏈碼插件相關(guān)的配置信息:
...
    systemPlugins:
      - enabled: true
        name: databackscc
        path: /etc/hyperledger/fabric/plugin/databackscc.so
        invokableExternal: true
        invokableCC2CC: true
...
    system:
        ...
        databackscc: enable
  1. 準(zhǔn)備 fabric 啟動(dòng)相關(guān)配置、腳本等

萬(wàn)事具備只欠東風(fēng),啟動(dòng)網(wǎng)絡(luò)等待成功時(shí)刻。

...

這么順利是不可能的,這是常識(shí)。如果真的這么順利就成功了,那說(shuō)明一定有哪個(gè)地方弄錯(cuò)了。

排雷記錄

  • 插件功能未啟用

第一個(gè)問(wèn)題就是,默認(rèn)的 peer 鏡像是不支持系統(tǒng)鏈碼插件功能,分析相關(guān)源碼(github.com/hyperledger/fabric/core/scc/register_pluginsenabled.go)可知:

// +build pluginsenabled,cgo
// +build darwin,go1.10 linux,go1.10 linux,go1.9,!ppc64le

/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package scc

// CreatePluginSysCCs creates all of the system chaincodes which are loaded by plugin
func CreatePluginSysCCs(p *Provider) []SelfDescribingSysCC {
    var sdscs []SelfDescribingSysCC
    for _, pscc := range loadSysCCs(p) {
        sdscs = append(sdscs, &SysCCWrapper{SCC: pscc})
    }
    return sdscs
}

這段加載系統(tǒng)鏈碼插件的核心代碼是加了 build tag 的,而 makefile 中的默認(rèn)編譯方式均未有 pluginsenabled 的編譯標(biāo)簽,因此我們需要重新編譯鏡像,增加 pluginsenabled

的標(biāo)簽,以啟用插件功能。

GO_TAGS+=" pluginsenabled" make peer-docker
  • 插件編譯失敗

編譯出新的 peer 鏡像后,重新啟動(dòng)網(wǎng)絡(luò),終于在日志中看到加載系統(tǒng)鏈碼插件的相關(guān)信息了,可是加載失敗。這時(shí)回想起編譯鏡像時(shí)有警告信息:

Building .build/docker/bin/peer
# github.com/hyperledger/fabric/peer
/tmp/go-link-488306534/000006.o: In function `pluginOpen':
/workdir/go/src/plugin/plugin_dlopen.go:19: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-488306534/000021.o: In function `mygetgrouplist':
/workdir/go/src/os/user/getgrouplist_unix.go:16: warning: Using 'getgrouplist' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
...

在靜態(tài)編譯方式下使用了動(dòng)態(tài)鏈接庫(kù),所以這個(gè)插件實(shí)際上是不OK的。經(jīng)過(guò)艱苦的研(goo)究(gle)之后,終于找到原因并解決。

peer 的鏡像編譯方式中,采用了靜態(tài)編譯的模式,但是其原生可執(zhí)行文件卻不是這樣。所以需要在編譯 peer 鏡像時(shí)指定采用動(dòng)態(tài)編譯的方式:

DOCKER_DYNAMIC_LINK=true GO_TAGS+=" pluginsenabled" make peer-docker

成功生成新的具備系統(tǒng)鏈碼插件功能的 peer 鏡像。

  • 插件依賴包版本不一致問(wèn)題

根據(jù)官方文檔說(shuō)明,插件所用的依賴包必須和主程序所用的依賴包版本相同。

為了編譯方便,最開(kāi)始我將插件的依賴包導(dǎo)入了 vendor 包中,雖然引用的依賴包和所編譯的 peer 為同一版本,但是 go 識(shí)別為 $GOPATH 下 vendor 中的為不同版本。因此放棄 vendor,同時(shí)將插件源碼和 peer 編譯環(huán)境放在一起,在 peer 編譯環(huán)境下編譯插件,這樣保證 peer 和 plugin 的依賴包一致。

運(yùn)行之后,依然是依賴包版本不一致的問(wèn)題:

panic: Error opening plugin at path /etc/hyperledger/fabric/plugin/databackscc.so: plugin.Open("/etc/hyperledger/fabric/plugin/databackscc"): plugin was built with a different version of package github.com/hyperledger/fabric/vendor/github.com/golang/protobuf/proto
...

再一次經(jīng)過(guò)艱苦的研(goo)究(gle)之后,原理是 go 的 plugin 功能在對(duì)標(biāo)準(zhǔn)庫(kù)和第三方庫(kù)的依賴包處理上略有不同:

  • 針對(duì)標(biāo)準(zhǔn)庫(kù)的依賴包,plugin 中只會(huì)記錄 $GOROOT 向下的相對(duì)路徑;
  • 針對(duì)第三方庫(kù)的依賴包,plugin 會(huì)記錄完整 GOPATH 及包的完整路徑。這是因?yàn)? mathimg=GOPATH 允許多個(gè),為了避免混淆,會(huì)記錄每個(gè)依賴包的完整路徑;

而我編譯插件的環(huán)境的 GOPATH 與 peer 運(yùn)行環(huán)境的GOPATH 不一樣,導(dǎo)致 go 認(rèn)為兩者使用了不同版本的依賴包。(有點(diǎn)奇怪)

一種解決方案是,將插件的編譯直接放到 peer 容器中,即 peer 的運(yùn)行環(huán)境,這樣兩者的依賴包信息絕對(duì)一樣,這個(gè)方案稍顯麻煩(啟動(dòng)一個(gè)用于編譯的 peer 容器略微麻煩)。

我采用的解決方案是,仿造一個(gè)跟 peer 運(yùn)行環(huán)境一個(gè) $GOPATH 出來(lái)即可。

在原插件編譯環(huán)境中構(gòu)建一個(gè) peer 的 OGPATH 相同的路徑,然后將插件源碼和依賴包拷貝到這個(gè)臨時(shí)的GOPATH 下,臨時(shí)指定 $GOPATH 變量進(jìn)行編譯。

GOPATH=/opt/gopath go build -buildmode=plugin -o databackscc.so databackscc.go

實(shí)踐結(jié)果

系統(tǒng)鏈碼加載日志:

019-02-27 03:08:12.342 UTC [sccapi] deploySysCC -> INFO 017 system chaincode lscc/(github.com/hyperledger/fabric/core/scc/lscc) deployed
2019-02-27 03:08:12.342 UTC [cscc] Init -> INFO 018 Init CSCC
2019-02-27 03:08:12.342 UTC [sccapi] deploySysCC -> INFO 019 system chaincode cscc/(github.com/hyperledger/fabric/core/scc/cscc) deployed
2019-02-27 03:08:12.343 UTC [qscc] Init -> INFO 01a Init QSCC
2019-02-27 03:08:12.343 UTC [sccapi] deploySysCC -> INFO 01b system chaincode qscc/(github.com/hyperledger/fabric/core/scc/qscc) deployed
2019-02-27 03:08:12.343 UTC [sccapi] deploySysCC -> INFO 01c system chaincode (+lifecycle,github.com/hyperledger/fabric/core/chaincode/lifecycle) disabled
2019-02-27 03:08:12.343 UTC [DataBackSCC] Info -> INFO 001 Init Success
2019-02-27 03:08:12.343 UTC [sccapi] deploySysCC -> INFO 01d system chaincode databackscc/(/etc/hyperledger/fabric/plugin/databackscc.so) deployed

調(diào)用系統(tǒng)鏈碼日志:

2019-02-27 03:10:27.756 UTC [endorser] callChaincode -> INFO 049 [mychannel][b5660446] Entry chaincode: name:"databackcc" 
2019-02-27 03:10:27.759 UTC [DataBackSCC] Info -> INFO 003 Invoke
2019-02-27 03:10:27.759 UTC [DataBackSCC] Infof -> INFO 004 Not Exist, Create Dir /data/backup
2019-02-27 03:10:27.759 UTC [DataBackSCC] Infof -> INFO 005 Create File /data/backup/backup.txt
2019-02-27 03:10:27.759 UTC [DataBackSCC] Infof -> INFO 006 BackUp String: ''
2019-02-27 03:10:27.759 UTC [DataBackSCC] Infof -> INFO 007 BackUp String: 'b5660446c2ebdc43c113e6ffe09965e6ff7707ee711157332427d10da5ae3c91'

數(shù)據(jù)備份結(jié)果:

root@784af02a43be:/opt/gopath/src/github.com/hyperledger/fabric/peer# cat  /data/backup/backup.txt

b5660446c2ebdc43c113e6ffe09965e6ff7707ee711157332427d10da5ae3c91

總結(jié)

  1. 系統(tǒng)鏈碼插件功能的啟用,需要添加 pluginsenabled 編譯標(biāo)簽重新編譯;
  2. 插件的編譯需要保證合適的環(huán)境,保證依賴包相同;
  3. 系統(tǒng)鏈碼的調(diào)用生效是在鏈碼模擬執(zhí)行階段,跟業(yè)務(wù)鏈碼略微不同;
  4. 系統(tǒng)鏈碼插件的功能能大大拓展 fabric 中合約的能力,它能夠打破業(yè)務(wù)鏈碼的運(yùn)行環(huán)境,實(shí)現(xiàn)更多資源的訪問(wèn),甚至于鏈外資源進(jìn)行交互。(比如ipfs等)

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容