區塊鏈核心技術與應用/實戰篇


第7章 比特幣

1。比特幣特點

2。比特幣P2P網絡

3。比特幣的發行機制

4。比特幣的賬號系統

5。比特幣的生態系統

6。開發實施一個比特幣存證應用

第8章 以太坊 --公有鏈

1。以太坊關鍵概念

2。以太坊架構

3。以太坊智能合約

4。以太坊適用場景剖析

第9章 超級賬本Fabric --聯盟鏈

fabric里包括若干不同的項目,每個項目根據發展程度可處於5個階段:提案,孵化,活躍,棄用,終止。

截止2018.6,通過提案進入孵化或活躍的項目共10個:

區塊鏈框架類項目5個:Fabric,Sawtooth,Iroha,Buroow,Indy

區塊鏈工具類項目5個:Cello,Composer,Explorer,Quilt,Caliper

1。Fabric基礎架構

(一)。fabric總架構圖

(1)。網絡層

網絡層由多個分布式節點組成。這些節點構成了一個點對點(P2P)的網絡;

采用Gossip協議進行節點間互相發現和數據傳輸;

並采用gPRC的框架互相調用接口功能;

(2)。核心層

A。共識機制

核心層中的共識機制是區塊鏈系統的核心模塊,它確保各個節點對數據達成共識。

Fabric 1.0僅支持用於開發測試的SOLO模式和用於生產系統的Kafka方式。

其中Kafka實現的是CFT(Crash Fault Tolerant)的容錯類型,需要假定聯盟鏈網絡中沒有故意作惡的節點。

Fabric還允許用插拔的方式增加BFT(Byzantine Fault Tolerant)的容錯類型。

B。區塊鏈存儲

區塊鏈的存儲主要包含以文件形式存儲的鏈式區塊數據,以及在數據庫保存的鍵值對(Key-Value Pair)狀態數據。

其中鏈式區塊數據存放的是交易的原始數據區塊,通過區塊的哈希值形成防篡改的鏈式結構。

狀態數據庫的作用主要是加速對數據的訪問。因為區塊鏈數據采用鏈式順序存放,在讀取數據時通常需要遍歷整個鏈的數據塊,采用數據庫能夠從索引迅速定位到所需數據。

C。鏈碼管理

Fabric中的智能合約稱為“鏈碼”(chain code)

鏈碼部署在節點上,采用容器技術形成隔離的運行環境。

鏈碼的生命周期管理主要包括鏈碼的安裝、實例化、調用和終止等。

D。身份管理

作為聯盟鏈方案,Fabric包含管理成員身份的功能。

參與區塊鏈網絡的成員身份必須是明確的,成員之間知道彼此組織身份信息,每個交易都有確定的參與方和背書方,

這是絕大多數商用系統的需求。相比之下,許多公有鏈的用戶身份是匿名的,參與方無須確認身份信息。

(3)。服務層

服務層利用核心層的基礎功能,封裝成服務的形式,提供給應用端來使用。

A。賬本服務和交易服務通過核心層的共識算法和區塊鏈存儲,實現基本的區塊鏈數據操作能力。

B。鏈碼服務提供智能合約的功能封裝。

C。事件服務則提供應用偵聽系統事件並處理的功能。

D。權限服務根據成員和用戶的身份信息,對其操作權限進行控制,成為商業應用中安全管理的一部分。

(4)。接口層

接口層的目的是使Fabric的應用(客戶端)能夠方便地調用區塊鏈的服務。

接口主要以API的形式提供,能夠完成通道、鏈碼、交易等方面的操作。

為了便於編程語言的調用,Fabric提供了綁定不同語言的SDK,如Node和Java等的SDK。

此外,Fabric還提供了命令行接口CLI,可無須編程,通過命令直接調用Fabric的功能。

(二)。fabric主要組件

(三)。P2P網絡 (基於Gossip協議)

Fabric的節點組成了一個P2P網絡。每個節點既是請求者又是響應者。提供網絡資源和服務,達到資源共享。

Fabric中P2P技術主要用於網絡節點間的健康檢測和賬本同步。

節點間的P2P網絡由Gossip協議實現,在通道中的各個節點會持續廣播和接收Gossip消息。

Gossip內容:節點狀態,賬本數據,通道數據等信息

Gossip消息需發送者簽名,目的:防篡改,及信息隔離。

通過Gossip協議,受延遲,網絡分區或其他因素導致的賬本沒有同步的節點,最終均會同步到最新的賬本狀態。

(四)。通道(channel)

Fabric是以通道為基礎的多鏈多賬本系統。

