最近開始研究區塊鏈,對這個新興的技術有了基本概念上的了解,所以打算基於一個開源項目做做實驗。如果是做數字貨幣,那么比特幣的源代碼是最好的了,不過這算是區塊鏈1.0吧,已經有很多改進的競爭幣和山寨幣出來了,所以打算對區塊鏈2.0,也就是智能合約入手。
智能合約比較成功的就是以太坊了。以太坊主要是公有鏈,其實對企業應用來說並不是特別合適,而且本身並沒有權限控制功能,面向企業的,主要還是超級賬本HyperLedger的Fabric和剛剛開源出來的R3的Corda。關於這些項目的應用場景和區別,我覺得這篇文章寫的比較好:http://geek.csdn.net/news/detail/134967
經過比較,覺得Fabric目前比較合適,所以就以這個項目為基礎,學習智能合約。
一、環境准備
1.1 安裝VirtualBox並在其中安裝好Ubuntu
這一步其實沒啥好說的,下載好最新版的VirtualBox,下載Ubuntu Server,我用的是16.10 X64。在安裝完Ubuntu后,需要保證apt source是國內的,不然如果是國外的話會很慢很慢的。具體做法是
sudo vi /etc/apt/sources.list
打開這個apt源列表,如果其中看到是http://us.xxxxxx之類的,那么就是外國的,如果看到是http://cn.xxxxx之類的,那么就不用換的。我的是美國的源,所以需要做一下批量的替換。在命令模式下,輸入:
:%s/us./cn./g
就可以把所有的us.改為cn.了。然后輸入:wq即可保存退出。
sudo apt-get update
更新一下源。
然后安裝ssh,這樣接下來就可以用putty或者SecureCRT之類的客戶端遠程連接Ubuntu了。
sudo apt-get install ssh
1.2 安裝Docker
安裝Docker也會遇到外國網絡慢的問題,幸好國內有很好的鏡像,推薦DaoClound,安裝Docker的命令是:
curl -sSL https://get.daocloud.io/docker | sh
安裝完成后,運行以下腳本將當前用戶添加到Docker的組中
sudo usermod -aG docker studyzy
重新登錄當前用戶,接下來修改 Docker 服務配置(/etc/default/docker
文件)。
sudo vi /etc/default/docker
添加以下內容:
DOCKER_OPTS="$DOCKER_OPTS -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --api-cors-header='*'"
接下來就需要設置國內的Docker鏡像地址,需要注冊一個賬號,然后在加速器頁面提供了設置Docker鏡像的腳本,加速器頁面是:
https://www.daocloud.io/mirror 我提供的腳本是:
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://d4cc5789.m.daocloud.io
運行完腳本后,重啟Docker服務
sudo service docker restart
1.3 安裝docker-compose
Docker-compose是支持通過模板腳本批量創建Docker容器的一個組件。在安裝Docker-Compose之前,需要安裝Python-pip,運行腳本:
sudo apt-get install python-pip
安裝完成后,接下來從DaoClound安裝Docker-compose,運行腳本:
curl -L https://get.daocloud.io/docker/compose/releases/download/1.10.1/docker-compose-`uname -s`-`uname -m` > ~/docker-compose sudo mv ~/docker-compose /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose
二、Fabric部署
2.1 下載Fabric鏡像
Fabric的Docker鏡像是在https://hub.docker.com/r/hyperledger/ 我們要做實驗主要用到peer,baseimage,membersrvc,先現在Peer和membersrvc,這兩個鏡像提供了latest版本,所以直接pull下來即可。
docker pull hyperledger/fabric-peer
docker pull hyperledger/fabric-membersrvc
但是baseimage是沒有latest版本,所以我們可以下載一個新一點的版本,然后rename成latest。
docker pull hyperledger/fabric-baseimage:x86_64-0.3.0 docker tag hyperledger/fabric-baseimage:x86_64-0.3.0 hyperledger/fabric-baseimage:latest
現在我們運行docker images命令,可以看到我們准備好的鏡像:
REPOSITORY TAG IMAGE ID CREATED SIZE
hyperledger/fabric-baseimage latest f4751a503f02 7 days ago 1.27 GB
hyperledger/fabric-baseimage x86_64-0.3.0 f4751a503f02 7 days ago 1.27 GB
hyperledger/fabric-membersrvc latest b3654d32e4f9 3 months ago 1.42 GB
hyperledger/fabric-peer latest 21cb00fb27f4 3 months ago 1.42 GB
2.2 使用Git下載Docker-compose模板
如果沒有安裝Git,那么需要先安裝Git,安裝Git很簡單:
sudo apt-get install git
感謝yeasy提供的很好的HyperLedger的模板,我們先克隆到本地:
git clone https://github.com/yeasy/docker-compose-files
2.3 以PBFT模式啟動Fabric
先進入Git下載下來的Docker-compose目錄:
cd docker-compose-files/hyperledger/0.6/pbft/
這里提供了多種模式的啟動方案,一種是啟動4個節點的Peer,沒有權限認證:4-peers.yml 另一種是在4節點Peer的基礎上,再加上MembershipService節點,也就是需要權限認證的:4-peers-with-membersrvc.yml 另外還有再進一步,提供了web的Explorer的:4-peers-with-membersrvc-explorer.yml
這里我們就簡單點,直接忽略掉MembershipService和Explorer,只啟用4個節點的PBFT:
docker-compose -f 4-peers.yml up
系統會打印出啟動的日志:
Creating network "pbft_default" with the default driver Creating pbft_vp0_1 Creating pbft_vp3_1 Creating pbft_vp2_1 Creating pbft_vp1_1 ……
至此,我們的環境搭建完畢,接下來我們就可以在上面跑鏈上代碼了。
三、測試Fabric
3.1 在CLI中測試Example02
我們前面創建了4個容器,開啟另外一個命令行窗口,輸入docker ps命令,可以看到當前容器的狀態:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2131cede4ade hyperledger/fabric-peer:latest "peer node start" 3 minutes ago Up 3 minutes 7050-7059/tcp pbft_vp1_1
5acea88f21bc hyperledger/fabric-peer:latest "peer node start" 3 minutes ago Up 3 minutes 7050-7059/tcp pbft_vp2_1
546b103d904d hyperledger/fabric-peer:latest "peer node start" 3 minutes ago Up 3 minutes 7050-7059/tcp pbft_vp3_1
327ab874b2e3 hyperledger/fabric-peer:latest "peer node start" 3 minutes ago Up 3 minutes 0.0.0.0:7050->7050/tcp, 7051-7059/tcp pbft_vp0_1
這里我們可以看到,最后一個容器pbft_vp0_1其啟用了端口映射的,容器上面的7050端口會映射到Ubuntu的7050端口上。我們要執行命令行代碼,需要先連接到這個容器內部:
docker exec -it pbft_vp0_1 bash
進入容器后,命令行會變為:root@vp0:/opt/gopath/src/github.com/hyperledger/fabric#
這里的容器已經幫我們把測試代碼都放在了容器里面,所以我們不需要再下載測試代碼。
3.1.1部署Go語言的ChainCode並初始化
下面我們部署Example02到Fabric上:
peer chaincode deploy -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -c '{"Function":"init", "Args": ["a","100", "b", "200"]}'
這個示例是初始化兩個賬戶a和b,a有余額100元,b有余額200元,這是運行結果:
root@vp0:/opt/gopath/src/github.com/hyperledger/fabric# peer chaincode deploy -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -c '{"Function":"init", "Args": ["a","100", "b", "200"]}' 08:37:12.187 [chaincodeCmd] chaincodeDeploy -> INFO 001 Deploy result: type:GOLANG chaincodeID:<path:"github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"init" args:"a" args:"100" args:"b" args:"200" > Deploy chaincode: ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539 08:37:12.188 [main] main -> INFO 002 Exiting.....
這里我們可以看到已經部署成功,並返回了ChainCode的ID:ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
3.1.2查詢ChainCode
下面我們把這個ID放入一個變量中:
CC_ID="ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539"
下面我們來查詢一下a賬戶的余額:
peer chaincode query -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}'
這是運行結果:
root@vp0:/opt/gopath/src/github.com/hyperledger/fabric# peer chaincode query -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}' 08:41:17.780 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully queried transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"query" args:"a" > > Query Result: 100 08:41:17.781 [main] main -> INFO 002 Exiting.....
可以看到查詢結果是100元。
注意:這里如果遇到了拋出異常:
LedgerError - ResourceNotFound: ledger: resource not found
那么就得看log,到底是什么地方錯了,我們可以切換回docker-compose的那個窗口,那個窗口會打印錯誤日志,或者我們再打開一個窗口,運行命令:
docker logs -f pbft_vp0_1
查看peer日志,找到原因。我之前一直遇到這個異常,后來發現是baseimage沒有latest版的造成的,所以2.1步驟不能出錯。
3.1.3調用ChainCode
接下來,我們讓a給b轉賬10元,運行命令:
peer chaincode invoke -n ${CC_ID} -c '{"Function": "invoke", "Args": ["a", "b", "10"]}'
這是調用后的結果:
root@vp0:/opt/gopath/src/github.com/hyperledger/fabric# peer chaincode invoke -n ${CC_ID} -c '{"Function": "invoke", "Args": ["a", "b", "10"]}' 08:44:19.903 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully invoked transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"invoke" args:"a" args:"b" args:"10" > > (94c9cbd9-ea04-436f-9cf8-3436303554d2) 08:44:19.904 [main] main -> INFO 002 Exiting.....
3.1.4檢查調用ChainCode后的結果
現在已經轉賬完畢,我們再來查詢一下a賬戶的余額:
peer chaincode query -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}'
查詢結果:
root@vp0:/opt/gopath/src/github.com/hyperledger/fabric# peer chaincode query -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}' 08:45:33.937 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully queried transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"query" args:"a" > > Query Result: 90 08:45:33.937 [main] main -> INFO 002 Exiting.....
可以看到,a賬戶變成90元了。
3.2 在REST API中測試Example02
前面我們已經說到,容器的7050端口會映射成Ubuntu的7050端口,我們在Ubuntu下,運行ifconfig,可以看到Ubuntu的IP,然后我們回到Windows,就可以通過REST的Client來測試,這里我喜歡用Chrome的插件DHC,很好用,強烈推薦!不過要翻牆才能裝。
這里我Ubuntu的IP是192.168.100.129,下面就用DHC進行REST API的Example02部署。
3.2.1通過REST API部署GO語言的ChainCode
POST 192.168.100.129:7050/chaincode
Body是:
{ "jsonrpc": "2.0", "method": "deploy", "params": { "type": 1, "chaincodeID":{ "path":"github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" }, "ctorMsg": { "function":"init", "args":["a", "1000", "b", "2000"] } }, "id": 1 }
這里為了區別,我們把a賬戶初始化1000元,b賬戶初始化2000元。返回的結果是:
{ "jsonrpc": "2.0", "result":{ "status": "OK", "message": "04233c6dd8364b9f0749882eb6d1b50992b942aa0a664182946f411ab46802a88574932ccd75f8c75e780036e363d52dd56ccadc2bfde95709fc39148d76f050" }, "id": 1 }
這里04233c6dd8364b9f0749882eb6d1b50992b942aa0a664182946f411ab46802a88574932ccd75f8c75e780036e363d52dd56ccadc2bfde95709fc39148d76f050就是部署后的ChainCodeID。
3.2.2通過REST API查詢ChainCode
POST 192.168.100.129:7050/chaincode
Body內容是:
{ "jsonrpc": "2.0", "method": "query", "params": { "type": 1, "chaincodeID":{ "name":"04233c6dd8364b9f0749882eb6d1b50992b942aa0a664182946f411ab46802a88574932ccd75f8c75e780036e363d52dd56ccadc2bfde95709fc39148d76f050" }, "ctorMsg": { "function":"query", "args":["a"] } }, "id": 2 }
系統返回的結果是:
{ "jsonrpc": "2.0", "result":{ "status": "OK", "message": "1000" }, "id": 2 }
一切正常,返回a賬戶的1000元。
3.2.3通過REST API調用ChainCode
我們試着從a向b轉賬100元:
POST 192.168.100.129:7050/chaincode
Body內容是:
{ "jsonrpc": "2.0", "method": "invoke", "params": { "type": 1, "chaincodeID":{ "name":"04233c6dd8364b9f0749882eb6d1b50992b942aa0a664182946f411ab46802a88574932ccd75f8c75e780036e363d52dd56ccadc2bfde95709fc39148d76f050" }, "ctorMsg": { "function":"invoke", "args":["a", "b", "100"] } }, "id": 3 }
返回的結果:
{ "jsonrpc": "2.0", "result":{ "status": "OK", "message": "2ac78b5f-6d35-400d-b7c4-75ef81e14d3e" }, "id": 3 }
3.2.4通過REST API檢查調用ChainCode后的結果
這里我們來查詢一下b賬戶。
POST 192.168.100.129:7050/chaincode
Body內容改為:
{ "jsonrpc": "2.0", "method": "query", "params": { "type": 1, "chaincodeID":{ "name":"04233c6dd8364b9f0749882eb6d1b50992b942aa0a664182946f411ab46802a88574932ccd75f8c75e780036e363d52dd56ccadc2bfde95709fc39148d76f050" }, "ctorMsg": { "function":"query", "args":["b"] } }, "id": 4 }
返回結果:
{ "jsonrpc": "2.0", "result":{ "status": "OK", "message": "2100" }, "id": 4 }
一切正常,b賬戶果然真假了100元。
關於更多的REST API,我們可以參考這里:https://github.com/hyperledger-archives/fabric/blob/master/docs/API/CoreAPI.md#rest-api
3.3測試Java版Chain Code
Fabric除了支持本身的Go語言的ChainCode,也可以支持其他語言,比如最常用的Java語言。Fabric的源代碼中也提供了Java示例,這里我們就用SimpleSample這個示例:
https://github.com/hyperledger/fabric/tree/master/examples/chaincode/java/SimpleSample
3.3.1在CLI中部署該Java代碼的ChainCode到Fabric
命令是:
peer chaincode deploy -l java -p /opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/java/SimpleSample -c '{"Function":"init", "Args": ["a","100", "b", "200"]}'
運行結果為:
root@vp0:/opt/gopath/src/github.com/hyperledger/fabric# peer chaincode deploy -l java -p /opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/java/SimpleSample -c '{"Function":"init", "Args": ["a","100", "b", "200"]}'
09:20:16.857 [chaincodeCmd] chaincodeDeploy -> INFO 001 Deploy result: type:JAVA chaincodeID:<path:"/opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/java/SimpleSample" name:"0f5b1d65041bc6d500bd0f1cab50eb6154c291ef0f4596d64b6797e8ef8f7c34a179b5a2cea82253ff3d74e768512fe0481503eadcf13d18f9761bbb8133efd0" > ctorMsg:<args:"init" args:"a" args:"100" args:"b" args:"200" >
Deploy chaincode: 0f5b1d65041bc6d500bd0f1cab50eb6154c291ef0f4596d64b6797e8ef8f7c34a179b5a2cea82253ff3d74e768512fe0481503eadcf13d18f9761bbb8133efd0
09:20:16.857 [main] main -> INFO 002 Exiting.....
接下來的各種查詢,調用都是差不多的,我就不再累述了。
3.3.2通過REST API部署Java ChainCode到Fabric
POST 192.168.100.129:7050/chaincode
Body為
{ "jsonrpc": "2.0", "method": "deploy", "params": { "type": 4, "chaincodeID":{ "path":"/opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/java/SimpleSample" }, "ctorMsg": { "function":"init", "args":["a", "1000", "b", "2000"] } }, "id": 1 }
系統返回的結果為:
{ "jsonrpc": "2.0", "result":{ "status": "OK", "message": "27cb2925013a5e8f27b41be748e6767c3fbc7bfdfe2453c2640f9069e75c4db38735fa3b6b8cac78e212a1c97193f3bfb2f9b810ce0a11f437a96b330d508fbd" }, "id": 1 }
這里需要注意的是type:4,不再是1。1是Go語言的,而Java語言是4.接下來的操作也是類似的了,我就不累述了。
總的來說,Fabric基於Docker容器技術,部署的ChainCode在運行時會基於baseimage重新創建Docker容器,運行的鏈上代碼越多,容器就會越多。運行docker ps會看到很多容器被創建。docker images也可以看到多了很多鏡像。需要注意清理。