前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Hyperledger Fabric 2.x 自定义智能合约

Hyperledger Fabric 2.x 自定义智能合约

作者头像
陶陶技术笔记
发布2022-02-25 09:54:52
8890
发布2022-02-25 09:54:52
举报
文章被收录于专栏:陶陶技术笔记陶陶技术笔记

一、说明

为了持续地进行信息的更新,以及对账本进行管理(写入交易,进行查询等),区块链网络引入了智能合约来实现对账本的访问和控制;智能合约在 Fabric 中称之为 链码,是区块链应用的业务逻辑。

本文分享如何使用 Java 语言开发智能合约,以及合约的安装与使用。

二、环境准备

1、部署好 Fabric 的测试网络,按照上一篇文章《Hyperledger Fabric 2.x 环境搭建》的内容执行第1至5步

代码语言:javascript
复制
- 启动好两个 peer 节点和一个 orderer 节点
- 创建好 mychannel 通道

2、在环境变量中配置好执行命令(bin)、配置(config)与MSP文件夹的路径:执行 vim /etc/profile 添加以下内容:

代码语言:javascript
复制
export FABRIC_PATH=/opt/gopath/src/github.com/hyperledger/fabric-samples
export FABRIC_CFG_PATH=${FABRIC_PATH}/config/
export MSP_PATH=${FABRIC_PATH}/test-network/organizations
export CORE_PEER_TLS_ENABLED=true
export PATH=${FABRIC_PATH}/bin:$PATH

FABRIC_PATH路径按实际进行修改。

三、下载合约代码

gitee:https://gitee.com/zlt2000_admin/my-fabric-chaincode-java

github:https://github.com/zlt2000/my-fabric-chaincode-java

四、代码解析

Fabric 2.x 版本后的合约编写方式与旧版本略有不同,需要实现 ContractInterface 接口,下面是官方的一段说明:

All chaincode implementations must extend the abstract class ChaincodeBase. It is possible to implement chaincode by extending ChaincodeBase directly however new projects should implement org.hyperledger.fabric.contract.ContractInterface and use the contract programming model instead.

4.1. pom.xml文件

配置远程仓库

代码语言:javascript
复制
<repositories>
  <repository>
    <id>central</id>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <releases>
      <enabled>true</enabled>
    </releases>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>
  <repository>
    <id>jitpack.io</id>
    <url>https://www.jitpack.io</url>
  </repository>
  <repository>
    <id>artifactory</id>
    <url>https://hyperledger.jfrog.io/hyperledger/fabric-maven</url>
  </repository>
</repositories>

依赖合约sdk

代码语言:javascript
复制
<dependency>
  <groupId>org.hyperledger.fabric-chaincode-java</groupId>
  <artifactId>fabric-chaincode-shim</artifactId>
  <version>${fabric-chaincode-java.version}</version>
</dependency>

通过插件 maven-shade-plugin 指定 mainClassorg.hyperledger.fabric.contract.ContractRouter

新版本所有合约的 mainClass 都为 org.hyperledger.fabric.contract.ContractRouter