(1)設計通道channel目的:提供成員間的隱私保護。(類比:群,群內成員可見,私有通信息的“子網”)

(2)通道由排序服務管理,創建通道時需定義它的

A。成員

B。組織:同一組織的節點會選舉主導節點(leading peer),負責接收從排序服務發來的區塊,並轉發給本組織其他節點。

C。錨節點(anchor peer):代表本組織與其他組織的節點交互,以發現通道中的所有節點。

D。排序服務節點

(3)通道初始配置信息記錄在區塊鏈的創世區塊(第一個區塊)中,若要更改,可增加一個新的配置區塊。

(4)fabric網絡中可能同時存在多個彼此隔離的通道,每個channel包含一條私有區塊鏈和一個私有賬本。

通道中可實例化一個或多個鏈碼,以操作區塊鏈上的數據。因此fabric是以通道為基礎的多鏈多賬本系統。

(五)。分布式賬本

fabric里的數據以分布式賬本的形式存儲。

世界狀態:賬本中的數據項以鏈值對的形式存放,賬本中所有的鏈值對構成了賬本的狀態(記錄所有數據的全部狀態改變),也稱世界狀態。

每個channel中有唯一的賬本,由channel中所有成員共同維護。

每個確認節點上都保存了它所屬通道的賬本的一個副本,因此是分布式賬本。

對賬本的訪問需要通過鏈碼實現對賬本鏈值的增,刪,改,查等操作。

賬本由區塊鏈和狀態DB組成:

(1)區塊鏈:記錄全部交易日志

(2)狀態DB:記錄賬本中所有鏈值對的當前值,相當於對當前賬本的交易日志做了索引

(六)。共識機制

Fabric網絡節點本質上是互相復制的狀態機,節點間需保持相同的賬本狀態。各節點需通過共識,對賬本狀態變化達成一致。

Fabric共識過程包括:背書,排序,校驗

(1)背書(endorsement)

背書節點對C端發來的交易預案進行合法性檢驗,然后模擬執行鏈碼得到交易結果,最后根據設定的背書邏輯判斷是否支持該交易預案。

A。支持交易預案:將預案簽名並發回給C端

B。不支持交易預案,返回C端錯誤信息

客戶端通常根據鏈碼的背書策略(定義需要哪些節點背書交易才有效),向一個/多個成員的背書節點發出背出請求。C端收集足夠背書后廣播交易才有效。

(2)排序(ordering)

由排序服務對交易進行排序,確定交易間的時序關系。

排序服務把一段時間內收到的交易進行排序,然后把排序后的交易打包成區塊,再把區塊廣播給通道中的成員。

Fabric1.0中的排序服務支持可插拔的架構。除了提供SOLO和Kafka模式外,用戶還可添加第三方的排序服務。

A。SOLO:單機確認模式,僅適合於開發測試

B。Kafka:基於Kafka開源的分布式數據流平台,具有高擴展性和容錯能力。適用於生產系統

注:Kafka只提供了CFT類型的容錯能力,即僅可對節點的一般故障失效容錯,缺乏對節點故意作惡行為進行容錯的能力。因此需第三方的容錯方案來支持BFT

排序服務采用輕量級設計,只完成確定交易順序的功能,不參與其他操作。

3)校驗(validation)

校驗階段:確認節點對排序后的交易進行一系列的檢驗,包括:

A。交易數據的完整性檢查(有沒有被篡改)

B。是否重復交易(雙花)

C。背書簽名是否符合背書策略的要求(收集足夠背書)

D。交易的讀寫集是否符合多版本並發控制MVCC(Multiversion Concurrency Control)的校驗

(七)。智能合約(鏈碼:chaincode)

智能合約:是一套以數字形式定義的承諾(Promises),包括合約參與方可在上面執行這些承諾的協議。

智能合約也稱“鏈碼”,分“用戶鏈碼”和“系統鏈碼”。通常所說的鏈碼為“用戶鏈碼”。

鏈碼安裝在背書節點上,需在某個通道上實例化並且定義相應背書策略后才能運行。

鏈碼部署后不可更改,可通過升級鏈碼發布新功能或修復問題。

Fabric中鏈碼運行在一個安全的Docker容器沙盒內,該容器由背書節點創建和管理,以隔離背書節點和鏈碼的運行環境。

(八)。成員服務提供者

fabric與其他公有鏈系統重要區別:Fabric具有成員身份和權限管理的能力。

成員服務提供者MSP(Membership Service Provider)

成員服務是成員服務提供者的具體實現,成員服務最重要的一個部件是參與者所持有的“身份證書”。該證書由CA簽發,當若干參與者的身份證書可追溯到同一根CA,則認為參與者處於同一信任鏈。

