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 execution command (bin), configuration (config) and MSP folder in the environment variable:
Execute vim /etc/profile and 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
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
- The Contract class is identified by @ Contract and @ Default annotations, and implements the ContractInterface interface
The contract method is identified with the @ Transaction annotation
Transaction.TYPE.SUBMIT is a write transaction
Transaction.TYPE.EVALUATE as query- It includes three transaction methods: init, addUser and transfer
- 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
- The transaction data uses the peer chaincode invoke [flags] command, which will attempt to submit the endorsed transaction to the network.
- 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"]}'