Skip to content

Aion Java API introduction and tutorial

Jay Tseng edited this page Mar 13, 2018 · 3 revisions

Introduction

The Aion Java API is a Java implementation of the Aion blockchain kernel application programming interface. It was built using Java 8. It provides web3 client API compatibility and has additional dedicated APIs features designed specifically for the Aion blockchain technology. It provides a practical interface for deploying a Java application and integrating with the blockchain as a service (BaaS).

The Aion Java API uses the ZMQ Java binding as network transfer layer protocol for improved stability, reliability and security. Also aiming at high performance for enterprise application use, we chose the solution here.

In the message packaging layer, we use the Google Protocol Buffer for serializing our API message input/output. It’s small, fast and simple and reduces the hardware resources requirements. For more detail about the Google Protocol Buffer, please see their webpage.

Supported Environments

  • Ubuntu 16.04 64bit
  • JDK/JRE 9 (if you want to develop under the JDK/JRE8, please use the branch java8 )

API Overview

Error Handling Model

To communicate to the API user that an error occurred, instead of an exception-throw model, the Aion Java API uses an error-object-return model. This means that for all non-instantiation function calls the API returns an Object of type org.aion.api.type.ApiMsg. Two things need to be kept in mind when interacting with this error-handling model:

  1. Every API call can be evaluated synchronously for success or failure, like so:

    ApiMsg m = AionApiCall(args);
    if (m.error()) {
        // handle error here
    }
  2. To get the output of the API call, the user must then use the ApiMsg.getObject method, like so:

    int result = m.getObject();

The user must look up the type for the return value for all function calls and appropriately cast the function output; the IDE cannot statically determine this because the ApiMsg.getObject method returns a base type that all types derive from.

The advantage of this type of error handling model is to fine-tune this process for developers using the API. The user is then able to use the API to check for permissible errors right after the function call instead of doing so in the catch block, making it easier to write correct programs.

Connection Model

In order to communicate with the Aion blockchain kernel through the Aion Java API, a connection to the Aion Java API must be opened, which in-turn manages the connection with the Aion blockchain kernel.

The static public IP of the machine running the kernel instance, and the port being used by the Aion Java API must be known (see the section on Configuration Settings for more details on setting this up). Once this information is known, the address string is of the form: tcp://{API IP Address}:{API Port Number}

The code to connect to the API and then close the connections looks functionally similar to the following:

 IAionAPI api = new IAionAPI.inst();
 ApiMsg apiMsg = new ApiMsg();
 apiMsg.set (api.connect("tcp://127.0.0.1:8547"));
 if (apiMsg.error()) {
     System.out.println("Aion api connection could not be established.");
     closeApi();
 }
 ...    
 closeApi():
 {
    if (api != null)
        api.destroyApi();
 }

Thread Safety

With regards to thread safety, the following rules must be adhered to:

  1. A connection instance CANNOT be shared by multiple threads (i.e. holding one open connection reference of IAionAPI and sharing it between threads to make function calls to the API)

  2. Every application thread that needs a connection to the Aion Java API shall open and safely close the connection before returning from the thread to avoid potential socket, thread, and memory leaks in the application.

Non-Blocking Mode

Alternatively, the user can choose to use the non-blocking version of the IContract function call by using the nonBlock() function before performing the execute() function to send the transaction.

The following is an example of using the non-block feature with the Contract object to perform a transaction send:

apiMsg.set(contract.newFunction("balanceOf")
      .setParam(Address.copyFrom(acc))
      .build()
      .nonBlock()
      .execute());

The non-blocking functions will immediately return after. The user would be responsible for polling the status byte of the Contract object to make sure that the transaction has either:

  1. failed to be validated; or
  2. got successfully included in a block.

The status byte should be interpreted using the following table of status codes:

public static final int r_tx_Init_VALUE = 100;

public static final int r_tx_Recved_VALUE = 101;

public static final int r_tx_Dropped_VALUE = 102;

public static final int r_tx_NewPending_VALUE = 103;

public static final int r_tx_Pending_VALUE = 104;

public static final int r_tx_Included_VALUE = 105;

Waiting for N Block Confirmations

The default rule throughout the Aion Java API is that a transaction is considered “complete” or “included” if it has been included in at least one block, as observed by the API-connected node.

Some users might want to wait for N block confirmations as perceived by the API-connected node before considering a transaction “complete” (based on use case, etc.).

The user can use the strategy of using a non-blocking transaction-send and then use a thread to poll for:

  1. First, ensure that the transaction has been included in at least one block.
  2. Get the transaction hash and the corresponding block number tx_block for the transaction.
  3. Check the current block number as observed by the API-connected node.
  4. Check that the current block is at least N blocks ahead of the block with the transaction in question.
  5. Verify that the tx_block is an ancestor of the current block as observed by the API-connected node to guard against orphaned-chain issues.

Using Aion Java API with Existing Java Project

Using the Aion Java API in a Java project is as simple as importing the aionapi.jar into the Java project in your preferred IDE. The jar is available at:

$AION_HOME/api/aionapi.jar

In addition, if your IDE supports this feature, the Javadocs corresponding to the jar can be linked to the jar for in-editor documentation; the Javadocs can be found at:

$AION_HOME/docs/java-api

Deploying Smart Contract to the Aion Network

Aion Java API

Assuming that the smart contract is stored in a .sol on the filesystem, the user must take the following steps:

  1. Connect to the Aion Java API.
  2. Unlock the account being used for sending the contract creation transaction.
  3. Load the solidity contract from the filesystem into a variable.
  4. Compile the contract.
  5. Send the contract creation transaction to the Aion network.
  6. Wait for the contract creation transaction to be “mined” or included in at-least 1 block.

