当前位置:   article > 正文

Go语言+Fabric搭建区块链应用_goland引入fabric-chaincode-go版本

goland引入fabric-chaincode-go版本

        在没有区块链的相关知识之前建议先学习一下基本知识,如果有一定理论基础便可通过这篇文章对区块链应用有一个更深层次的理解。

        闲话少说,直接开始!

PART1:Go语言安装

        这里我以Linux服务器为主从零开始搭建,有条件的可以选择一个好的配置

        具体服务器配置教程如下:

从0开始部署阿里云服务器(萌新必看)_阿里云搭建服务器部署-CSDN博客

        配置完后可以进行下面的内容,首先安装Go语言      

        控制台输入

wget https://golang.google.cn/dl/go1.20.6.linux-amd64.tar.gz

        然后解压缩

sudo tar -C /usr/local -xzf go1.20.6.linux-amd64.tar.gz

        接着配置环境

vim ~/.bash_profile

        加入

  1. export GOPATH=$HOME/gopath
  2. export GOROOT=/usr/local/go
  3. export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

        返回然后

source ~/.bash_profile

        接着输入go version检查

        即可完成安装

PART2:搭建区块链网络

        1、下载 fabric 二进制工具

        以 v1.4.12 版本为例, fabric 二进制工具的下载地址在https://github.com/hyperledger/fabric/releases/tag/v1.4.12

        下载linux系统的,其他系统的根据需求下载

        下载完后把它丢到服务器中并解压

        2、将 fabric 二进制工具添加到环境变量

        为了后续方便使用命令,可以将第 1 步下载的工具添加到系统环境变量中:

export PATH=/usr/local/fabric/hyperledger-fabric-linux-amd64-1.4.12/bin:$PATH

        3、生成证书和秘钥

        我在相同目录上新建了个0202demo,接下来的所有操作都将在这个文件夹里进行

        首先我们新建一个network文件夹,在0202demo中

        进入到network文件夹

        这里将使 cryptogen 工具生成各种加密材料(x509 证书和签名秘钥)。这些证书是身份的代表,在实体相互通信和交易的时候,可以对其身份进行签名和验证。

        首先创建 crypto-config.yaml 文件,定义网络拓扑,为所有组织和属于这些组织的组件(也就是节点)生成一组证书和秘钥,内容如下:

  1. # 排序节点的组织定义
  2. OrdererOrgs:
  3.   - Name: QQ # 名称
  4.     Domain: qq.com # 域名
  5.     Specs: # 节点域名:orderer.qq.com
  6.       - Hostname: orderer # 主机名
  7. # peer节点的组织定义
  8. PeerOrgs:
  9.   # Taobao-组织
  10.   - Name: Taobao # 名称
  11.     Domain: taobao.com # 域名
  12.     Template: # 使用模板定义。Count 指的是该组织下组织节点的个数
  13.       Count: 2 # 节点域名:peer0.taobao.com 和 peer1.taobao.com
  14.     Users: # 组织的用户信息。Count 指该组织中除了 Admin 之外的用户的个数
  15.       Count: 1 # 用户:Admin 和 User1
  16.   # JD-组织
  17.   - Name: JD
  18.     Domain: jd.com
  19.     Template:
  20.       Count: 2 # 节点域名:peer0.jd.com 和 peer1.jd.com
  21.     Users:
  22.       Count: 1 # 用户:Admin 和 User1

        不会生成的,直接vim crypto-config.yaml进行编辑即可。

        接着执行 cryptogen generate 命令,生成结果将默认保存在 crypto-config 文件夹中

        进入这个文件夹是可以看到有很多文件生成

        总结:在这个环节中,我们假设 QQ 作为一个运营方,提供了 1  Orderer 节点 orderer.qq.com 来创建联盟链的基础设施,而 Taobao  JD 则是作为组织成员加入到链中,各自提供 2  Peer 节点 peer0.xx.com  peer1.xx.com 参与工作,以及还各自创建了 2 个组织用户 Admin  User1 。然后我们使用 crypto-config.yaml 文件和 cryptogen 工具为其定义所需要的证书文件以供后续使用。

