第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指定需要被注銷的成員。
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字段則定義了容器中的環境變量。