(九)。交易流程

2。架構詳細原理

(一)成員身份管理

(1)fabric屬於許可鏈,與公有鏈最大區別:

        在於要求參與者先注冊身份,該身份即為參與者在區塊鏈網絡中的標識。

(2)fabric中,身份證書是每個參與者在區塊鏈網絡中的標識,同時也是權限管理的基礎,

        證書采用了x.509標准且通過橢圓曲線密碼學算法來生成公私鑰對。

【1】成員服務提供者組件(MSP)

成員服務提供者組件MSP是對身份證書的抽象表達。

網絡中每個參與者都擁有MSP,它包含了參與者的身份證書,數字簽名,驗證算法,若干判斷身份是否有效的規則。

Fabric中通道(重要概念)實現了數據隔離,只有授權的網絡實體才能訪問通道內的數據。

   --每個channel定義一個/多個MSP實體用於控制數據的訪問權限。

   --其中每個MSP均對應一個根CA/中間CA。只有由MSP對應的CA簽發的身份證書才能通過相應的MSP身份校驗。

【2】節點MSP:定義了節點在網絡中的身份

A。節點的身份屬性

在Fabric中,每個成員的身份都有一些特殊的屬性以進行某些權限操作,

如某個身份的attrs中有hf.Registrar.Roles字段且該字段的值為“client, user, peer”

則說明該身份可以簽發其他身份證書,可簽發的證書類型為client、user和peer。

除了上述的hf.Registrar. Roles外,還有hf.Revoker和hf.IntermediateCA兩個常用屬性分別對應該實體能否注銷用戶和簽發中間CA

B。節點MSP基礎組成

在Fabric啟動peer節點或排序節點前,需要先配置localMspId環境變量來指定節點的MSP名稱。

其次是mspConfigPath環境變量,該變量為節點設定了MSP存放的目錄,節點運行時會從該目錄加載相應的MSP數據。

MSP的目錄結構包括如下子目錄(以下提及的證書均采用X.509標准)

 

cacerts:存放一組CA的自簽名根證書,這些證書構成整個組織證書信任的基礎。

intermediatecerts(可選):存放一組受信任的中間證書,這組證書充當了中間CA,可用於驗證證書的合法性,由根證書簽發。

admincerts:存放一組MSP管理員的身份證書,管理員擁有權限修改本MSP的配置,如刪除cacerts目錄中的某些證書。

signcerts:存放本節點的身份證書,身份證書必須通過本組織的CA簽發。

keystore:存放與本節點身份證書對應的私鑰,用於對消息簽名。

tlscacerts:存放用於TLS通信協議的自簽名證書。

crls(可選):存放若干已經撤銷的身份證書。

config.yaml(可選):存放組織單元列表。若身份證書中的ou域包含在ous中,且由cacerts中的根證書簽發,則該身份證書有效。

 

假設存在MSP實體Org1MSP,若某個網絡實體要通過Org1MSP校驗,則該實體的證書必須滿足以下條件:

❑證書鏈的根在Org1MSP的cacerts目錄中;

❑證書不在Org1MSP的crls目錄中;

❑若Org1MSP定義了組織單元,則證書中的ou域必須包含組織單元中定義的一個或多個元素。

C。生成節點的MSP目錄

若需要生成節點的上述msp目錄文件,可使用cryptogen工具和Fabric CA server 兩種方法

1)使用cryptogen工具(fabric命令行工具)生成msp目錄

一個簡單的crypto-config.yaml例子:

更多配置可參考cryptogen的描述 

運行命令:cryptogen,會生成crypto-config目錄,結構:

在編輯好crypto-config.yaml文件后,可用下述命令生成msp目錄

//生成msp目錄
$cryptogen generate --config=./crypto-config.yaml

2)通過fabric-CA server生成msp目錄

CA節點是每個組織給本組織成員提供數字證書的身份信息的服務。CA節點中運行的主要是Fabric-CA server這個服務。

該服務啟動的方法有2種,分別是通過fabric-ca-server二進制可執行文件直接啟動和通過Docker容器鏡像啟動。

無論選擇哪種方式,都可以很方便地配置server的參數。通常與fabric-ca-server配套使用的還有客戶端fabric-ca-client,

該客戶端程序可以方便用戶與CA server之間的交互。

下面介紹2種啟動Fabric-CA Server的方法:

① 使用fabric-ca-server可執行文件啟動CA服務。

下面以Ubuntu16.0.4為例。請先確保Go語言已經安裝並且設置GOPATH環境變量。

