This topic describes how to use the Java language to develop smart contracts.
Chaincode structure
Chaincode in the Java language consists mainly of the following methods.
/**
* Defines methods that all chaincodes must implement.
*/
public interface Chaincode {
/**
*Called during an instantiate transaction after the container has been
*established, allowing the chaincode to initialize its internal data
*/
public Response init(ChaincodeStub stub);
/**
*Called for every Invoke transaction. The chaincode may change its state
*variables.
*/
public Response invoke(ChaincodeStub stub);
}
init: The chaincode calls this interface during initialization and upgrade to initialize related data.
invoke: This method is mainly used to implement the internal business logic of chaincode. You can implement related businesses in this method.
During the implementation of the above method, a user may call a ChaincodeStubImpl API interface to interact with the chain.
Chaincode example
Hyperledger Fabric provides many examples of official chaincodes. For more information, see Examples of official chaincodes. The example02 sample provided by Hyperledger Fabric is used as an example to describe the chaincode development specifications.
Simple example
First, let's look at a sample code for an empty chaincode structure
import java.util.List;
import com.google.protobuf.ByteString;
import io.netty.handler.ssl.OpenSsl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperledger.fabric.shim.ChaincodeBase;
import org.hyperledger.fabric.shim.ChaincodeStub;
import static java.nio.charset.StandardCharsets.UTF_8;
/*
* A simple chaincode for managing assets
*/
public class SimpleAssetDemo extends ChaincodeBase {
/*
* Call Init to initialize data during the chaincode instantiation period.
*/
@Override
public Response init(ChaincodeStub stub) {
}
/*
* Invoke will be called during each comparison transaction. This method should contain set and get to create and obtain the corresponding key value.
*/
@Override
public Response invoke(ChaincodeStub stub) {
}
public static void main(String[] args) {
new SimpleAssetDemo().start(args);
}
}
init example
The Init function is called when the chaincode is instantiated and upgraded. In the process of implementing the Init function, you can use the Java version of the contract API list to perform operations on parameters and distributed ledgers.
@Override
public Response init(ChaincodeStub stub) {
try {
_logger.info("Init java simple chaincode");
// Call the getFunction method to obtain the currently called function.
String func = stub.getFunction();
if (!func.equals("init")) {
return newErrorResponse("function other than init is not supported");
}
// Call API getParameters to obtain the parameters of the call.
List<String> args = stub.getParameters();
if (args.size() != 4) {
return newErrorResponse("Incorrect number of arguments. Expecting 4");
}
// Initialize related data
String account1Key = args.get(0);
int account1Value = Integer.parseInt(args.get(1));
String account2Key = args.get(2);
int account2Value = Integer.parseInt(args.get(3));
_logger.info(String.format("account %s, value = %s; account %s, value %s", account1Key, account1Value, account2Key, account2Value));
// Call the putStringState method to write data to the ledger.
stub.putStringState(account1Key, args.get(1));
stub.putStringState(account2Key, args.get(3));
return newSuccessResponse();
} catch (Throwable e) {
return newErrorResponse(e);
}
}
This example requires the user to enter the a parameter KEY1_NAME
, VALUE1
, KEY2_NAME
, and VALUE2
, initialize two key-value pairs, and call putStringState to write data to the distributed ledger.
Example of invoke
The invoke function is the implementation of user-specific business logic. You can call different business processing functions, such as the invoke,delete, and query functions, based on different business processing logic.
// invoke subdivides the function called by the user into several sub-functions, including invoke,delete, and query.
@Override
public Response invoke(ChaincodeStub stub) {
try {
_logger.info("Invoke java simple chaincode");
String func = stub.getFunction();
List<String> params = stub.getParameters();
if (func.equals("invoke")) {
return invoke(stub, params);
}
if (func.equals("delete")) {
return delete(stub, params);
}
if (func.equals("query")) {
return query(stub, params);
}
return newErrorResponse("Invalid invoke function name. Expecting one of: [\"invoke\", \"delete\", \"query\"]");
} catch (Throwable e) {
return newErrorResponse(e);
}
}
invoke function
The business logic invoke function transfers assets in the business logic to accountTo.
// invoke implements value transfer between two keys. The input parameters are KEY1_NAME, KEY2_NAME, and VALUE.
private Response invoke(ChaincodeStub stub, List<String> args) {
if (args.size() != 3) {
return newErrorResponse("Incorrect number of arguments. Expecting 3");
}
String accountFromKey = args.get(0);
String accountToKey = args.get(1);
// Obtain the current asset information of accountFromKey.
String accountFromValueStr = stub.getStringState(accountFromKey);
if (accountFromValueStr == null) {
return newErrorResponse(String.format("Entity %s not found", accountFromKey));
}
int accountFromValue = Integer.parseInt(accountFromValueStr);
// Obtain the current asset information of accountToKey.
String accountToValueStr = stub.getStringState(accountToKey);
if (accountToValueStr == null) {
return newErrorResponse(String.format("Entity %s not found", accountToKey));
}
int accountToValue = Integer.parseInt(accountToValueStr);
int amount = Integer.parseInt(args.get(2));
if (amount > accountFromValue) {
return newErrorResponse(String.format("not enough money in account %s", accountFromKey));
}
// Business logic: implements the transfer of assets.
accountFromValue -= amount;
accountToValue += amount;
_logger.info(String.format("new value of A: %s", accountFromValue));
_logger.info(String.format("new value of B: %s", accountToValue));
// Update the updated asset to the account book.
stub.putStringState(accountFromKey, Integer.toString(accountFromValue));
stub.putStringState(accountToKey, Integer.toString(accountToValue));
_logger.info("Transfer complete");
return newSuccessResponse("invoke finished successfully", ByteString.copyFrom(accountFromKey + ": " + accountFromValue + " " + accountToKey + ": " + accountToValue, UTF_8).toByteArray());
}
Use the getStringState API to obtain the total asset value corresponding to KEY_NAME.
Call the business logic to transfer the number of asset units.
Call the putStringState API to write the updated asset status to the account book.
The above implementation is a simple logic similar to the transfer, but does not verify the validity of the parameters such as the transfer amount is greater than zero, the balance is not negative, etc.
delete function
The business logic delete function implements the account deletion function in the business logic.
// remove the entity from the state
private Response delete(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
return newErrorResponse("Incorrect number of arguments. Expecting 1");
}
String key = args.get(0);
// Delete the key from the state in the ledger
stub.delState(key);
return newSuccessResponse();
}
query function
The business logic query function implements the account query function in the business logic. You can call the GetState API to query the assets of the corresponding account.
// query callback representing the query of a chaincode
private Response query(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
return newErrorResponse("Incorrect number of arguments. Expecting name of the person to query");
}
String key = args.get(0);
//byte[] stateBytes
String val = stub.getStringState(key);
if (val == null) {
return newErrorResponse(String.format("Error: state for %s is null", key));
}
_logger.info(String.format("Query Response:\nName: %s, Amount: %s\n", key, val));
return newSuccessResponse(val, ByteString.copyFrom(val, UTF_8).toByteArray());
}