Hyperledger Fabric Account-based Wallet Java Chaincode
Learning by Example — Account-based Wallet Model in A Transaction System
This is a tutorial for Hyperledger Fabric Java Chaincode. There are Go and NodeJS Chaincodes as well and we might talk about them in the future. Also, I adopt the account-based wallet model as an example, and we will use Hyperledger Fabric “Build Your First Network (BYFN)” as our testing environment (v1.4 network).
In case you don’t know how to start a “BYFN” network, I will also provide the steps, no worry. But If you want to learn how “BYFN” works, you could refer to:
What Account-based Wallet Model Is Basically
Account-based wallet model is one of the implementations of the transaction system, in which, generally speaking, there are wallets and each wallet contains some values or tokens that could be used to transfer. Of course, there are lots of variations on the details of the implementations, such as the token exchange and multi-tokens features.
Features
In the Java Chaincode, we will implement:
- User wallet creation
- User wallet query
- Wallet token transfer (i.e. to send money or tokens to from one wallet another wallet)
[Optional] There are two database options in Hyperledger Fabric to hold the ledger world state — LevelDB and CouchDB, in this tutorial, I will use LevelDB for simplicity.
[Optional] Above, world state (or global state) is not the ledger itself and is separated from the ledger, though it is derived and could be recovered anytime from the ledger. Also, world state is stored in the database to represent the ledger, such that those nodes don’t need to always search the ledger for data retrieval and could retrieve data (states) more quickly from the database.
[Optional] In my load test and some papers about Hyperledger Fabric performance, LevelDB’s performance is better than CouchDB’s. But CouchDB can support rich query that could be used to build a more complex query, compared with the simple key-value query in LevelDB.
The IDE — Tool for Development of Java Chaincode
We use IntelliJ IDEA Community. Also, you need to have JDK. Please install them.
Of course, if you have your own Java IDE, you could also use that. But in this tutorial, I use IntelliJ.
Step 1 — Preparation for Development
Let’s open IntelliJ.
Create New Project in IntelliJ:
Choose Gradle on the left, then Next:
Type your GroupId and ArtifactId. In my case, I use “java_account_model_cc” for both. Then Next:
Then, just next. Now, you should configure your own project location. In my case, I use “~/Desktop/java_account_model_cc”. Click Finish.
1.1 settings.gradle
In the project files on the left, you should have “settings.gradle”. Let’s double click to open it:
Type the following:
1.2 build.gradle
In the project files panel on the left, you should have “build.gradle”. Let’s double click to open it.
Then, type the following:
*If there is a “Gradle project needs to be imported” popup on the bottom right in your IntelliJ, let’s choose “Import Changes”.
1.3 The Chaincode File
In the project files panel on the left, under src > main > java, right click on it and choose New > Java Class:
Type “org.hyperledger.fabric.chaincode.AccountBasedChaincode” for the Name field:
Then, you should have the following:
AccountBasedChaincode(.java) above is the place we write Chaincode in Java.
Step 2 — Analysis of the Requirement
Before we start coding, let’s organize what we need to code.
As said in the “Features” section, in the Java Chaincode, we will implement:
- User wallet creation
- User wallet query
- Wallet token transfer (i.e. to send money to from one wallet another wallet)
From this simple requirement above, we need to have the following classes:
- Wallet Class
- The Chaincode class — holding the Chaincode
And our Chaincode should provide these functions:
- Create a wallet
- Transfer tokens from one wallet to another
- Get (query) a wallet
- Init function — called when the Chaincode is instantiated
- Invoke function — called when a user wants to call the function (1), (2), or (3)
The Init function must be implemented in Chaincode and will be called automatically whenever we instantiate or upgrade the Chaincode. Usually, it is used to initialize some data in the Blockchain.
The Invoke function is used to receive all user function calls, then based on the call, it invokes (calls) the corresponding function — (1), (2), or (3). It is like a router — routing incoming requests to a different path.
Step 3 — The Wallet Class
Now, we create and code the Wallet class.
Create a new package under “org.hyperledger.fabric.chaincode”:
Call the package Models, then OK:
Now you should have the following:
Under the Models package, create a new Java class and name it Wallet. (I don’t show detailed steps this time, let’s try yourself)
Now we code for the Wallet class, let’s type:
The wallet has a wallet id to identify a certain wallet and a token amount to specify how many tokens the wallet owns.
Note that in the production case, the Wallet class should be (far) more complicated. For example, you probably use “BigDecimal” data type instead of “Double” for tokenAmount. Also, in our case, we support only one token type (i.e. only one currency) in the whole transaction system.
Step 4— The Chaincode Class
Note that in the production case, the codes should be different from mine. These codes are in demo or tutorial purpose mainly.
Step 5 — Running the Chaincode in BYFN
5.1 Install Prerequisites
We use Hyperledger Fabric v1.4 in this tutorial.
First of all, you could install prerequisites by following the official instructions:
5.2 Chaincode Preparation
Then, let’s switch to this directory (*supposed that you finish the prerequisite part, you should have all needed files and directories):
cd fabric-samples/chaincode/chaincode_example02/
mv java java_01
mkdir java
Now, copy the following highlighted files in your project directory to ”fabric-samples/chaincode/chaincode_example02/java/”:
5.3 Bring Up the Network
cd ../../first-network
./byfn.sh up -l java
After running this script, you might need to wait for a moment…
*** If you see the following error (instead of other errors), that’s OKAY, just proceed to the next steps ***
Also, remember to start Docker, before you run the command above.
*In case you make something wrongly, you can run the following commands to turn down the network, and bring up again:
./byfn.sh down
./byfn.sh up -l java
Now, we test whether our Chaincode works.
5.4 Access the Cli
There is a cli Docker container created automatically, it is a command line interface to control the nodes.
Let’s get access to the cli:
docker exec -it cli bash
Then, set up environment variables used by some programs:
export CHANNEL_NAME=mychannel
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
You could just copy all of them, then paste to your terminal and press “enter”.
These environment variables are used to let some Hyperledger Fabric programs know that we need to use peer0.org1.example.com:7051 to invoke Chaincode functions.
Now, we create two wallets with wallet IDs — “tom” and “sam”:
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["createWallet","tom","100"]}'peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["createWallet","sam","100"]}'
Update: Note that we have two
--peerAddresses
in order to send to 2 Peers to endorse the transaction, since the default endorsement policy in Build Your First Network (BYFN) is an AND policy, which requires both organizations, Org1 and Org2, to sign each transaction. For more explainations, please refer to here.
You should see something like this in the terminal after running each command above:
Update: As Tommaso Martin suggested,
--peerAddresses
can support array for its argument as well. Instead of the--peerAddresses
usage in the Invoke commands above (using--peerAddresses
two times for two Peers), you may consider (using--peerAddresses
one time and passing it an array of two Peers):
[...] --peerAddresses peer0.org1.example.com:7051 peer0.org2.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt [...]
Now, we get two wallets created above to verify whether they are existing in the Blockchain:
peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["getWallet","tom"]}'peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["getWallet","sam"]}'
You should see something like this in the terminal after running each command above:
Above, we can see that the two wallets created previously can be queried. And both of them have 100 tokens.
Next, we make a transfer transaction — let’s transfer 10 tokens from “tom” wallet to “sam” wallet:
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["transfer","tom","sam","10"]}'
Finally, let’s verify two wallets again:
peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["getWallet","tom"]}'peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["getWallet","sam"]}'
Can you have the following result?
Note that now Tom’s wallet remains 90 tokens and Sam’s wallet has 110 tokens, the transaction is done and written into the Blockchain ledger.
Step 6— Cleaning Up
exit
./byfn.sh down
Then, delete directory “fabric-samples/chaincode/chaincode_example02/java” and rename directory ”java_01” back to ”java”.
Thanks! If you like my stories, please follow me for new updates!