版權聲明
轉載請與作者聯繫,轉載時請務必標明文章原始出處和作者信息及本聲明。
|
|
|
|
2018-02-10
你網上搜索hyperledger大部分文章是講解開發環境的安裝與配置,沒有一篇關於怎樣運維區塊鏈的文章。當你配置好開發環境,寫好合約,怎樣落地呢?卻很少文章提及。
要將區塊鏈落地,我們必須依賴運維技術,這是IT基礎設施,區塊鏈應用將建立在這個基礎設施之上,否則區塊鏈就是浮雲,懸在空中無法落地。
本文采用碎片化寫作,原文會不定期更新,請儘量閲讀原文。
由於區塊鏈是區中心化,與傳統運維不同,所以之前你積累的經驗,不一定適用於區塊鏈。要想運維好區塊鏈項目,就必須理解去中心化這個概念。
首先談談傳統運維,總結為三個字“中心化”,當然有人反對並拋出“分散式”感念,傳統運維的分散式仍然建立在中心化的基礎之上。
我們來看看傳統應用模式,決多數應用都可以概括為:
用戶 -> WEB -> Application -> Cache -> Database
可以在這個體系下面做靈活變化,例如加入所有引擎、分散式檔案系統,大數據等等應用,但都離不開這個模式。
區塊鏈完全不同,如果舉一個最接近的例子,我想可能與多數據中心遠程異地災備比較接近。
什麼是區塊鏈呢? 區塊鏈實際上就是資料庫,一個只能插入和查詢的資料庫,數據不能被修改和刪除,並且這個資料庫沒有DBA管理員角色。這麼一說你應該明白了把,實際上運維區塊鏈就是在維護一個分散式資料庫。
網上的絶大多數安裝例子中,均採用 docker 部署方案,但無一例外的是,全部安裝在一個物理機上。如果是生產環境,我們必須分開不是,首先要做的工作是化整為零,拆解應用,搞明白每個容器的功能和作用。然後我們將應用拆分,獨立部署到物理節點上去。
+---------------------------------+
| SDK |
+---------------------------------+
| golang | nodejs | python | java |
+---------------------------------+
| |
| |
| +--------------+ |
| | fabric-ca | |
| +--------------+ |
| |
V V
+-------------------+ +-------------------+
| Peer | | Peer |
+-------------------+ +-------------------+
| | | |
V | | V
+-----------+ | | +------------+
| Orderer | | | | Orderer |
+-----------+ | | +------------+
V V
+-------------------+
| Couchdb |
+-------------------+
接下來我們要做的工作是將上面拓撲圖種的技術點分分擊破。
由於 Hyperledger Fabric 是建立在 Docker 基礎之上的。所以不建議你去除 Docker 轉而使用傳統的本地編譯安裝方式。我們仍然保持使用 Docker 在每個物理節點上,省去軟件的編譯和安裝環節。
需要注意的是于其他傳統系統一樣,Hyperledger Fabric 的啟動也是有順序的,這是因為他們之間存在着依賴關係。
CentOS (Minimal ISO)
ca 節點,域名:ca.example.com,IP地址:172.16.0.20,連接埠:7054
orderer 節點,域名 orderer.example.com,IP地址:172.16.0.21,連接埠:7050
peer 節點,域名:peer.example.com,IP地址:172.16.0.22,連接埠:7051、7053
couchdb 節點,域名 couchdb.example.com,IP地址:172.16.0.25,連接埠:5984
tools 節點,域名:tools.example.com,IP地址:172.16.0.20 與 CA 共用一台機器(這裡為了節省資源)
在所有節點上運行下面腳本
curl -s https://raw.githubusercontent.com/oscm/shell/master/virtualization/docker/docker.centos7.ce.sh | bash curl -s https://raw.githubusercontent.com/oscm/shell/master/virtualization/docker/docker-compose/docker-compose-1.19.0.sh | bash
Tools 在生成創世區塊的時候我們就曾經使用,你可以沿用之前的 tools 節點,或者創建一個 cli 節點,這個節點主要是用於管理區塊鏈集群,例如合約部署,調試等等。
docker pull hyperledger/fabric-tools:x86_64-1.0.5 docker tag hyperledger/fabric-tools:x86_64-1.0.5 hyperledger/fabric-tools
version: '3' networks: basic: services: cli: container_name: cli image: hyperledger/fabric-tools tty: true environment: - GOPATH=/opt/gopath - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - CORE_LOGGING_LEVEL=DEBUG - CORE_PEER_ID=cli - CORE_PEER_ADDRESS=peer0.org1.example.com:7051 - CORE_PEER_LOCALMSPID=Org1MSP - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp - CORE_CHAINCODE_KEEPALIVE=10 working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: /bin/bash volumes: - /var/run/:/host/var/run/ - ./chaincode/:/opt/gopath/src/github.com/ - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ - ~/netkiller:/root/netkiller networks: - basic #depends_on: # - orderer.example.com # - peer0.org1.example.com # - couchdb extra_hosts: - "ca.example.com:172.16.0.20" - "orderer.example.com:172.16.0.21" - "peer0.org1.example.com:172.16.0.22" - "couchdb.example.com:172.16.0.25"
[root@localhost netkiller]# docker-compose -f docker-compose-cli.yaml up -d
後面合約的部署將在 cli 節點上進行
這裡我們需要幾個命令(configtxgen configtxlator cryptogen),官方的安裝方式:
curl -sSL https://goo.gl/byy2Qj | bash -s 1.0.5
無論如何我都安裝不成功,可能是(https://goo.gl/byy2Qj)被天朝給牆了。不過我發現 fabric-tools 裡面有這個工具。
經過翻牆發現 https://goo.gl/byy2Qj 地址是 301 到下面地址:
https://raw.githubusercontent.com/hyperledger/fabric/v1.0.5/scripts/bootstrap.sh
[root@localhost ~]# mkdir netkiller [root@localhost ~]# cd netkiller/ [root@localhost netkiller]# mkdir -p {chaincode,crypto-config,config,artifacts}
創建證書
OrdererOrgs: - Name: Orderer Domain: example.com Specs: - Hostname: orderer PeerOrgs: - Name: Org1 Domain: org1.example.com Template: Count: 1 Users: Count: 1
--- Profiles: OneOrgOrdererGenesis: Orderer: <<: *OrdererDefaults Organizations: - *OrdererOrg Consortiums: SampleConsortium: Organizations: - *Org1 OneOrgChannel: Consortium: SampleConsortium Application: <<: *ApplicationDefaults Organizations: - *Org1 Organizations: - &OrdererOrg Name: OrdererOrg ID: OrdererMSP MSPDir: crypto-config/ordererOrganizations/example.com/msp - &Org1 Name: Org1MSP ID: Org1MSP MSPDir: crypto-config/peerOrganizations/org1.example.com/msp AnchorPeers: - Host: peer0.org1.example.com Port: 7051 Orderer: &OrdererDefaults OrdererType: solo Addresses: - orderer.example.com:7050 BatchTimeout: 2s BatchSize: MaxMessageCount: 10 AbsoluteMaxBytes: 99 MB PreferredMaxBytes: 512 KB Kafka: Brokers: - 127.0.0.1:9092 Organizations: Application: &ApplicationDefaults Organizations:
命令
cryptogen generate --config=./crypto-config.yaml
演示
root@8f467a88de99:~/netkiller# cryptogen generate --config=./crypto-config.yaml org1.example.com root@8f467a88de99:~/netkiller# ls -1 crypto-config ordererOrganizations peerOrganizations
root@8f467a88de99:~/netkiller# export FABRIC_CFG_PATH=$PWD root@8f467a88de99:~/netkiller# configtxgen -profile OneOrgOrdererGenesis -outputBlock ./config/genesis.block 2018-02-08 08:35:30.121 UTC [common/configtx/tool] main -> INFO 001 Loading configuration 2018-02-08 08:35:30.236 UTC [common/configtx/tool] doOutputBlock -> INFO 002 Generating genesis block 2018-02-08 08:35:30.238 UTC [common/configtx/tool] doOutputBlock -> INFO 003 Writing genesis block
命令
CHANNEL_NAME=mychannel configtxgen -profile OneOrgChannel -outputCreateChannelTx ./config/channel.tx -channelID $CHANNEL_NAME
操作演示
root@8f467a88de99:~/netkiller# CHANNEL_NAME=mychannel root@8f467a88de99:~/netkiller# configtxgen -profile OneOrgChannel -outputCreateChannelTx ./config/channel.tx -channelID $CHANNEL_NAME 2018-02-08 08:41:08.010 UTC [common/configtx/tool] main -> INFO 001 Loading configuration 2018-02-08 08:41:08.020 UTC [common/configtx/tool] doOutputChannelCreateTx -> INFO 002 Generating new channel configtx 2018-02-08 08:41:08.020 UTC [common/configtx/tool] doOutputChannelCreateTx -> INFO 003 Writing new channel tx
命令
CHANNEL_NAME=mychannel configtxgen -profile OneOrgChannel -outputAnchorPeersUpdate ./config/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP
操作演示
root@8f467a88de99:~/netkiller# CHANNEL_NAME=mychannel root@8f467a88de99:~/netkiller# configtxgen -profile OneOrgChannel -outputAnchorPeersUpdate ./config/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP 2018-02-08 08:46:19.162 UTC [common/configtx/tool] main -> INFO 001 Loading configuration 2018-02-08 08:46:19.176 UTC [common/configtx/tool] doOutputAnchorPeersUpdate -> INFO 002 Generating anchor peer update 2018-02-08 08:46:19.177 UTC [common/configtx/tool] doOutputAnchorPeersUpdate -> INFO 003 Writing anchor peer update
至此所有需要生成的配置檔案全部生成完畢。
[root@localhost netkiller]# tree -L 4 crypto-config crypto-config |-- ordererOrganizations | `-- example.com | |-- ca | | |-- ca.example.com-cert.pem | | `-- de9204448c9c8e2a72d092f53e8ff069e12dea62001b7b8b9a83ae240d80ed57_sk | |-- msp | | |-- admincerts | | |-- cacerts | | `-- tlscacerts | |-- orderers | | `-- orderer.example.com | |-- tlsca | | |-- c0b4dd42bd396d68f468aa07dae8ce944ab2d9832b2593cfafb27e53c69ec5e2_sk | | `-- tlsca.example.com-cert.pem | `-- users | `-- Admin@example.com `-- peerOrganizations `-- org1.example.com |-- ca | |-- 74023bd84cc5e6957f9bc30b3ebcd6c5b7507016721702a014dd640df265b61a_sk | `-- ca.org1.example.com-cert.pem |-- msp | |-- admincerts | |-- cacerts | `-- tlscacerts |-- peers | `-- peer0.org1.example.com |-- tlsca | |-- 71bb82530580707aa20fa5955beab202f266aa4da4b435bef20741ce5e64abb9_sk | `-- tlsca.org1.example.com-cert.pem `-- users |-- Admin@org1.example.com `-- User1@org1.example.com 25 directories, 8 files
將config和crypto-config檔案加複製到ca,orderer,peer,cli等節點上去。
至此所需的證書與創世區塊都已生產完畢,fabric-tools 容易完成了它的使命,你可以繼續保留或者清理乾淨。
[root@localhost netkiller]# docker-compose -f docker-compose-tools.yml down Stopping tools ... done Removing tools ... done Removing network netkiller_basic
清理 tools 容器
docker rm -f $(docker ps -qa)
CA 節點需要我們之前生成 crypto-config
docker pull hyperledger/fabric-ca:x86_64-1.0.5 docker tag hyperledger/fabric-ca:x86_64-1.0.5 hyperledger/fabric-ca
version: '3' networks: basic: services: ca.example.com: image: hyperledger/fabric-ca environment: - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server - FABRIC_CA_SERVER_CA_NAME=ca.example.com - FABRIC_CA_SERVER_CA_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.example.com-cert.pem - FABRIC_CA_SERVER_CA_KEYFILE=/etc/hyperledger/fabric-ca-server-config/4239aa0dcd76daeeb8ba0cda701851d14504d31aad1b2ddddbac6a57365e497c_sk ports: - "XXX.XXX.XXX.XXX:7054:7054" command: sh -c 'fabric-ca-server start -b admin:adminpw -d' volumes: - ./crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/fabric-ca-server-config container_name: ca.example.com networks: - basic
docker-compose -f docker-compose-ca.yaml up -d
整個 Hyperledger Fabric 技術棧中只有這個 CouchDB 是個外來戶,看到 CouchDB 我就非常興奮,這是一個NoSQL資料庫(它與MongoDB十分類似),所以CouchDB 100%可以獨立運行,且最容易分離。
CouchDB 在這裡有兩個方案可以選擇。
採用 Docker 運行 CouchDB的方案。
採用傳統方式物理機上本地安裝 CouchDB
理論兩種方案對實際結果沒有什麼區別,只需提供IP地址,用戶名與密碼供其他節點訪問即可。但實際我們看到 Hyperledger Fabric 使用的鏡像是 hyperledger/fabric-couchdb 不清楚是否有修改過 CouchDB 資料庫。
如果你對 Docker 比較熟悉就採用 Docker 方案。如果不熟悉就採用本地安裝方式。總之選擇一種你能Hold住(掌控)的方案,一旦出現故障,你能第一時間排查並處理。
docker pull hyperledger/fabric-couchdb:x86_64-1.0.5 docker tag hyperledger/fabric-couchdb:x86_64-1.0.5 hyperledger/fabric-couchdb
下面是 Docker 方案
[root@localhost netkiller]# vim docker-compose-couchdb.yml version: '3' networks: basic: services: couchdb: container_name: couchdb image: hyperledger/fabric-couchdb # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. environment: - COUCHDB_USER=admin - COUCHDB_PASSWORD=passw0rd ports: - 172.16.0.17:5984:5984 networks: - basic
啟動 Docker 容器
docker-compose -f docker-compose-couchdb.yml up -d
訪問CouchDB管理界面,http://172.16.0.17:5984/_utils/ 請使用上面設置的密碼進入。若想進入到容器內部可以使用下面命令:
docker-compose -f docker-compose-couchdb.yml exec couchdb bash
至此 CouchDB 節點部署完畢。
既然是運維區塊鏈,對於運維工作我們最關心的就是如何備份數據,在出現故障的時候恢復數據。
npm install --save couchdb-backup-restore
var cbr = require('couchdb-backup-restore'); var config = {credentials: 'http://localhost:5984'}; function done(err) { if (err) { return console.error(err); } console.log('all done!'); } // backup cbr.backup(config, done).pipe(fs.createWriteStream('./db-backup.tar.gz')) // restore fs.createReadStream('./db-backup.tar.gz').pipe(cbr.restore(config, done));
docker pull hyperledger/fabric-orderer:x86_64-1.0.5 docker tag hyperledger/fabric-orderer:x86_64-1.0.5 hyperledger/fabric-orderer
version: '3' networks: basic: services: orderer.example.com: container_name: orderer.example.com image: hyperledger/fabric-orderer environment: - ORDERER_GENERAL_LOGLEVEL=debug - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 - ORDERER_GENERAL_GENESISMETHOD=file - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/configtx/genesis.block - ORDERER_GENERAL_LOCALMSPID=OrdererMSP - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp/orderer/msp working_dir: /opt/gopath/src/github.com/hyperledger/fabric/orderer command: orderer ports: - 7050:7050 volumes: - ./config/:/etc/hyperledger/configtx - ./crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/:/etc/hyperledger/msp/orderer - ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/:/etc/hyperledger/msp/peerOrg1 networks: - basic
docker-compose -f docker-compose-orderer.yaml up -d
docker pull hyperledger/fabric-peer:x86_64-1.0.5 docker tag hyperledger/fabric-peer:x86_64-1.0.5 hyperledger/fabric-peer
version: '3' networks: basic: services: peer0.org1.example.com: container_name: peer0.org1.example.com image: hyperledger/fabric-peer environment: - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - 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 # # 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}_basic - CORE_LEDGER_STATE_STATEDATABASE=CouchDB - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=172.16.0.17:5984 # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD # provide the credentials for ledger to connect to CouchDB. The username and password must # match the username and password set for the associated CouchDB. - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=passw0rd working_dir: /opt/gopath/src/github.com/hyperledger/fabric command: peer node start # command: peer node start --peer-chaincodedev=true 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 - ./crypto-config/peerOrganizations/org1.example.com/users:/etc/hyperledger/msp/users - ./config:/etc/hyperledger/configtx #depends_on: # - orderer.example.com # - couchdb networks: - basic
Peer 需要連接到 CouchDB 注意配置項 CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=172.16.0.17:5984
同時連接CouchDB的用戶與密碼要正確
[root@localhost netkiller]# docker-compose -f docker-compose-peer.yaml up -d
進入 Peer 容器
docker-compose -f docker-compose-peer.yaml exec peer0.org1.example.com bash
添加 Orderer 節點並創建 Channel
CORE_PEER_LOCALMSPID=Org1MSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/users/Admin@org1.example.com/msp peer channel create -o orderer.example.com:7050 -c mychannel -f /etc/hyperledger/configtx/channel.tx peer channel create -o 172.16.0.17:7050 -c mychannel -f /etc/hyperledger/configtx/channel.tx
加入到 mychannel
CORE_PEER_LOCALMSPID=Org1MSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/users/Admin@org1.example.com/msp peer channel join -b mychannel.block
查看通道
st t@f39764f58ff7:/opt/gopath/src/github.com/hyperledger/fabric# peer channel list 2018-02-09 08:12:46.454 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP 2018-02-09 08:12:46.454 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity 2018-02-09 08:12:46.456 UTC [channelCmd] InitCmdFactory -> INFO 003 Endorser and orderer connections initialized 2018-02-09 08:12:46.457 UTC [msp/identity] Sign -> DEBU 004 Sign: plaintext: 0A8A070A5C08031A0C08FEAFF5D30510...631A0D0A0B4765744368616E6E656C73 2018-02-09 08:12:46.458 UTC [msp/identity] Sign -> DEBU 005 Sign: digest: E27446498819AA4FE8EE835ADEF16195489975377A3C18D89C36D37AA24E5CA2 2018-02-09 08:12:46.469 UTC [channelCmd] list -> INFO 006 Channels peers has joined to: 2018-02-09 08:12:46.469 UTC [channelCmd] list -> INFO 007 mychannel 2018-02-09 08:12:46.469 UTC [main] main -> INFO 008 Exiting.....
[root@localhost netkiller]# cat chaincode/fabcar/fabcar.go /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * The sample smart contract for documentation topic: * Writing Your First Blockchain Application */ package main /* Imports * 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation * 2 specific Hyperledger Fabric specific libraries for Smart Contracts */ import ( "bytes" "encoding/json" "fmt" "strconv" "github.com/hyperledger/fabric/core/chaincode/shim" sc "github.com/hyperledger/fabric/protos/peer" ) // Define the Smart Contract structure type SmartContract struct { } // Define the car structure, with 4 properties. Structure tags are used by encoding/json library type Car struct { Make string `json:"make"` Model string `json:"model"` Colour string `json:"colour"` Owner string `json:"owner"` } /* * The Init method is called when the Smart Contract "fabcar" is instantiated by the blockchain network * Best practice is to have any Ledger initialization in separate function -- see initLedger() */ func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response { return shim.Success(nil) } /* * The Invoke method is called as a result of an application request to run the Smart Contract "fabcar" * The calling application program has also specified the particular smart contract function to be called, with arguments */ func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response { // Retrieve the requested Smart Contract function and arguments function, args := APIstub.GetFunctionAndParameters() // Route to the appropriate handler function to interact with the ledger appropriately if function == "queryCar" { return s.queryCar(APIstub, args) } else if function == "initLedger" { return s.initLedger(APIstub) } else if function == "createCar" { return s.createCar(APIstub, args) } else if function == "queryAllCars" { return s.queryAllCars(APIstub) } else if function == "changeCarOwner" { return s.changeCarOwner(APIstub, args) } return shim.Error("Invalid Smart Contract function name.") } func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } carAsBytes, _ := APIstub.GetState(args[0]) return shim.Success(carAsBytes) } func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response { cars := []Car{ Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"}, Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"}, Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"}, Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"}, Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"}, Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"}, Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"}, Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"}, Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"}, Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"}, } i := 0 for i < len(cars) { fmt.Println("i is ", i) carAsBytes, _ := json.Marshal(cars[i]) APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes) fmt.Println("Added", cars[i]) i = i + 1 } return shim.Success(nil) } func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 5 { return shim.Error("Incorrect number of arguments. Expecting 5") } var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]} carAsBytes, _ := json.Marshal(car) APIstub.PutState(args[0], carAsBytes) return shim.Success(nil) } func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { startKey := "CAR0" endKey := "CAR999" resultsIterator, err := APIstub.GetStateByRange(startKey, endKey) if err != nil { return shim.Error(err.Error()) } defer resultsIterator.Close() // buffer is a JSON array containing QueryResults var buffer bytes.Buffer buffer.WriteString("[") bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return shim.Error(err.Error()) } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { buffer.WriteString(",") } buffer.WriteString("{\"Key\":") buffer.WriteString("\"") buffer.WriteString(queryResponse.Key) buffer.WriteString("\"") buffer.WriteString(", \"Record\":") // Record is a JSON object, so we write as-is buffer.WriteString(string(queryResponse.Value)) buffer.WriteString("}") bArrayMemberAlreadyWritten = true } buffer.WriteString("]") fmt.Printf("- queryAllCars:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes()) } func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } carAsBytes, _ := APIstub.GetState(args[0]) car := Car{} json.Unmarshal(carAsBytes, &car) car.Owner = args[1] carAsBytes, _ = json.Marshal(car) APIstub.PutState(args[0], carAsBytes) return shim.Success(nil) } // The main function is only relevant in unit test mode. Only included here for completeness. func main() { // Create a new Smart Contract err := shim.Start(new(SmartContract)) if err != nil { fmt.Printf("Error creating new Smart Contract: %s", err) } }
安裝合約在 tools 節點上進行。
docker-compose -f docker-compose-cli.yaml exec cli bash CORE_PEER_ADDRESS=172.16.0.17:7051 CORE_PEER_LOCALMSPID=Org1MSP CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')" peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[""]}'
root@a90d0d869dd3:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar 2018-02-09 11:26:28.025 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP 2018-02-09 11:26:28.025 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity 2018-02-09 11:26:28.025 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc 2018-02-09 11:26:28.025 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc 2018-02-09 11:26:28.139 UTC [golang-platform] getCodeFromFS -> DEBU 005 getCodeFromFS github.com/fabcar 2018-02-09 11:26:29.394 UTC [golang-platform] func1 -> DEBU 006 Discarding GOROOT package bytes 2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 007 Discarding GOROOT package encoding/json 2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 008 Discarding GOROOT package fmt 2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 009 Discarding provided package github.com/hyperledger/fabric/core/chaincode/shim 2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 00a Discarding provided package github.com/hyperledger/fabric/protos/peer 2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 00b Discarding GOROOT package strconv 2018-02-09 11:26:29.396 UTC [golang-platform] GetDeploymentPayload -> DEBU 00c done 2018-02-09 11:26:29.406 UTC [msp/identity] Sign -> DEBU 00d Sign: plaintext: 0A8A070A5C08031A0C08E58AF6D30510...939FFF060000FFFF9C08DC0700200000 2018-02-09 11:26:29.406 UTC [msp/identity] Sign -> DEBU 00e Sign: digest: A504EE8048EEE8C77F9E1C39827124474638110FD3980017BCA6D644E3E7EC98 2018-02-09 11:26:29.426 UTC [chaincodeCmd] install -> DEBU 00f Installed remotely response:<status:200 payload:"OK" > 2018-02-09 11:26:29.427 UTC [main] main -> INFO 010 Exiting.....
peer chaincode instantiate -o 172.16.0.17:7050 -C mychannel -n fabcar -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')"