0
点赞
收藏
分享

微信扫一扫

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

以前干嘛去了 2022-02-16 阅读 50

file

一、说明

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

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

 

二、环境准备

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

- 启动好两个 peer 节点和一个 orderer 节点
- 创建好 mychannel 通道

file

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

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

file

 

三、下载合约代码

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

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

 

四、代码解析

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

4.1. pom.xml文件

配置远程仓库

<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

<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

<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 注解标识:

@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 注解标识
  3. 包含3个交易方法:initaddUsertransfer
  4. 包含2个查询方法:getUserqueryAll
@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));
    }
}

 

五、打包合约代码

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

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 环境:

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

执行以下命令安装:

peer lifecycle chaincode install mycc.tar.gz

成功后返回:

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 环境:

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

执行以下命令安装:

peer lifecycle chaincode install mycc.tar.gz

成功后返回:

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

查看安装的合约清单:

peer lifecycle chaincode queryinstalled

返回合约的 Package IDLabel

Installed chaincodes on peer:
Package ID: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae, Label: mycc

 

七、审批合约

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

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

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

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

执行以下命令审批合约:

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

成功后返回:

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 环境:

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

执行以下命令审批合约:

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

成功后返回:

2022-02-09 22:22:47.711 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [796a1e0a735e69425bcd5911bdf4b2a8003bbac977c5e60c769f84da6b86ef86] committed with status (VALID) at localhost:9051

 

7.3. 合约提交检查

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

peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name mycc --version 1.0 --sequence 1 --output json

返回:

{
    "approvals": {
        "Org1MSP": true,
        "Org2MSP": true
    }
}

 

八、提交合约

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

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

成功后返回:

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

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

peer lifecycle chaincode querycommitted --channelID mychannel --name mycc --output json

返回:

{
    "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 添加以下内容:

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条账本记录:

sh invoke.sh '{"function":"init","Args":[]}'

 

9.2. 查询数据

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

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 方法,查询所有数据:

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

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

[{"money":100.0,"name":"zlt","userId":"1"},{"money":200.0,"name":"admin","userId":"2"},{"money":300.0,"name":"guest","userId":"3"}]

 

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

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

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

{"money":100,"name":"zlt","userId":"1"}

 

9.3. 新增数据

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

sh invoke.sh '{"function":"addUser","Args":["4","test","400"]}'

 

9.4. 转账

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

sh invoke.sh '{"function":"transfer","Args":["4","1","400"]}'

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

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

file

 

扫码关注有惊喜!

file

举报

相关推荐

0 条评论