❑安裝libtool和libtdhl-dev:$ sudo apt install libtool libltdl-dev

❑安裝Fabric-CA server和Fabric-CA client:$ go get -u github.com/hyperledger/fabric-ca/cmd/...

❑創建test_ca文件夾,存放Fabric-CA server的配置文件:$ mkdir $GOPATH/src/test_ca && cd $GOPATH/src/test_ca

❑初始化Fabric-CA server:$ fabric-ca-server init -b admin:adminpw

  該命令在當前目錄創建CA server的證書、私鑰和數據庫等文件,其中fabric-ca-server-config.yaml為CA server配置文件。

  配置文件提供了豐富的參數設置以滿足不同的需求。參數-b為server設定管理員的初始ID和secret。

  管理員可從客戶端通過admin:adminpw來獲取默認管理員的根證書,以進行后續的證書簽發、證書撤銷等操作。

❑啟動Fabric-CA server:$ fabric-ca-server start

   該命令讀取當前目錄下fabric-ca-server-config.yaml配置文件,載入相應的證書和私鑰並啟動CA server服務。

② 通過Docker容器方式啟動CA服務

先確保系統已安裝17.03或以上版本的Docker程序,以及1.11或以上版本的docker-compose程序。

❑從Docker Hub上下載fabric-ca鏡像:$ docker pull hyperledger/fabric-ca:x86_64-1.0.0

❑創建test_ca文件夾,存放Fabric-CA server的配置文件:$ mkdir $GOPATH/src/test_ca && cd $GOPATH/src/test_ca

❑創建docker-compose.yaml文件:

fabric-ca-server:

  image: hyperledger/fabric-ca:x86_64-1.0.0

  container_name: fabric-ca-server

  ports:

    - "7054:7054"

  environment:

    - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server

  volumes:

    - "./fabric-ca-server:/etc/hyperledger/fabric-ca-server"

  command: bash -c 'while true; do sleep 3; done'

 

❑啟動CA服務:

$ docker-compose -f docker-compose.yaml up -d

$ docker exec fabric-ca-server fabric-ca-server start -b admin:adminpw

 

當容器啟動后,會在當前目錄下新創建fabric-ca-server目錄以存放CA Server的證書、私鑰配置文件等

 

③fabric-ca-client客戶端的使用方法

 

使用fabric-ca-client命令可以方便地跟Fabric CA Server進行交互。一些常用的操作如下:

 

❑登錄管理員賬戶,獲取證書簽發權限。

 

上述啟動CA Server的流程中定義了管理員的ID和密碼分別為admin和adminpw,通過以下命令登錄管理員用戶:

 

$ export FABRIC_CA_CLIENT_HOME=$HOME/fabric-ca/clients/admin

$ fabric-ca-client enroll -u http://admin:adminpw@localhost:7054

 

執行上面的命令,會在$FABRIC_CA_CLIENT_HOME目錄下生成一個msp目錄和一個fabric-ca-client-config.yaml配置文件。

 

❑注冊成員peer1:

 

$ export FABRIC_CA_CLIENT_HOME=$HOME/fabric-ca/clients/admin

$ fabric-ca-client register --id.name peer1--id.type peer --id.secret peer1pw

 

在上述命令中,通過id.name、id.type、id.secret分別聲明了注冊用戶的名稱、類型和登錄密碼。

 

❑登錄新注冊的peer1:

 

$ export FABRIC_CA_CLIENT_HOME=$HOME/fabric-ca/clients/peer1

$ fabric-ca-client enroll -u http://peer1:peer1pw@localhost:7054

 

第一條命令通過環境變量FABRIC_CA_CLIENT_HOME為peer1指定了主目錄$HOME/fabric-ca/clients/peer1,該目錄用於存放Fabric server返回的文件。

 

第二條命令用於獲取peer1用戶的證書文件,該命令會在$HOME/fabric-ca/clients/peer1目錄下生成一個msp目錄,其中msp目錄存放着peer1的證書和對應的私鑰。

 

❑注銷成員peer1:

 

$ export FABRIC_CA_CLIENT_HOME=$HOME/fabric-ca/clients/admin

$ fabric-ca-client revoke -e peer1

 

上述命令使用admin的身份來注銷成員,參數-e指定需要被注銷的成員。

 

fabric-ca-server配置參數詳細描述

 

fabric-ca-client配置參數詳細描述

 

D。通道MSP

除了節點需要MSP,通道也需要MSP。

只有由MSP對應的CA簽發的身份證書才能通過相應MSP的身份校驗。

在通道中定義MSP可實現對通道中數據的訪問權限控制。

(二)通道的結構

(1)系統通道和應用通道