4、创建排序通道创世区块

       我们可以使用 configtx.yaml 文件和 configtxgen 工具轻松地创建通道的配置。configtx.yaml 文件可以以易于理解和编辑的 yaml 格式来构建通道配置所需的信息。configtxgen 工具通过读取 configtx.yaml 文件中的信息,将其转成 Fabric 可以读取的 protobuf 格式。

        先来创建 configtx.yaml 文件,内容如下:

  1. # 定义组织机构实体
  2. Organizations:
  3.   - &QQ
  4.     Name: QQ # 组织的名称
  5.     ID: QQMSP # 组织的 MSPID
  6.     MSPDir: crypto-config/ordererOrganizations/qq.com/msp #组织的证书相对位置(生成的crypto-config目录)
  7.   - &Taobao
  8.     Name: Taobao
  9.     ID: TaobaoMSP
  10.     MSPDir: crypto-config/peerOrganizations/taobao.com/msp
  11.     AnchorPeers: # 组织锚节点的配置
  12.       - Host: peer0.taobao.com
  13.         Port: 7051
  14.   - &JD
  15.     Name: JD
  16.     ID: JDMSP
  17.     MSPDir: crypto-config/peerOrganizations/jd.com/msp
  18.     AnchorPeers: # 组织锚节点的配置
  19.       - Host: peer0.jd.com
  20.         Port: 7051
  21. # 定义了排序服务的相关参数,这些参数将用于创建创世区块
  22. Orderer: &OrdererDefaults
  23.   # 排序节点类型用来指定要启用的排序节点实现,不同的实现对应不同的共识算法
  24.   OrdererType: solo # 共识机制
  25.   Addresses: # Orderer 的域名(用于连接)
  26.     - orderer.qq.com:7050
  27.   BatchTimeout: 2s # 出块时间间隔
  28.   BatchSize: # 用于控制每个block的信息量
  29.     MaxMessageCount: 10 #每个区块的消息个数
  30.     AbsoluteMaxBytes: 99 MB #每个区块最大的信息大小
  31.     PreferredMaxBytes: 512 KB #每个区块包含的一条信息最大长度
  32.   Organizations:
  33. # 用来定义用于 configtxgen 工具的配置入口
  34. # 将 Profile 参数( TwoOrgsOrdererGenesis 或 TwoOrgsChannel )指定为 configtxgen 工具的参数
  35. Profiles:
  36.   #  TwoOrgsOrdererGenesis配置文件用于创建系统通道创世块
  37.   #  该配置文件创建一个名为SampleConsortium的联盟
  38.   #  该联盟在configtx.yaml文件中包含两个Peer组织Taobao和JD
  39.   TwoOrgsOrdererGenesis:
  40.     Orderer:
  41.       <<: *OrdererDefaults
  42.       Organizations:
  43.         - *QQ
  44.     Consortiums:
  45.       SampleConsortium:
  46.         Organizations:
  47.           - *Taobao
  48.           - *JD
  49.   # 使用TwoOrgsChannel配置文件创建应用程序通道
  50.   TwoOrgsChannel:
  51.     Consortium: SampleConsortium
  52.     Application:
  53.       <<: *ApplicationDefaults
  54.       Organizations:
  55.         - *Taobao
  56.         - *JD

        在network中新建一个config文件夹

        执行 configtxgen 命令,并指定 Profile 为 TwoOrgsOrdererGenesis 参数:

configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./config/genesis.block -channelID firstchannel

        排序区块是排序服务的创世区块,通过以上命令就可以预先生成创世区块的 protobuf 格式的配置文件 ./config/genesis.block 了。

        5、创建通道配置交易

       接下来,我们需要继续使用 configtxgen 根据去创建通道的交易配置,和第 4 步不同的是,这次需要指定Profile  TwoOrgsChannel 参数。

        生成通道配置事务 ./config/appchannel.tx 

configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./config/appchannel.tx -channelID appchannel

        Taobao 组织定义锚节点,生成 ./config/TaobaoAnchor.tx 

configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./config/TaobaoAnchor.tx -channelID appchannel -asOrg Taobao

        JD 组织定义锚节点,生成 ./config/JDAnchor.tx 

       然后我们可以看到,config里有如下四个文件

        6、创建并启动各组织的节点

        我们说过:我们假设 QQ 作为一个运营方,提供了 1 个 Orderer 节点 orderer.qq.com 来创建联盟链的基础设施, 而 Taobao 和 JD 则是作为组织成员加入到链中,各自提供 2 个 Peer 节点 peer0.xx.com 和 peer1.xx.com 参与工作。

        现在这些组织及其节点所需要的配置已经准备好了。我们接下来就可以使用 Docker Compose 来模拟启动这些节点服务。

        由于这些节点之间需要互相通信,所以我们需要将这些节点都放入到一个 Docker 网络中,以 fabric_network 为例。

        这里的话我们就在network文件夹下新建docker-compose.yaml

        docker-compose.yaml 的内容如下:

  1. version: '2.1'
  2. volumes:
  3.   orderer.qq.com:
  4.   peer0.taobao.com:
  5.   peer1.taobao.com:
  6.   peer0.jd.com:
  7.   peer1.jd.com:
  8. networks:
  9.   fabric_network:
  10.     name: fabric_network
  11. services:
  12.   # 排序服务节点
  13.   orderer.qq.com:
  14.     container_name: orderer.qq.com
  15. image: hyperledger/fabric-orderer:1.4.12
  16.     environment:
  17.       - GODEBUG=netdns=go
  18.       - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
  19.       - ORDERER_GENERAL_GENESISMETHOD=file
  20.       - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/config/genesis.block # 注入创世区块
  21.       - ORDERER_GENERAL_LOCALMSPID=QQMSP
  22.       - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/orderer/msp # 证书相关
  23.     command: orderer
  24.     ports:
  25.       - "7050:7050"
  26.     volumes: # 挂载由cryptogen和configtxgen生成的证书文件以及创世区块
  27.       - ./config/genesis.block:/etc/hyperledger/config/genesis.block
  28.       - ./crypto-config/ordererOrganizations/qq.com/orderers/orderer.qq.com/:/etc/hyperledger/orderer
  29.       - orderer.qq.com:/var/hyperledger/production/orderer
  30.     networks:
  31.       - fabric_network
  32.   #  Taobao 组织 peer0 节点
  33.   peer0.taobao.com:
  34.     extends:
  35.       file: docker-compose-base.yaml
  36.       service: peer-base
  37.     container_name: peer0.taobao.com
  38.     environment:
  39.       - CORE_PEER_ID=peer0.taobao.com
  40.       - CORE_PEER_LOCALMSPID=TaobaoMSP
  41.       - CORE_PEER_ADDRESS=peer0.taobao.com:7051
  42.     ports:
  43.       - "7051:7051" # grpc服务端口
  44.       - "7053:7053" # eventhub端口
  45.     volumes:
  46.       - ./crypto-config/peerOrganizations/taobao.com/peers/peer0.taobao.com:/etc/hyperledger/peer
  47.       - peer0.taobao.com:/var/hyperledger/production
  48.     depends_on:
  49.       - orderer.qq.com
  50.   #  Taobao 组织 peer1 节点
  51.   peer1.taobao.com:
  52.     extends:
  53.       file: docker-compose-base.yaml
  54.       service: peer-base
  55.     container_name: peer1.taobao.com
  56.     environment:
  57.       - CORE_PEER_ID=peer1.taobao.com
  58.       - CORE_PEER_LOCALMSPID=TaobaoMSP
  59.       - CORE_PEER_ADDRESS=peer1.taobao.com:7051
  60.     ports:
  61.       - "17051:7051"
  62.       - "17053:7053"
  63.     volumes:
  64.       - ./crypto-config/peerOrganizations/taobao.com/peers/peer1.taobao.com:/etc/hyperledger/peer
  65.       - peer1.taobao.com:/var/hyperledger/production
  66.     depends_on:
  67.       - orderer.qq.com
  68.   #  JD 组织 peer0 节点
  69.   peer0.jd.com:
  70.     extends:
  71.       file: docker-compose-base.yaml
  72.       service: peer-base
  73.     container_name: peer0.jd.com
  74.     environment:
  75.       - CORE_PEER_ID=peer0.jd.com
  76.       - CORE_PEER_LOCALMSPID=JDMSP
  77.       - CORE_PEER_ADDRESS=peer0.jd.com:7051
  78.     ports:
  79.       - "27051:7051"
  80.       - "27053:7053"
  81.     volumes:
  82.       - ./crypto-config/peerOrganizations/jd.com/peers/peer0.jd.com:/etc/hyperledger/peer
  83.       - peer0.jd.com:/var/hyperledger/production
  84.     depends_on:
  85.       - orderer.qq.com
  86.   #  JD 组织 peer1 节点
  87.   peer1.jd.com:
  88.     extends:
  89.       file: docker-compose-base.yaml
  90.       service: peer-base
  91.     container_name: peer1.jd.com
  92.     environment:
  93.       - CORE_PEER_ID=peer1.jd.com
  94.       - CORE_PEER_LOCALMSPID=JDMSP
  95.       - CORE_PEER_ADDRESS=peer1.jd.com:7051
  96.     ports:
  97.       - "37051:7051"
  98.       - "37053:7053"
  99.     volumes:
  100.       - ./crypto-config/peerOrganizations/jd.com/peers/peer1.jd.com:/etc/hyperledger/peer
  101.       - peer1.jd.com:/var/hyperledger/production
  102.     depends_on:
  103.       - orderer.qq.com
  104.   # 客户端节点
  105.   cli:
  106.     container_name: cli
  107.     image: hyperledger/fabric-tools:1.4.12
  108.     tty: true
  109.     environment:
  110.       # go 环境设置
  111.       - GO111MODULE=auto
  112.       - GOPROXY=https://goproxy.cn
  113.       - CORE_PEER_ID=cli
  114.     command: /bin/bash
  115.     volumes:
  116.       - ./config:/etc/hyperledger/config
  117.       - ./crypto-config/peerOrganizations/taobao.com/:/etc/hyperledger/peer/taobao.com
  118.       - ./crypto-config/peerOrganizations/jd.com/:/etc/hyperledger/peer/jd.com
  119.       - ./../chaincode:/opt/gopath/src/chaincode # 链码路径注入
  120.     networks:
  121.       - fabric_network
  122.     depends_on:
  123.       - orderer.qq.com
  124.       - peer0.taobao.com
  125.       - peer1.taobao.com
  126.       - peer0.jd.com
  127.       - peer1.jd.com

        如果是mac电脑使用M1,M2芯片,则需要添加这一行

       为了方便,这里我还定义了一个 docker-compose-base.yaml 作为 Peer 节点的公共模板,内容如下:

  1. version: '2.1'
  2. services:
  3.   peer-base: # peer的公共服务
  4.     image: hyperledger/fabric-peer:1.4.12
  5.     environment:
  6.       - GODEBUG=netdns=go
  7.       - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
  8.       - CORE_LOGGING_PEER=info
  9.       - CORE_CHAINCODE_LOGGING_LEVEL=INFO
  10.       - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/msp # msp证书(节点证书)
  11.       - CORE_LEDGER_STATE_STATEDATABASE=goleveldb # 状态数据库的存储引擎(or CouchDB)
  12.       - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_network # docker 网络
  13.     volumes:
  14.       - /var/run/docker.sock:/host/var/run/docker.sock
  15.     working_dir: /opt/gopath/src/github.com/hyperledger/fabric
  16.     command: peer node start
  17.     networks:
  18.       - fabric_network

       如果是mac电脑使用M1,M2芯片,则需要添加这一行

