Hyperledger Fabric 2.x custom smart contract

1. Description

In order to continuously update information and manage the ledger (write transactions, query, etc.), the blockchain network introduces smart contracts to access and control the ledger; smart contracts are called chaincode in Fabric, which 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. Environment 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”

- Start two peer nodes and one orderer node
- Created mychannel channel

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

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

3. Download contract code

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

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

4. 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 remote warehouse

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

Dependency 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 through the plugin maven-shade-plugin

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

Create the data object User of the contract using the @DataType annotation to identify, define three fields userId, name, money and use the @Property annotation to identify:

@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()})
                 & amp; & amp;
                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 the ContractInterface interface
  2. Contract methods are identified using the @Transaction annotation

    Transaction.TYPE.SUBMIT is write transaction Transaction.TYPE.EVALUATE is query

  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() {}

    /**
     * Initialize 3 records
     */
    @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);
    }

    /**
     * New users
     */
    @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;
    }

    /**
     * Query a 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;
    }

    /**
     * Query all users
     */
    @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);
    }

    /**
     * transfer
     * @param sourceId source user id
     * @param targetId target user id
     * @param money amount
     */
    @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. Package 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, and the following are the installations for the two organizations.

6.1. Install contract for organization peer0.org1

Execute the following command to set up the 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/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:7051

Execute the following command to install:

peer lifecycle chaincode install mycc.tar.gz

Returns on success:

2022-02-09 22:09:13.498 EST 0001 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Installed remotely: response:<status:200 payload:"\\
Emycc:4c8dce2c7f746d26293ca8f27a3ccde c8d6438090f873f40f8ac9508c01973ae\022\004mycc">
2022-02-09 22:09:13.498 EST 0002 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Chaincode code package identifier: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f 8ac9508c01973ae

6.2. Install contract for organization peer0.org2

Execute the following command to set up the 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/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:9051

Execute the following command to install:

peer lifecycle chaincode install mycc.tar.gz

Returns on success:

2022-02-09 22:14:14.862 EST 0001 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Installed remotely: response:<status:200 payload:"\\
Emycc:4c8dce2c7f746d26293ca8f27a3ccde c8d6438090f873f40f8ac9508c01973ae\022\004mycc">
2022-02-09 22:14:14.862 EST 0002 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Chaincode code package identifier: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f 8ac9508c01973ae

View the list of installed contracts:

peer lifecycle chaincode query installed

Return the contract’s Package ID and Label:

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

7. Approve contract

After the contract is installed, it must be approved by the agency before it can be used.

7.1. Approval contract definition for organization peer0.org1

Execute the following command to set up the 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/[email protected]/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 accordingly.

Returns 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. Approval contract definition for organization peer0.org2

Execute the following command to set up the 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/[email protected]/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 accordingly.

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

Returns 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 submitted contracts 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 the smart contract

  1. Transaction data uses the peer chaincode invoke [flags] command, which will attempt to commit endorsed transactions to the network.
  2. Query data using peer chaincode query [flags], this command will not generate transactions.

Since the invoke command requires many parameters, we first create a script command. Execute vim invoke.sh and 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. Initialize 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. Query data

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

Execute the following command to set up the 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/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:7051

Execute the following command to call the 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 the getUser method to pass in the 1 parameter, and query a single data:

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

Return the data with id 1 after execution:

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

9.3. Add data

Execute the following command to call the addUser method to add a new record with id 4:

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

9.4. Transfer

Execute the following command to call the 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 check:

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

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Cloud native entry skill treeHome pageOverview 14086 people are studying systematically