(2)使用configtxgen工具生成通道的配置

configtxgen是fabric提供的工具,用於生成通道所需要的配置文件。

configtxgen工具以一個yaml文件作為輸入,一般稱為configtx.yaml。該文件定義了將要創建通道的配置信息。

該文件通常包括以下部分

1)Profiles:包含了通道的配置模板,通過configtxgen工具的參數-profile來指定使用哪個模板。

2)Organizations:定義了組織以及與之對應的MSP。

3)Orderer:定義系統通道的相關配置,如排序節點地址、共識算法。

4)Application:定義應用通道相關配置,被profile引用。

profile定義如下:

Profiles:

    Genesis:

        Orderer:     ##系統通道必須orderer和Consortiums 兩部分

            <<: *OrdererDefaults ##引用orderer字段定義的內容,通過該引用定義系統通道的配置信息,如共識算法,orderer地址等

            Organizations:   ##定義參與此系統通道的組織信息

                - *OrdererOrg

        Consortiums: ##定義為哪些聯盟提供服務,即只有聯盟中的組織能夠創建通道

            SampleConsortium:

                Organizations:

                    - *PeerOrg1

                    - *PeerOrg2

    Channel:

        Consortium: SampleConsortium  ##Consortium指定了與應用通道相關聯聯盟的名稱

        Application:  ##應用通道必須Application和Consortium兩部分.Application定義了應用通道內的組織信息

            <<: *ApplicationDefaults

            Organizations: ##定義orderer類型和普通類型(每個普通類型組織均需定義一個錨節點,用於代表本組織與其他組織通信)

                - *PeerOrg1

                - *PeerOrg2

 

(3)通道相關命令

對通道的管理可通過命令行的方式。與通道相關的命令如下。

❑peer channel create:用於創建通道,主要參數有-c、-f、-o,分別用於指定通道ID、configtx的路徑和orderer的地址。

❑peer channel fetch:抓取通道中的特定區塊,通過-c和-f參數來指定通道ID和orderer地址。

❑peer channel join :加入通道,通過-b參數指定初始區塊。

❑peer channel list :列出peer加入的通道。

❑peer channel update:簽名並且發送configtx以升級通道配置,需要通過-c、-f、-o參數分別指定通道ID、configtx的路徑以及排序節點的地址。

 

(4)動態修改通道配置

在通道創建后,通道相關的配置以區塊的形式存在於通道的賬本中。如果需要修改通道的配置,可通過生成新的配置區塊去更新。修改通道配置的步驟如下:

❑通過SDK或CLI獲得最新的配置區塊;

❑編輯配置區塊;

❑計算配置更新量;

❑為配置區塊添加配置更新量;

❑SDK或CLI簽名並發送配置區塊。

若新的配置區塊通過驗證,則通道配置以最新配置區塊為准。

由於獲取的配置區塊以二進制的proto格式返回,導致配置區塊的信息難以直接閱讀。

一般通過Fabric自帶的configtxlator工具來把配置區塊轉化為JSON格式用以閱讀和編輯。

當編輯完成后再次使用工具把修改好的JSON文件轉化為proto格式,最后把proto格式的配置區塊發回給排序節點驗證

(三)鏈碼

鏈碼運行在背書節點上的Docker容器沙盒中,以便與背書節點的其他進程隔離。

(1)鏈碼的生命周期管理

Fabric提供命令行工具管理鏈碼的生命周期,常用命令如下。

❑peer chaincode install:把鏈碼打包成可部署格式,並將其存入到背書節點的文件系統目錄下(CORE_PEER_FILESYSTEMPATH/chaincode)。

❑peer chaincode instantiate:把安裝到背書節點上的鏈碼實例化到指定的通道上。該命令會在節點上創建運行鏈碼的Docker容器,並初始化鏈碼。

❑peer chaincode invoke:調用指定鏈碼,若執行交易的節點還沒創建運行鏈碼的容器,則背書節點會先創建該容器再執行交易。

❑peer chaincode query:查詢指定鏈碼,若執行交易的節點還沒創建運行鏈碼的容器,則背書節點會先創建該容器再執行交易。該交易只查詢節點上的狀態,不生成區塊。

❑peer chaincode package:把鏈碼打包成可部署格式。

❑peer chaincode signpackage:簽名打包后的鏈碼。

❑peer chaincode upgrade:升級鏈碼,需要先用peer chaincode install命令安裝最新的代碼,然后使用本命令來升級已經實例化的代碼。

(2)鏈碼的背書策略

(3)鏈碼開發

鏈碼開發過程中需要實現鏈碼接口。交易的類型決定了哪個接口函數將會被調用,