platform: linux/x86_64

        在上面这个位置

        注意观察,在 volumes 配置项中,我们将 config 和 crypto-config 内的配置文件都挂载到相对应的节点中了。并且在 peer 的公共服务中,我们还挂载了 /var/run/docker.sock 文件,有了该文件,在容器内就可以向其发送 http 请求和 Docker Daemon 通信,通俗理解,就是有了它,就可以在容器内操作宿主机的 Docker 了,比如在容器内控制 Docker 再启动一个容器出来。而这,就是为了后面可以部署智能合约(节点部署链码其实就是启动一个链码容器)。

        接下来将节点启动

docker-compose up -d

        出现以上界面即为启动成功

        7、为 cli 服务配置环境

        接下来我们要使用 cli 服务来执行 peer 命令,所以要为其先配置一下环境变量,使用四个不同的变量 TaobaoPeer0CliTaobaoPeer1CliJDPeer0CliJDPeer1Cli ,代表 cli 服务代表着不同的节点:

        分别输入

  1. TaobaoPeer0Cli="CORE_PEER_ADDRESS=peer0.taobao.com:7051 CORE_PEER_LOCALMSPID=TaobaoMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/taobao.com/users/Admin@taobao.com/msp"
  2. TaobaoPeer1Cli="CORE_PEER_ADDRESS=peer1.taobao.com:7051 CORE_PEER_LOCALMSPID=TaobaoMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/taobao.com/users/Admin@taobao.com/msp"
  3. JDPeer0Cli="CORE_PEER_ADDRESS=peer0.jd.com:7051 CORE_PEER_LOCALMSPID=JDMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/jd.com/users/Admin@jd.com/msp"
  4. JDPeer1Cli="CORE_PEER_ADDRESS=peer1.jd.com:7051 CORE_PEER_LOCALMSPID=JDMSP CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/peer/jd.com/users/Admin@jd.com/msp"

        8、开始创建通道

        通道主要用于实现区块链网络中业务的隔离。一个联盟中可以有多个通道,每个通道可代表一项业务,并且对应一套账本。通道内的成员为业务参与方(即联盟内的组织),一个组织可以加入多个通道。

        我们现在有请 Taobao 组织的 peer0 节点来创建一个通道 appchannel 

        9、将所有节点加入通道

       将所有的节点都加入到通道 appchannel 中(正常是按需加入):

docker exec cli bash -c "$TaobaoPeer0Cli peer channel join -b appchannel.block"

docker exec cli bash -c "$TaobaoPeer1Cli peer channel join -b appchannel.block"

docker exec cli bash -c "$JDPeer0Cli peer channel join -b appchannel.block"

docker exec cli bash -c "$JDPeer1Cli peer channel join -b appchannel.block"

        10、更新锚节点

       锚节点是必需的。普通节点只能发现本组织下的其它节点,而锚节点可以跨组织服务发现到其它组织下的节点,建议每个组织都选择至少一个锚节点。

        利用之前准备好的配置文件,向通道更新锚节点:

docker exec cli bash -c "$TaobaoPeer0Cli peer channel update -o orderer.qq.com:7050 -c appchannel -f /etc/hyperledger/config/TaobaoAnchor.tx" 

docker exec cli bash -c "$JDPeer0Cli peer channel update -o orderer.qq.com:7050 -c appchannel -f /etc/hyperledger/config/JDAnchor.tx"

