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 isorg.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
- The contract class is marked with
@Contract
and@Default
annotations, and implements theContractInterface
interface - Contract methods are identified using the
@Transaction
annotation
Transaction.TYPE.SUBMIT is write transaction Transaction.TYPE.EVALUATE is query
- Contains 3 transaction methods:
init
,addUser
,transfer
- 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
- Transaction data uses the
peer chaincode invoke [flags]
command, which will attempt to commit endorsed transactions to the network. - 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