如instantiate和upgrade類型會調用鏈碼的Init接口,而invoke類型的交易則調用鏈碼的Invoke接口。

鏈碼的接口定義如下:

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

 

下面通過一個例子講解鏈碼的開發流程。示例鏈碼根據交易的類型創建鍵值對並記錄到賬本中,或者根據鍵名到賬本中查找與之相對應的值。

請先確保Go語言環境已經安裝並且正確設置GOPATH環境變量。

step1:創建鏈碼存放目錄

創建keyValueStore目錄以存放鏈碼,同時進入目錄:

mkdir $GOPATH/src/keyValueStore

cd $GOPATH/src/keyValueStore

創建並編輯鏈碼文件keyValueStore.go。完整代碼如下

import (
        "fmt"
        "github.com/hyperledger/fabric/core/chaincode/shim"
        "github.com/hyperledger/fabric/protos/peer"
)
type KeyValueStore struct {
    }
func (t * KeyValueStore) Init(stub shim.ChaincodeStubInterface) peer.Response {
    args := stub.GetStringArgs()
    if len(args) ! = 2 {
        return shim.Error("Incorrect arguments. Expecting a key and a value")
    }
    err := stub.PutState(args[0], []byte(args[1]))
    if err ! = nil {
        return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
    }
    return shim.Success(nil)
}
func (t *KeyValueStore) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
        fn, args := stub.GetFunctionAndParameters()
        var result string
        var err error
        if fn == "set" {
                result, err = set(stub, args)
        } else {
                    result, err = get(stub, args)
            }
            if err ! = nil {
                    return shim.Error(err.Error())
            }
            return shim.Success([]byte(result))
    }
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
     if len(args) ! = 2 {
         return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
      }
     err := stub.PutState(args[0], []byte(args[1]))
     if err ! = nil {
         return "", fmt.Errorf("Failed to set asset: %s", args[0])
        }
            return args[1], nil
    }
    func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
            if len(args) ! = 1 {
                    return "", fmt.Errorf("Incorrect arguments. Expecting a key")
            }
            value, err := stub.GetState(args[0])
            if err ! = nil {
                    return "", fmt.Errorf("Failed to get asset: %s with error: %s",
                                          args[0], err)
            }
            if value == nil {
                    return "", fmt.Errorf("Asset not found: %s", args[0])
            }
            return string(value), nil
    }
    func main() {
            if err := shim.Start(new(KeyValueStore)); err ! = nil {
              fmt.Printf("Error starting KeyValueStore chaincode: %s", err)
            }
    }

 

step2:鏈碼的源代碼分析:

1)導入頭文件。

鏈碼必須依賴chaincode shim包和peer protobuf包,它們分別用於鏈碼的控制與數據傳輸。

其次定義KeyValueStore類型,作為chaincode shim的載體。

package main

import (

        "fmt"

        "github.com/hyperledger/fabric/core/chaincode/shim"

        "github.com/hyperledger/fabric/protos/peer"

)

type KeyValueStore struct {

    }

 

2)實現Init方法。

Init方法通過shim.ChaincodeStubInterface接口來獲取實例化鏈碼交易的相關信息。

該接口的GetStringArgs方法可獲取交易傳給鏈碼的參數。

鏈碼實例化時接收key和value兩個參數,因此先對參數個數進行驗證。

若驗證通過,則將第一個和第二個參數分別作為key和value存入到賬本中。

把狀態存入賬本需要借助shim.ChaincodeStubInterface接口PutState方法來完成。

由於賬本中的數據都以鍵值對的形式儲存,因此該方法也只接受key、value兩個參數。其中value為byte格式,里面還包含多個JSON格式的鍵值對。

由於執行結果需要以消息的形式返回給客戶端,因此還需要把返回消息封裝成fabric/protos/peer中的Response格式。

值得注意的是,鏈碼升級的時候都會調用Init方法,編寫升級鏈碼時應注意Init方法的實現,以避免重新初始化或覆蓋上一版本的賬本狀態。

func (t * KeyValueStore) Init(stub shim.ChaincodeStubInterface) peer.Response {

    args := stub.GetStringArgs()

    if len(args) ! = 2 {

        return shim.Error("Incorrect arguments. Expecting a key and a value")

    }

    err := stub.PutState(args[0], []byte(args[1]))

    if err ! = nil {

        return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))

    }

    return shim.Success(nil)

}

 

3)實現Invoke方法。

與Init方法類似,Invoke方法通過shim.ChaincodeStubInterface的GetFunctionAndParameters方法來獲取invoke交易的參數。

