头图

1. Description

In order to continuously update information and manage the ledger (writing transactions, querying, etc.), the blockchain network introduces smart contracts to access and control the ledger; smart contracts are called chain in Fabric Code is the business logic of blockchain applications.

This article shares how to use the Java language to develop smart contracts, as well as the installation and use of contracts.

 

2. Environmental preparation

1. Deploy the test network of Fabric and perform steps 1 to 5 according to the content of the previous article "Hyperledger Fabric 2.x Environment Construction"

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

2. Configure the path to execute the command (bin), configuration (config) and MSP folder in the environment variable:
Execute vim /etc/profile to add the following:

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
The FABRIC_PATH path is modified as it is.

 

3. Download the contract code

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

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

 

Fourth, code analysis

The contract writing method after the Fabric 2.x version is slightly different from the old version, and the ContractInterface interface needs to be implemented. The following is an official description:

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 file

Configure the remote repository

<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>

 

Depends on contract sdk

<dependency>
        <groupId>org.hyperledger.fabric-chaincode-java</groupId>
        <artifactId>fabric-chaincode-shim</artifactId>
        <version>${fabric-chaincode-java.version}</version>
</dependency>

 

Specify mainClass as org.hyperledger.fabric.contract.ContractRouter via plugin maven-shade-plugin

The mainClass of all contracts in the new version are org.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

The data object User of the created contract is identified with the @DataType annotation, and three fields userId , name , money are identified with the @Property annotation:

@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. Contract logic

  1. The contract class is marked with @Contract and @Default annotations, and implements ContractInterface interface
  2. Contract methods are identified using the @Transaction annotation

    Transaction.TYPE.SUBMIT writes transaction for
    Transaction.TYPE.EVALUATE query
    for
  3. Contains 3 transaction methods: init , addUser , transfer
  4. Contains 2 query methods: getUser , queryAll
@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));
    }
}

 

5. Packaging contract code

Package the contract source code into a compressed file for subsequent installation:

peer lifecycle chaincode package mycc.tar.gz --path /opt/app/my-fabric-chaincode-java --lang java --label mycc

 

6. Installation contract

Install the chain code on the specified peer node. The following are the installations for the two institutions.

6.1. Install the contract for the institution peer0.org1

Execute the following command to set up peer0.org1 environment:

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

Execute the following command to install:

peer lifecycle chaincode install mycc.tar.gz

Return on success:

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. Install the contract for the institution peer0.org2

Execute the following command to set peer0.org2 environment:

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

Execute the following command to install:

peer lifecycle chaincode install mycc.tar.gz

Return on success:

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

Check out the list of installed contracts:

peer lifecycle chaincode queryinstalled

Return Package ID and Label of the contract:

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

 

7. Approval of the contract

When the contract is installed, it is only allowed to be used after the approval of the agency.
 

7.1. Approving contract definitions for institution peer0.org1

Execute the following command to set up peer0.org1 environment:

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

Execute the following command to approve the contract:

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
The value of package-id is modified as it is.

Return on success:

2022-02-09 22:22:38.403 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [2531db2811945a641947000cb15cfd19e0b72da594dfba994f5f79b6bc51bce2] committed with status (VALID) at localhost:7051

 

7.2. Approving contract definitions for agency peer0.org2

Execute the following command to set peer0.org2 environment:

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

Execute the following command to approve the contract:

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
The value of package-id is modified as it is.

Return on success:

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

 

7.3. Contract submission check

Check the approval status of the contract and whether it can be submitted to the channel:

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

return:

{
    "approvals": {
        "Org1MSP": true,
        "Org2MSP": true
    }
}
On behalf of both Org1 and Org2 approved

 

8. Submit the contract

Execute the following command to submit the contract to the channel:

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

Return on success:

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

View the contracts that have been submitted on the channel:

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

return:

{
    "sequence": 1,
    "version": "1.0",
    "endorsement_plugin": "escc",
    "validation_plugin": "vscc",
    "validation_parameter": "EiAvQ2hhbm5lbC9BcHBsaWNhdGlvbi9FbmRvcnNlbWVudA==",
    "collections": {},
    "approvals": {
        "Org1MSP": true,
        "Org2MSP": true
    }
}

 

9. Test smart contracts

  1. Transaction data uses the peer chaincode invoke [flags] command, which will attempt to submit an endorsed transaction to the network.
  2. To query data use peer chaincode query [flags] , this command does not generate a transaction.

Since the invoke command requires many parameters, we first create a script command.
Execute vim invoke.sh add the following:

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. Initializing the ledger

Execute the following command to call the init method of the contract to initialize 3 ledger records:

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

 

9.2. Querying data

Need to connect to one of the peer nodes for data query

Execute the following command to set peer0.org1 environment:

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

Execute the following command to call queryAll method to query all data:

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

After execution, an array of 3 pieces of data is returned:

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

 

Execute the following command, call getUser method and pass 1 parameter to query a single data:

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

Returns data with id 1 after execution:

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

 

9.3. Adding data

Execute the following command, call addUser method, and add a record with an id of 4:

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

 

9.4. Transfer

Execute the following command to call transfer method to perform the transfer operation:

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

After the transfer is successful, use the query command to view:

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


zlt2000
111 声望2.5k 粉丝

具备多年一线互联网分布式系统开发和设计经验,专注分享Java、SpringBoot、SpringCloud、分布式系统/微服务、中间件等领域。