代码语言:javascript
复制
<build>
  <sourceDirectory>src/main/java</sourceDirectory>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>${java.version}</source>
        <target>${java.version}</target>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <version>3.1.0</version>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>shade</goal>
          </goals>
          <configuration>
            <finalName>chaincode</finalName>
            <transformers>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>org.hyperledger.fabric.contract.ContractRouter</mainClass>
              </transformer>
            </transformers>
            <filters>
              <filter>
                <!-- filter out signature files from signed dependencies, else repackaging fails with security ex -->
                <artifact>*:*</artifact>
                <excludes>
                  <exclude>META-INF/*.SF</exclude>
                  <exclude>META-INF/*.DSA</exclude>
                  <exclude>META-INF/*.RSA</exclude>
                </excludes>
              </filter>
            </filters>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

4.2. model

创建合约的数据对象 User 使用 @DataType 注解标识,定义三个字段 userIdnamemoney 使用 @Property 注解标识:

代码语言:javascript
复制
@DataType
public class User {
    @Property
    private final String userId;

    @Property
    private final String name;

    @Property
    private final double money;

    public User(final String userId, final String name, final double money) {
        this.userId = userId;
        this.name = name;
        this.money = money;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if ((obj == null) || (getClass() != obj.getClass())) {
            return false;
        }
        User other = (User) obj;
        return Objects.deepEquals(
                new String[] {getUserId(), getName()},
                new String[] {other.getUserId(), other.getName()})
                &&
                Objects.deepEquals(
                        new double[] {getMoney()},
                        new double[] {other.getMoney()});
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUserId(), getName(), getMoney());
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }

    public String getUserId() {
        return userId;
    }

    public String getName() {
        return name;
    }

    public double getMoney() {
        return money;
    }
}

4.3. 合约逻辑

  1. 合约类使用 @Contract@Default 注解标识,并实现 ContractInterface 接口
  2. 合约方法使用 @Transaction 注解标识Transaction.TYPE.SUBMIT 为 「写入交易」 Transaction.TYPE.EVALUATE 为 「查询」
  3. 包含3个交易方法:initaddUsertransfer
  4. 包含2个查询方法:getUserqueryAll
代码语言:javascript
复制
@Contract(name = "mycc")
@Default
public class MyAssetChaincode implements ContractInterface {
    public  MyAssetChaincode() {}

    /**
     * 初始化3条记录
     */
    @Transaction(intent = Transaction.TYPE.SUBMIT)
    public void init(final Context ctx) {
        addUser(ctx, "1", "zlt",100D);
        addUser(ctx, "2", "admin",200D);
        addUser(ctx, "3", "guest",300D);
    }

    /**
     * 新增用户
     */
    @Transaction(intent = Transaction.TYPE.SUBMIT)
    public User addUser(final Context ctx, final String userId, final String name, final double money) {
        ChaincodeStub stub = ctx.getStub();
        User user = new User(userId, name, money);
        String userJson = JSON.toJSONString(user);
        stub.putStringState(userId, userJson);
        return user;
    }

    /**
     * 查询某个用户
     */
    @Transaction(intent = Transaction.TYPE.EVALUATE)
    public User getUser(final Context ctx, final String userId) {
        ChaincodeStub stub = ctx.getStub();
        String userJSON = stub.getStringState(userId);
        if (userJSON == null || userJSON.isEmpty()) {
            String errorMessage = String.format("User %s does not exist", userId);
            throw new ChaincodeException(errorMessage);
        }
        User user = JSON.parseObject(userJSON, User.class);
        return user;
    }

    /**
     * 查询所有用户
     */
    @Transaction(intent = Transaction.TYPE.EVALUATE)
    public String queryAll(final Context ctx) {
        ChaincodeStub stub = ctx.getStub();
        List<User> userList = new ArrayList<>();
        QueryResultsIterator<KeyValue> results = stub.getStateByRange("", "");
        for (KeyValue result: results) {
            User user = JSON.parseObject(result.getStringValue(), User.class);
            System.out.println(user);
            userList.add(user);
        }
        return JSON.toJSONString(userList);
    }

    /**
     * 转账
     * @param sourceId 源用户id
     * @param targetId 目标用户id
     * @param money 金额
     */
    @Transaction(intent = Transaction.TYPE.SUBMIT)
    public void transfer(final Context ctx, final String sourceId, final String targetId, final double money) {
        ChaincodeStub stub = ctx.getStub();
        User sourceUser = getUser(ctx, sourceId);
        User targetUser = getUser(ctx, targetId);
        if (sourceUser.getMoney() < money) {
            String errorMessage = String.format("The balance of user %s is insufficient", sourceId);
            throw new ChaincodeException(errorMessage);
        }
        User newSourceUser = new User(sourceUser.getUserId(), sourceUser.getName(), sourceUser.getMoney() - money);
        User newTargetUser = new User(targetUser.getUserId(), targetUser.getName(), targetUser.getMoney() + money);
        stub.putStringState(sourceId, JSON.toJSONString(newSourceUser));
        stub.putStringState(targetId, JSON.toJSONString(newTargetUser));
    }
}