The following is a Java program that uses the Aion Java API to execute these steps:

IAionAPI api = IAionAPI.inst();
ApiMsg apiMsg = new ApiMsg();
apiMsg.set (api.connect("tcp://127.0.0.1:8547"));
if (apiMsg.error()) {
    System.out.println("Aion api connection could not be established.");
    closeApi();
    // safely close API connection
}

// set up the arguments for unlock account operation
Address account = Address.wrap("cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe");
String password = "password";
int unlockTimeout = 300; 
	
// unlock an account
apiMsg.set(api.getWallet().unlockAccount(account, password, unlockTimeout));
if (apiMsg.error() || !(boolean)apiMsg.getObject()) {
	System.out.println("Unlock account failed! Please check your password  " + apiMsg.getErrString());
	// cleanup() on return
} 
	 
// get the Smart Contract code from file
String sc = null; 
try {
	sc = new String(Files.readAllBytes(Paths.get("/path/to/Ticker.sol")));
} catch (IOException e) {
	e.printStackTrace();
	closeApi();
	// safely close API connection
}
// contract creation:
// this function blocks until the contract creation transaction
// has been included in at least one block on the Blockchain
long energyLimit = 1_000_000L;
long energyPrice = 1L;
apiMsg.set(api.getContractController().createFromSource(sc, account, energyLimit, energyPrice));
if (apiMsg.error()) {
	System.out.println("Deploy contract failed with error: " + apiMsg.getErrString());
	closeApi();
	// safely close API connection
} 

IContract contract = api.getContractController().getContract();

// make sure to save the ABI definition for the contract since an API
// limitation is that in order to create a Contract object, one needs the
// contract address and the ABI definition
String contractAddress = contract.getContractAddress();
String contractAbi = contract.getAbiDefToString();
	
closeApi(): {
		if (api != null)
			api.destroyApi();
}

Interacting with the Smart Contract

There are three types of interactions one can have with Smart Contracts using the Aion Java API:

  1. Contract Creation Transactions This is the transaction which creates the smart contract on the Aion network; this translates to the fact that for any fully-validating node on the network which has a block on its consensus chain that includes this contract creation transaction, then that block has the ability to respond to any contract calls or transactions. The use of this type of transaction with the Aion Java API has been covered in the previous section.

  2. Contract Calls These are reads from the Contract storage of the API-connected node; since these are simple database reads and do not involve any state changes for contract storage and therefore does not involve the consensus process; this information can be retrieved directly from the contract storage database for the smart contract.

  3. Contract Transactions These are state changes to the contract storage and require a consensus process to guarantee that the contract state change is agreed to and is propagated across a majority of nodes on the network.

All interaction with smart contracts happens using the Contract object using the IContract interface. The IContract interface has no public constructors.

Please see the Aion java API documentation for all available overloads.

The Contract object represents a smart contract deployed on-chain. There are two methods of obtaining and using a Contract instance:

  1. At the contract-deploy time, the contract instance returned can be used for sending transactions to that contract. After the IAionAPI connection has been closed or this reference is lost, a new Contract instance must be created pointing to the same Smart Contract

  2. In order to create a new Contract instance, the address of the contract and the ABI definition is required (this is both a limitation and feature, since this allows the Aion Java API to do compile-time checks, allowing programmers to write correct programs without requiring them to test transaction calls against a test Blockchain, just to validate if the transaction, as described in software does not have any errors in its declaration, as is the case in Web3)

The Contract object uses the currying pattern to prepare the Contract for a transaction, by changing its state and returning the object. Calling the .execute() function will send the contract transaction or contract call to the Kernel.

The following example shows how to interact with the Ticker contract:

  1. Assuming that an active connection to the Aion Java API exists, and the known account whose address is stored in the variable String account has been unlocked.
  2. A contract object is created from the known contract address and ABI for the Ticker contract, which we acquired in the last code snippet.
  3. A contract transaction is sent, calling the tick() function.
  4. The contract state change is verified by using the contract call showMeCount()
// open a connection to the Aion API called api
// ...

// unlock account to be used for transactions
// ...

ApiMsg apiMsg = new ApiMsg();
String abiDef = "known ABI definition";
Address contractAddress = Address.wrap("known contract address");

// create a contract object from a known smart contract address
apiMsg.set(api.getContractController().getContractAt(account, contractAddress, abiDef));
    
if (apiMsg.error()) {
	System.out.println("Contract instantiation failed!" + apiMsg.getErrString());
	closeApi();
	// safely close API connection
}

IContract contract = api.getContractController().getContract();
// perform a contract transaction by calling the tick() function:
// by default, the execute() function blocks until this
// transaction has been included in at least one block on the Blockchain
apiMsg.set(contract.newFunction("tick")
      .build()
      .execute());

if (apiMsg.error()) {
	System.out.println("Function execution error! " + apiMsg.getErrString());
	closeApi();
	// safely close API connection
}
	
apiMsg.set(contract.newFunction("showMeCount")
.build()
.execute());
	
if (apiMsg.error()) {
	System.out.println("Function showMeCount error! " + apiMsg.getErrString());
	closeApi();
	// safely close API connection
}

int tickCount = (int) apiMsg.getObject();

// now you can verify that the value of the count variable
// in the Ticker contract has incremented by printing out this value to std.out
System.out.println("Ticker contract count value: " + tickCount);

// more operations
// ...