PART3:编写智能合约

        fabric 的智能合约称为链码,编写智能合约也就是编写链码。

        在0202demo文件夹下新建一个chaincode文件夹

        在里面以 Go 为例,创建一个 main.go 文件:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/hyperledger/fabric/core/chaincode/shim"
  5. pb "github.com/hyperledger/fabric/protos/peer"
  6. )
  7. type MyChaincode struct {
  8. }
  9. // Init 初始化时会执行该方法
  10. func (c *MyChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
  11.     fmt.Println("链码初始化")
  12.     return shim.Success(nil)
  13. }
  14. // Invoke 智能合约的功能函数定义
  15. func (c *MyChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
  16.     funcName, args := stub.GetFunctionAndParameters()
  17.     switch funcName {
  18.     default:
  19.        return shim.Error(fmt.Sprintf("没有该功能: %s", funcName))
  20.     }
  21. }
  22. func main() {
  23.     err := shim.Start(new(MyChaincode))
  24.     if err != nil {
  25.        panic(err)
  26.     }
  27. }

        这里我们定义的 MyChaincode 结构体实现了 shim.Chaincode 接口:

        然后在启动入口 main 函数中调用 shim.Start(new(MyChaincode)) 就完成了链码的启动,例子如下。

        我们知道链码其实就是用来处理区块链网络中的成员一致同意的业务逻辑。比如 Taobao 和 JD 规定了一个规则,将其编写成链码,后面双方就只能遵循这个规则了,因为链码到时候即部署在你的节点,也会部署在我的节点上,你偷偷改了逻辑,我的节点不会认可你的,这也正是区块链的作用之一。

        链码的功能定义在 Invoke 方法中。

        一个简易的示例如下:

  1. package main
  2. import (
  3.     "encoding/json"
  4.     "errors"
  5.     "fmt"
  6.     "strconv"
  7.     "github.com/hyperledger/fabric/core/chaincode/shim"
  8.     pb "github.com/hyperledger/fabric/protos/peer"
  9. )
  10. type MyChaincode struct {
  11. }
  12. // Init 初始化时会执行该方法
  13. func (c *MyChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
  14.     fmt.Println("链码初始化")
  15.     // 假设A有1000元,以复合主键 userA 的形式写入账本
  16.     err := WriteLedger(stub, map[string]interface{}{"name": "A", "balance": 1000}, "user", []string{"A"})
  17.     if err != nil {
  18.        return shim.Error(err.Error())
  19.     }
  20.     // 假设B有1000元,以复合主键 userB 的形式写入账本
  21.     err = WriteLedger(stub, map[string]interface{}{"name": "B", "balance": 1000}, "user", []string{"B"})
  22.     if err != nil {
  23.        return shim.Error(err.Error())
  24.     }
  25.     return shim.Success(nil)
  26. }
  27. // Invoke 智能合约的功能函数定义
  28. func (c *MyChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
  29.     funcName, args := stub.GetFunctionAndParameters()
  30.     switch funcName {
  31.     case "query":
  32.        return query(stub, args)
  33.     case "transfer":
  34.        return transfer(stub, args)
  35.     default:
  36.        return shim.Error(fmt.Sprintf("没有该功能: %s", funcName))
  37.     }
  38. }
  39. func query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
  40.     // 如果 args 为空,则表示查询所有 user
  41.     results, err := ReadLedger(stub, "user", args)
  42.     if err != nil {
  43.        return shim.Error(err.Error())
  44.     }
  45.     var users []map[string]interface{}
  46.     for _, result := range results {
  47.        var user map[string]interface{}
  48.        if err = json.Unmarshal(result, &user); err != nil {
  49.           return shim.Error(err.Error())
  50.        }
  51.        users = append(users, user)
  52.     }
  53.     usersByte, err := json.Marshal(&users)
  54.     if err != nil {
  55.        return shim.Error(err.Error())
  56.     }
  57.     return shim.Success(usersByte)
  58. }
  59. func transfer(stub shim.ChaincodeStubInterface, args []string) pb.Response {
  60.     // 验证参数
  61.     if len(args) != 3 {
  62.        return shim.Error("参数个数不满足")
  63.     }
  64.     from := args[0]
  65.     to := args[1]
  66.     money, err := strconv.ParseFloat(args[2], 64)
  67.     if err != nil {
  68.        return shim.Error(err.Error())
  69.     }
  70.     // 从账本查询 from 用户
  71.     fromResults, err := ReadLedger(stub, "user", []string{from})
  72.     if err != nil {
  73.        return shim.Error(err.Error())
  74.     }
  75.     if len(fromResults) != 1 {
  76.        return shim.Error("没有该用户 " + from)
  77.     }
  78.     var fromUser map[string]interface{}
  79.     if err = json.Unmarshal(fromResults[0], &fromUser); err != nil {
  80.        return shim.Error(err.Error())
  81.     }
  82.     // 从账本查询 to 用户
  83.     toResults, err := ReadLedger(stub, "user", []string{to})
  84.     if err != nil {
  85.        return shim.Error(err.Error())
  86.     }
  87.     if len(toResults) != 1 {
  88.        return shim.Error("没有该用户 " + to)
  89.     }
  90.     var toUser map[string]interface{}
  91.     if err = json.Unmarshal(toResults[0], &toUser); err != nil {
  92.        return shim.Error(err.Error())
  93.     }
  94.     // from 用户扣除余额
  95.     if money > fromUser["balance"].(float64) {
  96.        return shim.Error("余额不足")
  97.     }
  98.     fromUser["balance"] = fromUser["balance"].(float64) - money
  99.     // to 用户增加余额
  100.     toUser["balance"] = toUser["balance"].(float64) + money
  101.     // 写回账本
  102.     err = WriteLedger(stub, fromUser, "user", []string{from})
  103.     if err != nil {
  104.        return shim.Error(err.Error())
  105.     }
  106.     err = WriteLedger(stub, toUser, "user", []string{to})
  107.     if err != nil {
  108.        return shim.Error(err.Error())
  109.     }
  110.     return shim.Success([]byte("ok"))
  111. }
  112. func main() {
  113.     err := shim.Start(new(MyChaincode))
  114.     if err != nil {
  115.        panic(err)
  116.     }
  117. }
  118. // WriteLedger 写入账本
  119. // obj 为要写入的数据
  120. // objectType和keys 共同组成复合主键
  121. func WriteLedger(stub shim.ChaincodeStubInterface, obj interface{}, objectType string, keys []string) error {
  122.     //创建复合主键
  123.     var key string
  124.     if val, err := stub.CreateCompositeKey(objectType, keys); err != nil {
  125.        return errors.New(fmt.Sprintf("%s-创建复合主键出错 %s", objectType, err.Error()))
  126.     } else {
  127.        key = val
  128.     }
  129.     bytes, err := json.Marshal(obj)
  130.     if err != nil {
  131.        return err
  132.     }
  133.     //写入区块链账本
  134.     if err := stub.PutState(key, bytes); err != nil {
  135.        return errors.New(fmt.Sprintf("%s-写入区块链账本出错: %s", objectType, err.Error()))
  136.     }
  137.     return nil
  138. }
  139. // ReadLedger 根据复合主键查询账本数据(适合获取全部或指定的数据)
  140. // objectType和keys 共同组成复合主键
  141. func ReadLedger(stub shim.ChaincodeStubInterface, objectType string, keys []string) (results [][]byte, err error) {
  142.     // 通过主键从区块链查找相关的数据,相当于对主键的模糊查询
  143.     resultIterator, err := stub.GetStateByPartialCompositeKey(objectType, keys)
  144.     if err != nil {
  145.        return nil, errors.New(fmt.Sprintf("%s-获取全部数据出错: %s", objectType, err))
  146.     }
  147.     defer resultIterator.Close()
  148.     //检查返回的数据是否为空,不为空则遍历数据,否则返回空数组
  149.     for resultIterator.HasNext() {
  150.        val, err := resultIterator.Next()
  151.        if err != nil {
  152.           return nil, errors.New(fmt.Sprintf("%s-返回的数据出错: %s", objectType, err))
  153.        }
  154.        results = append(results, val.GetValue())
  155.     }
  156.     return results, nil
  157. }

       在这段链码中,初始化的时候我们假设有用户 A 和 B ,并且都各自有 1000 元余额,我们在 Invoke 方法中为其定义了两个功能函数 query 和 transfer 。 其中 query 函数可以查询 A 和 B 或指定用户的余额信息, transfer 函数可以通过传入转账人,被转账人,金额,三个参数来实现转账功能。例如 {"Args":["transfer","A","B","100.0"]}代表 A 向 B 转账 100 元。

PART4:部署链码

        我们将刚刚编写的智能合约也就是链码安装到区块链网络中,同样是借助 cli 服务,我们在 Taobao 组织的 peer0 节点和 JD 组织的 peer0 节点上都安装上链码:

docker exec cli bash -c "$TaobaoPeer0Cli peer chaincode install -n fabric-realty -v 1.0.0 -l golang -p chaincode"

docker exec cli bash -c "$JDPeer0Cli peer chaincode install -n fabric-realty -v 1.0.0 -l golang -p chaincode"

        其中 -n 参数是链码名称,可以自己随便设置,-v 是链码版本号,-p 是链码的目录(我们已经将链码挂载到cli 容器中了,在 /opt/gopath/src/ 目录下)

        链码安装后,还需要实例化后才可以使用,只需要在任意一个节点实例化就可以了,以 Taobao 组织的 peer0 节点为例:

docker exec cli bash -c "$TaobaoPeer0Cli peer chaincode instantiate -o orderer.qq.com:7050 -C appchannel -n fabric-realty -l golang -v 1.0.0 -c '{\"Args\":[\"init\"]}' -P \"AND ('TaobaoMSP.member','JDMSP.member')\""

        实例化链码主要就是传入 {"Args":["init"]} 参数,此时会调用我们编写的 func (c *MyChaincode) Init 方法,进行链码的初始化。其中 -P 参数用于指定链码的背书策略,AND ('TaobaoMSP.member','JDMSP.member') 代表链码的写入操作需要同时得到 Taobao和 JD 组织成员的背书才允许通过。AND 也可以替换成 OR,代表任意一组织成员背书即可,更多具体用法,可以去看官方文档。

        链码实例化成功之后就会启动链码容器,而启动的方法,就是我们之前提过的 peer 节点服务挂载了/var/run/docker.sock 文件。

        查看启动的链码容器:

docker ps -a | awk '($2 ~ /dev-peer.*fabric-realty.*/) {print $2}'

        因为我们使用 Taobao 组织的 peer0 节点实例化链码,所以此时还只有这个节点的链码容器启动起来了。

        我们可以试着使用 cli 服务去调用链码:

docker exec cli bash -c "$TaobaoPeer0Cli peer chaincode invoke -C appchannel -n fabric-realty -c '{\"Args\":[\"query\"]}'"

        使用JD组织的节点也是可以的:

docker exec cli bash -c "$JDPeer0Cli peer chaincode invoke -C appchannel -n fabric-realty -c '{\"Args\":[\"query\"]}'"

        此时,因为我们查询了 JD 组织的 peer0 节点上的链码,所以对应的链码容器也会启动起来了,再次查看启动的链码容器:

        现在,我们的智能合约就成功部署到区块链网络的通道中了。

