Hyperledger Fabric 2.x custom smart contract

1, Explain

In order to continuously update information and manage the ledger (write transactions, query, etc.), the blockchain network has introduced smart contracts to access and control the ledger; Smart contract is called chain code in Fabric, which is the business logic of blockchain application.

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 Fabric test network according to the previous article Hyperledger Fabric 2.x environment construction Carry out steps 1 to 5 according to the contents of

- Start two peer Node and one orderer node
- Create good mychannel passageway

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

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_ The path is modified according to the actual situation.

 

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 parsing

In fabric 2 The contract writing method after version x is slightly different from that of the old version. 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>

 

Dependent contract sdk

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

 

Specify the mainClass as org through the plugin Maven shade plugin hyperledger. fabric. contract. ContractRouter

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

The data object User creating the contract is identified with @ DataType annotation, and three fields are defined. userId, name and money are identified with @ 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 identified by @ Contract and @ Default annotations, and implements the ContractInterface interface
  2. The contract method is identified with the @ Transaction annotation

    Transaction.TYPE.SUBMIT is the write transaction TYPE. Evaluate as query

  3. It includes three transaction methods: init, addUser and transfer
  4. It contains two query methods: getUser and 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 user
     */
    @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 accounts
     * @param sourceId Source user id
     * @param targetId Target user id
     * @param money amount of 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, 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. The following are the installation of two mechanisms respectively.

6.1. For organization peer0 Org1 installation contract

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

peer lifecycle chaincode install mycc.tar.gz

Return after 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. For organization peer0 Org2 installation contract

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

To view the installed contract list:

peer lifecycle chaincode queryinstalled

Return the Package ID and Label of the contract:

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

 

7, Approve contract

After the contract is installed, it can be used only after the approval of the organization and agreement is reached.  

7.1. For organization peer0 Org1 approve contract definitions

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 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 according to the actual situation.

Return after success:

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

 

7.2. For organization peer0 Org2 approve contract definitions

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 according to the actual situation.

Return after 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 inspection

Check the approval 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
	}
}

Approved on behalf of Org1 and Org2

 

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

Return after 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 smart contract

  1. The transaction data uses the peer chaincode invoke [flags] command, which will attempt to submit the endorsed transaction to the network.
  2. peer chaincode query [flags] is used to query data. This command will not generate transactions.

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. Initialize ledger

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

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

 

9.2. Query data

You need to connect 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, pass in 1 parameter and query single data:

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

Data with id 1 returned after execution:

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

 

9.3. New data

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

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

 

9.4. transfer accounts

Execute the following command and call the transfer method to transfer:

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"]}'

 

Code scanning attention has surprises!

Keywords: Java bash fabric hyperledger

Added by Ken2k7 on Wed, 16 Feb 2022 05:39:53 +0200