五、打包合约代码

把合约源代码打包成压缩文件,用于后续安装:

代码语言:javascript
复制
peer lifecycle chaincode package mycc.tar.gz --path /opt/app/my-fabric-chaincode-java --lang java --label mycc

六、安装合约

在指定 peer 节点上安装链码,下面分别为两个机构安装。

6.1. 为机构peer0.org1安装合约

执行以下命令,设置 peer0.org1 环境:

代码语言:javascript
复制
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

执行以下命令安装:

代码语言:javascript
复制
peer lifecycle chaincode install mycc.tar.gz

成功后返回:

代码语言:javascript
复制
2022-02-09 22:09:13.498 EST 0001 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Installed remotely: response:<status:200 payload:"\nEmycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae\022\004mycc" > 
2022-02-09 22:09:13.498 EST 0002 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Chaincode code package identifier: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae

6.2. 为机构peer0.org2安装合约

执行以下命令,设置 peer0.org2 环境:

代码语言:javascript
复制
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

执行以下命令安装:

代码语言:javascript
复制
peer lifecycle chaincode install mycc.tar.gz

成功后返回:

代码语言:javascript
复制
2022-02-09 22:14:14.862 EST 0001 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Installed remotely: response:<status:200 payload:"\nEmycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae\022\004mycc" > 
2022-02-09 22:14:14.862 EST 0002 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Chaincode code package identifier: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae

查看安装的合约清单:

代码语言:javascript
复制
peer lifecycle chaincode queryinstalled

返回合约的 Package IDLabel

代码语言:javascript
复制
Installed chaincodes on peer:
Package ID: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae, Label: mycc

七、审批合约

当合约安装后,需经过机构的审批达成一致后才允许使用。

7.1. 为机构peer0.org1审批合约定义

执行以下命令,设置 peer0.org1 环境:

代码语言:javascript
复制
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

执行以下命令审批合约:

代码语言:javascript
复制
peer lifecycle chaincode approveformyorg \
  -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls 
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  --channelID mychannel \
  --name mycc \
  --version 1.0 \
  --package-id mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae \
  --sequence 1

package-id 的值按实际进行修改。

成功后返回:

代码语言:javascript
复制
2022-02-09 22:22:38.403 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [2531db2811945a641947000cb15cfd19e0b72da594dfba994f5f79b6bc51bce2] committed with status (VALID) at localhost:7051

7.2. 为机构peer0.org2审批合约定义

执行以下命令,设置 peer0.org2 环境:

代码语言:javascript
复制
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

执行以下命令审批合约:

代码语言:javascript
复制
peer lifecycle chaincode approveformyorg \
  -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  --channelID mychannel \
  --name mycc \
  --version 1.0 \
  --package-id mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae \
  --sequence 1

package-id 的值按实际进行修改。

成功后返回:

代码语言:javascript
复制
2022-02-09 22:22:47.711 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [796a1e0a735e69425bcd5911bdf4b2a8003bbac977c5e60c769f84da6b86ef86] committed with status (VALID) at localhost:9051

7.3. 合约提交检查

检查合约的审批情况,是否可以向通道进行提交:

代码语言:javascript
复制
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name mycc --version 1.0 --sequence 1 --output json

返回:

代码语言:javascript
复制
{
 "approvals": {
  "Org1MSP": true,
  "Org2MSP": true
 }
}

代表 Org1 和 Org2 都审批通过

八、提交合约

执行以下命令,向通道提交合约:

代码语言:javascript
复制
peer lifecycle chaincode commit \
  -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  --channelID mychannel \
  --name mycc \
  --peerAddresses localhost:7051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
  --peerAddresses localhost:9051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
  --version 1.0 \
  --sequence 1

成功后返回:

代码语言:javascript
复制
2022-02-09 22:22:57.445 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [97ded758675113b9339dc9b378a13c0790ea3780855bb8f651758bfb007fc1ec] committed with status (VALID) at localhost:7051
2022-02-09 22:22:57.456 EST 0002 INFO [chaincodeCmd] ClientWait -> txid [97ded758675113b9339dc9b378a13c0790ea3780855bb8f651758bfb007fc1ec] committed with status (VALID) at localhost:9051

查看通道上已经提交的合约:

代码语言:javascript
复制
peer lifecycle chaincode querycommitted --channelID mychannel --name mycc --output json

返回:

代码语言:javascript
复制
{
 "sequence": 1,
 "version": "1.0",
 "endorsement_plugin": "escc",
 "validation_plugin": "vscc",
 "validation_parameter": "EiAvQ2hhbm5lbC9BcHBsaWNhdGlvbi9FbmRvcnNlbWVudA==",
 "collections": {},
 "approvals": {
  "Org1MSP": true,
  "Org2MSP": true
 }
}

九、测试智能合约

  1. 交易数据使用 peer chaincode invoke [flags] 命令,该命令将尝试向网络提交背书过的交易。
  2. 查询数据使用 peer chaincode query [flags],该命令不会生成交易。

由于 invoke 命令所需要的参数较多,所以我们先创建一个脚本命令。执行 vim invoke.sh 添加以下内容:

代码语言:javascript
复制
peer chaincode invoke -o localhost:7050 \
  --ordererTLSHostnameOverride orderer.example.com \
  --tls \
  --cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
  -C mychannel \
  -n mycc \
  --peerAddresses localhost:7051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
  --peerAddresses localhost:9051 \
  --tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
  -c ${1}

9.1. 初始化账本

执行以下命令,调用合约的 init 方法初始化3条账本记录:

代码语言:javascript
复制
sh invoke.sh '{"function":"init","Args":[]}'

9.2. 查询数据

需要连接其中一个 peer 节点进行数据查询

执行以下命令,设置 peer0.org1 环境:

代码语言:javascript
复制
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

执行下面命令,调用 queryAll 方法,查询所有数据:

代码语言:javascript
复制
peer chaincode query -C mychannel -n mycc -c '{"Args":["queryAll"]}'

执行后返回3条数据的数组:

代码语言:javascript
复制
[{"money":100.0,"name":"zlt","userId":"1"},{"money":200.0,"name":"admin","userId":"2"},{"money":300.0,"name":"guest","userId":"3"}]

执行下面命令,调用 getUser 方法传入 1 参数,查询单个数据:

代码语言:javascript
复制
peer chaincode query -C mychannel -n mycc -c '{"Args":["getUser", "1"]}'

执行后返回id为1的数据:

代码语言:javascript
复制
{"money":100,"name":"zlt","userId":"1"}

9.3. 新增数据

执行以下命令,调用 addUser 方法,新增一条id为4的记录:

代码语言:javascript
复制
sh invoke.sh '{"function":"addUser","Args":["4","test","400"]}'

9.4. 转账

执行以下命令,调用 transfer 方法,进行转账操作:

代码语言:javascript
复制
sh invoke.sh '{"function":"transfer","Args":["4","1","400"]}'

转账成功后,使用查询命令进行查看:

peer chaincode query -C mychannel -n mycc -c '{"Args":["queryAll"]}'

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-02-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陶陶技术笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、说明
  • 二、环境准备
  • 三、下载合约代码
  • 四、代码解析
    • 4.1. pom.xml文件
      • 4.2. model
        • 4.3. 合约逻辑
        • 五、打包合约代码
        • 六、安装合约
          • 6.1. 为机构peer0.org1安装合约
            • 6.2. 为机构peer0.org2安装合约
            • 七、审批合约
              • 7.1. 为机构peer0.org1审批合约定义
                • 7.2. 为机构peer0.org2审批合约定义
                  • 7.3. 合约提交检查
                  • 八、提交合约
                  • 九、测试智能合约
                    • 9.1. 初始化账本
                      • 9.2. 查询数据
                        • 9.3. 新增数据
                          • 9.4. 转账
                          相关产品与服务
                          区块链
                          云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档