其中返回的fn與args分別為交易調用的具體函數名以及相應參數。此時Invoke方法進一步判斷fn的值以進行下一步操作(set或者get),並把操作結果存放在result變量中以返回操作結果。

func (t *KeyValueStore) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

    fn, args := stub.GetFunctionAndParameters()

    var result string

    var err error

    if fn == "set" {

            result, err = set(stub, args)

    } else {

            result, err = get(stub, args)

    }

    if err ! = nil {

            return shim.Error(err.Error())

    }

    return shim.Success([]byte(result))

}

為了完成對賬本的讀寫,鏈碼還需要實現以下2個方法。

❑set:把輸入的鍵值對記錄在賬本中。

❑get:根據鍵讀取賬本中與之對應的值。

4)實現get和put方法。

正如前面所說,invoke方法根據fn的值來執行相應的get或put函數。這兩個函數也需要shim.ChaincodeStubInterface接口來訪問賬本數據。

 func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {

        if len(args) ! = 2 {

            return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")

        }

        err := stub.PutState(args[0], []byte(args[1]))

        if err ! = nil {

            return "", fmt.Errorf("Failed to set asset: %s", args[0])

        }

        return args[1], nil

    }

    func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {

        if len(args) ! = 1 {

            return "", fmt.Errorf("Incorrect arguments. Expecting a key")

        }

        value, err := stub.GetState(args[0])

        if err ! = nil {

            return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0],err)

        }

        if value == nil {

            return "", fmt.Errorf("Asset not found: %s", args[0])

        }

        return string(value), nil

    }

 

5)實現主函數main()

鏈碼需要在main函數中調用shim.Start()方法用於鏈碼的部署。

func main() {

    if err := shim.Start(new(KeyValueStore)); err ! = nil {

        fmt.Printf("Error starting KeyValueStore chaincode: %s", err)

    }

}

 

step3:測試鏈碼

鏈碼的測試需要通過完整的Fabric網絡。使用官方提供的例子可以快速構建測試網絡,從而簡化鏈碼的開發流程。這里介紹搭建測試網絡的步驟。

1)參考9.3.1節安裝示例代碼庫。

2)進入fabric-samples目錄:

$ cd $GOPATH/src/github.com/hyperledger/fabric-samples

3)把新編寫的鏈碼放入fabric-samples的chaincode目錄下:

$ cp -r $GOPATH/src/keyValueStore ./chaincode

4)進入chaincode-docker-devmode目錄並啟動網絡,命令中會創建一個名為myc的通道:

$ cd chaincode-docker-devmode

$ docker-compose -f docker-compose-simple.yaml up -d

5)進入chaincode容器,編譯並運行鏈碼:

$ docker exec -it chaincode

$ cd keyValueStore && go build

$ export CORE_PEER_ADDRESS=peer:7051

$ export CORE_CHAINCODE_ID_NAME=mycc:0

$ ./keyValueStore

$ exit

6)進入CLI容器並初始化鏈碼,鏈碼ID為mycc,版本號為0,部署的通道名稱是myc:

$ docker exec -it cli bash

$ peer chaincode install -p chaincodedev/chaincode/keyValueStore -n mycc -v 0

$ peer chaincode instantiate -n mycc -v 0-c '{"Args":["a", "10"]}' -C myc

7)Invoke和Query鏈碼:

$ peer chaincode query -n mycc -c '{"Args":["query", "a"]}' -C myc

$ peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C myc

$ peer chaincode query -n mycc -c '{"Args":["query", "a"]}' -C myc

正常情況下,2次query返回的結果分別為10和20。

開發鏈碼時可以通過上述過程進行測試,但需避免使用相同的鏈碼ID,以免鏈碼實例化失敗。另外,對於鏈碼升級來說,鏈碼的ID應該保持不變,同時新鏈碼的版本號需要比先前實例化的版本高,並通過upgrade交易來更新鏈碼在通道中的狀態。

假設對鏈碼keyValueStore.go進行了更改,並把最新的鏈碼保存在$GOPATH/src/keyValueStoreNew下,則升級鏈碼的操作如下。

1)進入fabric-samples目錄並拷貝最新鏈碼到chaincode目錄:

$ cd $GOPATH/src/fabric-samples

$ cp -r $GOPATH/src/keyValueStoreNew ./chaincode

2)進入chaincode容器,編譯並運行更新后的鏈碼:

$ docker exec -it chaincode bash

$ cd keyValueStoreNew && go build

$ export CORE_PEER_ADDRESS=peer:7051

$ export CORE_CHAINCODE_ID_NAME=mycc:1

$ ./keyValueStoreNew