PART5:编写应用程序

        在0202demo里新建一个chaincode文件夹,在里面新建一个config.yaml,配置如下:

  1. version: 1.0.0
  2. # GO SDK 客户端配置
  3. client:
  4.   # 客户端所属的组织,必须是organizations定义的组织
  5.   organization: JD
  6.   # 日志级别
  7.   logging:
  8.     level: info
  9.   # MSP证书的根路径
  10.   cryptoconfig:
  11.     path: /network/crypto-config
  12. # 通道定义
  13. channels:
  14.   appchannel:
  15.     orderers:
  16.       - orderer.qq.com
  17.     peers:
  18.       peer0.jd.com:
  19.         endorsingPeer: true
  20.         chaincodeQuery: true
  21.         ledgerQuery: true
  22.         eventSource: true
  23.       peer1.jd.com:
  24.         endorsingPeer: true
  25.         chaincodeQuery: true
  26.         ledgerQuery: true
  27.         eventSource: true
  28. # 组织配置
  29. organizations:
  30.   JD:
  31.     mspid: "JDMSP"
  32.     cryptoPath: peerOrganizations/jd.com/users/{username}@jd.com/msp
  33.     peers:
  34.       - peer0.jd.com
  35.       - peer1.jd.com
  36. # orderer节点列表
  37. orderers:
  38.   orderer.qq.com:
  39.     url: orderer.qq.com:7050
  40.     # 传递给gRPC客户端构造函数
  41.     grpcOptions:
  42.       ssl-target-name-override: orderer.qq.com
  43.       keep-alive-time: 0s
  44.       keep-alive-timeout: 20s
  45.       keep-alive-permit: false
  46.       fail-fast: false
  47.       allow-insecure: true
  48. # peers节点列表
  49. peers:
  50.   # peer节点定义,可以定义多个
  51.   peer0.jd.com:
  52.     # URL用于发送背书和查询请求
  53.     url: peer0.jd.com:7051
  54.     # 传递给gRPC客户端构造函数
  55.     grpcOptions:
  56.       ssl-target-name-override: peer0.jd.com
  57.       keep-alive-time: 0s
  58.       keep-alive-timeout: 20s
  59.       keep-alive-permit: false
  60.       fail-fast: false
  61.       allow-insecure: true
  62.   peer1.jd.com:
  63.     url: peer1.jd.com:7051
  64.     grpcOptions:
  65.       ssl-target-name-override: peer1.jd.com
  66.       keep-alive-time: 0s
  67.       keep-alive-timeout: 20s
  68.       keep-alive-permit: false
  69.       fail-fast: false
  70.       allow-insecure: true
  71.   peer0.taobao.com:
  72.     url: peer0.taobao.com:7051
  73.     grpcOptions:
  74.       ssl-target-name-override: peer0.taobao.com
  75.       keep-alive-time: 0s
  76.       keep-alive-timeout: 20s
  77.       keep-alive-permit: false
  78.       fail-fast: false
  79.       allow-insecure: true
  80.   peer1.taobao.com:
  81.     url: peer1.taobao.com:7051
  82.     grpcOptions:
  83.       ssl-target-name-override: peer1.taobao.com
  84.       keep-alive-time: 0s
  85.       keep-alive-timeout: 20s
  86.       keep-alive-permit: false
  87.       fail-fast: false
  88.       allow-insecure: true

        我们假定是 JD 组织来编写这个应用程序,该配置主要就是用于验证 JD 组织及其节点的身份。其中组织配置中 {username} 为动态传递, MSP 证书的根路径我们后续会挂载进去。

        现在开始编写代码,我们先来实例化 SDK ,创建 sdk.go:

        这里其实可以偷懒一些直接在电脑上先用pycharm写好,同样项目名为chaincode

        sdk.go代码如下:

  1. package main
  2. import (
  3.     "github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
  4.     "github.com/hyperledger/fabric-sdk-go/pkg/core/config"
  5.     "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
  6. )
  7. // 配置信息
  8. var (
  9.     sdk           *fabsdk.FabricSDK                              // Fabric SDK
  10.     channelName   = "appchannel"                                 // 通道名称
  11.     username      = "Admin"                                      // 用户
  12.     chainCodeName = "fabric-realty"                              // 链码名称
  13.     endpoints     = []string{"peer0.jd.com", "peer0.taobao.com"} // 要发送交易的节点
  14. )
  15. // init 初始化
  16. func init() {
  17.     var err error
  18.     // 通过配置文件初始化SDK
  19.     sdk, err = fabsdk.New(config.FromFile("config.yaml"))
  20.     if err != nil {
  21.        panic(err)
  22.     }
  23. }
  24. // ChannelExecute 区块链交互
  25. func ChannelExecute(fcn string, args [][]byte) (channel.Response, error) {
  26.     // 创建客户端,表明在通道的身份
  27.     ctx := sdk.ChannelContext(channelName, fabsdk.WithUser(username))
  28.     cli, err := channel.New(ctx)
  29.     if err != nil {
  30.        return channel.Response{}, err
  31.     }
  32.     // 对区块链账本的写操作(调用了链码的invoke)
  33.     resp, err := cli.Execute(channel.Request{
  34.        ChaincodeID: chainCodeName,
  35.        Fcn:         fcn,
  36.        Args:        args,
  37.     }, channel.WithTargetEndpoints(endpoints...))
  38.     if err != nil {
  39.        return channel.Response{}, err
  40.     }
  41.     //返回链码执行后的结果
  42.     return resp, nil
  43. }
  44. // ChannelQuery 区块链查询
  45. func ChannelQuery(fcn string, args [][]byte) (channel.Response, error) {
  46.     // 创建客户端,表明在通道的身份
  47.     ctx := sdk.ChannelContext(channelName, fabsdk.WithUser(username))
  48.     cli, err := channel.New(ctx)
  49.     if err != nil {
  50.        return channel.Response{}, err
  51.     }
  52.     // 对区块链账本查询的操作(调用了链码的invoke),只返回结果
  53.     resp, err := cli.Query(channel.Request{
  54.        ChaincodeID: chainCodeName,
  55.        Fcn:         fcn,
  56.        Args:        args,
  57.     }, channel.WithTargetEndpoints(endpoints...))
  58.     if err != nil {
  59.        return channel.Response{}, err
  60.     }
  61.     //返回链码执行后的结果
  62.     return resp, nil
  63. }

        在这段代码中,我们将使用 Admin 的身份去调用合约,并将每次的交易同时发送给 peer0.jd.com 和peer0.taobao.com 节点进行背书,这是因为我们在实例化链码的时候指定了背书策略为 AND ('TaobaoMSP.member','JDMSP.member') ,代表交易需要同时得到 Taobao和 JD 组织成员的背书才允许通过。每次写入账本时,会验证这两个节点的数据一致性,只有当这两个节点的数据一致时,交易才算最终成功。

        继续编写 main.go ,我们使用 gin 来创建一个 http 服务:

        先运行go get -u github.com/gin-gonic/gin下载gin包

        main.go代码如下:

  1. package main
  2. import (
  3.     "bytes"
  4.     "encoding/json"
  5.     "github.com/gin-gonic/gin"
  6. )
  7. func main() {
  8.     g := gin.Default()
  9.     g.GET("/query", func(c *gin.Context) {
  10.        args := make([][]byte, 0)
  11.        user := c.Query("user")
  12.        if user != "" {
  13.           args = append(args, []byte(user))
  14.        }
  15.        // 调用链码的query函数
  16.        resp, err := ChannelQuery("query", args)
  17.        if err != nil {
  18.           c.AbortWithStatusJSON(500, gin.H{"err": err.Error()})
  19.           return
  20.        }
  21.        var data []map[string]interface{}
  22.        if err = json.Unmarshal(bytes.NewBuffer(resp.Payload).Bytes(), &data); err != nil {
  23.           c.AbortWithStatusJSON(500, gin.H{"err": err.Error()})
  24.           return
  25.        }
  26.        c.JSON(200, data)
  27.     })
  28.     g.POST("/transfer", func(c *gin.Context) {
  29.        from := c.Query("from")
  30.        to := c.Query("to")
  31.        money := c.Query("money")
  32.        if from == "" || to == "" || money == "" {
  33.           c.AbortWithStatusJSON(400, gin.H{"err": "参数不能为空"})
  34.           return
  35.        }
  36.        args := make([][]byte, 0)
  37.        args = append(args, []byte(from), []byte(to), []byte(money))
  38.        // 调用链码的transfer函数
  39.        resp, err := ChannelExecute("transfer", args)
  40.        if err != nil {
  41.           c.AbortWithStatusJSON(500, gin.H{"err": err.Error()})
  42.           return
  43.        }
  44.        c.JSON(200, gin.H{"msg": string(resp.Payload)})
  45.     })
  46.     g.Run("0.0.0.0:8000")
  47. }

        然后编写dockerfile文件

  1. FROM golang:1.20 AS server
  2. ENV GO111MODULE=on
  3. ENV GOPROXY https://goproxy.cn,direct
  4. WORKDIR /app
  5. COPY . .
  6. RUN CGO_ENABLED=0 go build -v -o "server" .
  7. RUN chmod +x server
  8. FROM scratch
  9. WORKDIR /app
  10. COPY --from=server /app/server ./
  11. COPY config.yaml ./
  12. ENTRYPOINT ["./server"]

        docker-compose.yml 文件:

  1. version: '2.1'
  2. networks:
  3.   fabric_network:
  4.     external:
  5.       name: fabric_network
  6. services:
  7.   app:
  8.     build: .
  9.     image: app:latest
  10.     ports:
  11.       - "8000:8000"
  12.     volumes:
  13.       - ./../network/crypto-config:/network/crypto-config # 挂载搭建区块链网络时生成的crypto-config文件夹
  14.     networks:
  15.       - fabric_network

        其中挂载的 crypto-config 文件夹就是之前搭建区块链网络时生成的。

        接下来运行docker-compose build

        如果出现问题如下:

       可能是go没有配置镜像的问题,在环境中添加:

export GOPROXY=https://goproxy.cn

        然后执行

  1. go mod tidy
  2. go mod download

        即可正常

        运行docker-compose up,效果如下:

        接着可以通过CURL测试:

curl "http://localhost:8000/query"

​curl http://localhost:8000/query?user=A

​curl http://localhost:8000/query?user=B

​curl -X POST http://localhost:8000/transfer?from=A&to=B&money=500

curl http://localhost:8000/query

        如果想要通过ip在浏览器上查看,不要忘记打开8000的端口和防火墙。

        测试完成,完结撒花~

        拓展可以添加前端页面。流程呢,和传统前后端分离架构也没什么区别。

        最后感谢原文博主的支持,特此奉上原文。

 万字长文,教你用go开发区块链应用

        本人也是根据以上的学习再加以总结写的这篇文章。

      

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/931535
推荐阅读
相关标签
  

闽ICP备14008679号