$ exit

3)進入cli容器並升級鏈碼:

$ docker exec -it cli bash

$ peer chaincode install -p chaincodedev/chaincode/keyValueStoreNew -n mycc -v 1

$ peer chaincode upgrade -n mycc -v 1-c '{"Args":["a", "10"]}' -C myc

至此升級鏈碼完畢,可以對最新的鏈碼mycc進行操作。

(4)ChaincodeStubInterface常用的接口方法

❑GetTXID:獲取交易的ID;

❑GetCreator:獲取交易發起者的信息;

❑GetState(key):獲取賬本中的key值;

❑PutState(key, value):往賬本中的key寫入value;

❑DelState(key):刪除賬本中的key。

3。應用開發流程

示例:構建完整的區塊鏈網絡,包括成員結構定義,節點的啟動及通道的創建等。

本示例通道中只有一個組織參與,可修改通道配置達到動態添加組織目的。

注:

若mychannl中本來就已經存在多個組織,則無法通過命令行工具進行通道的升級。

此時正確的方法是把構造好的更新交易通過SDK的signChannelConfig方法收集所有成員的簽名,

然后通過updateChannel方法向排序節點發送帶有簽名的交易。若排序節點對簽名的驗證通過,則執行更新通道操作。

 

 Step1:前期准備

假設已准備運行環境為Ubuntu 16.04的主機,並正確安裝docker、docker-compose、golang以及git等常用軟件。
1)從下面地址下載本文例子的代碼庫。例子源自Fabric官方示例代碼。為了說明開發原理,筆者做了少量修改。
$ cd $GOPATH/src
$ git clone https://github.com/LordGoodman/fabric-samples.git
2)下載Fabric常用工具集:
    $ cd~/Downloads
    $ curl https://nexus.hyperledger.org/content/repositories/releases/org/hyperledger/
fabric/hyperledger-fabric/linux-amd64-1.0.0/hyperledger-fabric-linux-amd64-1.0.0.tar.gz
| tar xz
3)把工具集加入到PATH環境變量中:
$ echo 'export PATH=$PATH:$HOME/Downloads/bin' >>~/.profile
$ source~/.profile
4)下載Fabric的Docker鏡像:
$ cd $GOPATH/src/fabric-samples/scripts && bash fabric-preload.sh

Step2:定義fabric集群

為了部署車輛登記應用,下面將啟動一個簡單的Fabric集群。

該集群僅包含了一個peer節點(同時具有背書節點和確認節點的功能)、一個排序節點以及一個CA組織。

peer節點的證書由該CA組織簽發。

A.證書以及通道的初始區塊生成

啟動Fabric集群之前,先使用cryptogen和configtxgen來生成必要的身份證書(存放在crypto-config目錄)、通道初始區塊(存放在config目錄)等文件,

$GOPATH/src/fabric-samples/basic-network目錄下的配置文件crypto-config.yaml定義了9.3.2節中描述的集群。

同時,在同一目錄下的configtx.yaml文件定義了只包含一個組織的應用通道,鏈碼將會在該通道中部署。

默認情況下,$GOPATH/src/fabric-samples/basic-network下已經預先生成好了crypto-config和config目錄。

用戶想要重新生成這兩個目錄可以運行generate.sh腳本。

該腳本會先刪除目錄,然后再通過cryptogen、configtxgen以及相應的配置文件重新生成。

B.編寫peer的docker-compose文件

由於Fabric中的節點都通過Docker容器來運行,因此當文件准備好后,還需要編寫docker-compose.yaml文件。

該文件定義了容器所使用的鏡像以及容器內的環境變量。

在$GOPATH/src/fabric-samples/basic-network目錄下的docker-compose.yaml文件已經定義好了本章所需的集群。

下面以該文件中peer容器配置為例進行簡要分析

eer0.org1.example.com:
    container_name: peer0.org1.example.com
    image: hyperledger/fabric-peer
    environment:
      - CORE_PEER_ID=peer0.org1.example.com
      - CORE_LOGGING_PEER=debug
      - CORE_CHAINCODE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      working_dir:/opt/gopath/src/github.com/hyperledger/fabric
command: peer node start
ports:
      -7051:7051
      -7053:7053
    volumes:
        - /var/run/:/host/var/run/
- ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.
com/msp:/etc/hyperledger/msp/peer
        - ./config:/etc/hyperledger/configtx
    depends_on:
      - orderer.example.com
    networks:
      - basic

上述文件定義了名為peer0.org1.example.com的容器。容器使用的鏡像為hyperledger/fabric-peer:1.0.0,而environment字段則定義了容器中的環境變量。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM