Algorand serverless application on AWS Lambda
In this tutorial, you will learn how to develop a simple serverless Java application and deploy and test it on AWS Lambda.
The application executes two straightforward tasks on the Algorand Blockchain:
- Retrieve the total amount of Algos of an Account
- Send Algos between accounts
Nevertheless, it can be used as a template to develop more complex solutions. At the end of the tutorial, you will be able to build a complete Java application, deploy it on AWS Lambda, and test it.
Requirements
To follow this tutorial, you will need JDK 11+, Maven 3.6+, and a Java IDE, e.g., VS Code studio or IntelliJ (used by me).
To deploy and test the serverless application, you need an AWS account. You can easily create one on AWS Lambda. You will be asked to provide credit/debit card information, phone number, and address.
This service is eligible for the AWS Lambda free tier, including one million free requests and 400,000 GB-seconds of computing time per month. So you won’t be charged for your tests (unless you make millions of calls…).
Even though the solution is easy to follow, it is worthy to have a basic experience with Java. Besides, you will need to import several sources to pom.xml
, where one of them is Algorand Java SDK
Background
A serverless application is a compute service that allows you to run code without provisioning or managing servers. You need zero administration so you can focus on the software instead of infrastructure management and let AWS maintain and scale the server for you.
Using AWS Lambda, you pay only for the compute time that you consume and you won’t be charged while your code isn’t running.
Steps
1. Dependencies and Initial setup
Note: You can find the complete project on GitHub to the URL: GitHub - david-ciamberlano/algo-serverless: Base project for Algorand on AWS lambda.
You can start with a standard Maven project. The build instructions might differ based on your operating system and IDE. On Linux, if you have successfully installed Maven and Java, you can easily create a sample project using a similar command: mvn archetype:generate -DgroupId=com.mytest.app -DartifactId=my-app -DarchetypeVersion=1.4 -DinteractiveMode=false
In the pom.xml
under <dependecies>
, we first need to import the AWS dependencies:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.6.0</version>
</dependency>
Since this application will interact with the Algorand Blockchain, you also have to import the Algorand Java SDK.
<dependency>
<groupId>com.algorand</groupId>
<artifactId>algosdk</artifactId>
<version>1.8.0</version>
</dependency>
Finally, to properly package the software, you need to set up the Shade plugin for Maven under <plugins>
.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
The pom.xml
should add the similar like this: pom.xml
Note: the versions of each dependency might change at the time when you try to deploy your service.
Before proceeding, make sure your project builds using the mvn package
.
2. The structure of the project
Lambda functions are event-driven. The handler is the method that processes events. When our serverless service is invoked, Lambda executes the handler method. When it finishes its job, it becomes available to handle another event.
In this tutorial, we will build two different handlers:
GetAmountHandler(...)
SendAlgoHandler(...)
The first one retrieves the total amount of Algo held by an Algorand account.
The second one, slightly more complex, shows how to send Algos from one account to another.
After deploying the application in AWS, you will be able to choose which function to invoke.
To interact with the Algorand blockchain, we will use a service Class
called Algoservice. This Class
contains a few methods that encapsulate more complex operations on the blockchain.
initAccount()
getAccountAmount(String address)
sendAlgo(String receiverAddress, long amount)
waitForConfirmation(String txId, int timeout)
We will examine each of these methods more in depth later in the article.
In this project, we will use two additional classes that represent the “models” for the request and the response. They are helpful to manage the JSON objects that pass and return parameters to our function. The two classes are:
RequestModel
ResponseModel
3. GetAmountHandler
As mentioned, a handler is an entry point for our function. It is invoked when the Lambda function is called. Let’s take a look at the code snippet:
public class GetAmountHandler implements RequestHandler<String, Long> {
AlgoService algoService = new AlgoService(
System.getenv("CORE_API_ADDR"),
Integer.parseInt(System.getenv("CORE_API_PORT")),
System.getenv("CORE_API_TOKEN"),
System.getenv("INDEXER_API_ADDR"),
Integer.parseInt(System.getenv("INDEXER_API_PORT")));
@Override
public Long handleRequest(final String address, final Context context) {
LambdaLogger logger = context.getLogger();
Long amount = algoService.getAccountAmount(address).orElse(-1L);
logger.log("Amount: " + amount);
return amount;
}
}
Note: You will have to update the environment variables with valid information for your project.
As you can see, the code is very simple.
The aws-lambda-java-core java library defines two interfaces for handler methods:
RequestHandler
RequestStreamHandler
The first accepts Objects
as parameters, and the second accepts Streams
.
We will use the first one in our examples - RequestHandler
.
It is a generic type that accepts two parameters: the input type and the output type (both must be objects). The Java runtime deserializes the event into an object of the type specified for the input and serializes the output into an object of the output type.
In our case, the GetAmountHandler Class
implements the RequestHandler Interface
. The two types <String, Long> are the Request
and the Response
types, respectively: it accepts a String as input and will return a Long.
The RequestHandler Interface
requires you to implement the HandleRequest method, which incidentally accepts a String and returns a Long.
The extra Context parameter contains helpful information on the current instance of the function.
When our Lambda is invoked, it will be passed the Algorand address and checked (a String). At the end of the computation, the Lambda itself will send back the total amount of Algo owned by the address (a Long).
Since we don’t have control over a Lambda execution, producing logs is important.
For this purpose, we can use the LambdaLogger object provided by the Context object as follows:
LambdaLogger logger = context.getLogger();
The handler code is very simple. We instantiate an AlgoService object and call its getAlgoAmount()
method.
This method retrieves the amount for Algos owned by the address we passed as input. AlgoService will be analyzed more in-depth below.
To initialize the AlgoService object, we used information contained in environment variables. In the Lambda function, we can retrieve the environment variables using the System.getEnv()
call. In this way, we can keep sensitive information out of the code and achieve more reusability.
The getAlgoAmount()
method returns an Optional
, so to get the response, we need to check if the optional is empty (in this case, we will return -1L, which represents an error) or it contains a value that will be returned.
4. SendAlgoHandler
To better understand the serverless potentiality, we will analyze a second slightly more complex example.
We are going to define a second Handler called SendAlgoHandler:
public class SendAlgoHandler implements RequestHandler<RequestModel, ResponseModel> {
AlgoService algoService;
public SendAlgoHandler(AlgoService algoService) {
this.algoService = new AlgoService(
System.getenv("CORE_API_ADDR"),
Integer.parseInt(System.getenv("CORE_API_PORT")),
System.getenv("CORE_API_TOKEN"),
System.getenv("INDEXER_API_ADDR"),
Integer.parseInt(System.getenv("INDEXER_API_PORT")),
System.getenv("ACC_PASSPHRASE"));
}
[...]
}
This time, we implemented the RequestHandler<RequestModel, ResponseModel>
interface.
RequestModel
and ResponseModel
are the Request and the Response types, respectively. They are two very simple POJO (Plain Old Java Classes):
public class RequestModel {
String address;
Long amount;
[...] //Getters and Setters
}
and
public class ResponseModel {
private String txId;
private String msg;
[...] //Getters and Setters
}
As mentioned, the Java runtime automatically deserializes the event into the RequestModel
object and serializes the response into the ResponseModel
object. We will see this in more detail in the last part of this tutorial.
Also, notice that we invoked a different constructor with one more parameter: the account passphrase (ACC_PASSPHRASE). This is because we want to send some Algos from our wallet to another address and we have to sign the transaction (which is only possible if we have full access to our account).
Let’s analyze the code of the HandleRequest
method of the SendAlgoHandler
class:
public ResponseModel handleRequest(final RequestModel request, final Context context) {
LambdaLogger logger = context.getLogger();
try {
String txId = algoService.sendAlgo(request.getAddress(), request.getAmount());
logger.log("Transaction executed");
return new ResponseModel(txId,"Transaction executed");
}
catch (Exception e) {
logger.log("Transaction Failed");
return new ResponseModel("", "Transaction Failed");
}
}
Again, the code is very simple, because the Algorand logic is hidden in AlgoService.
We simply call its sendAlgo()
method passing the destination address and the amount of microAlgos to send.
This method returns the id of the Algorand transaction that contains our transfer. We can then create a ResponseModel
object, using this transaction id and a message and use it in the return statement.
If an error occurs, a ResponseModel
object with an empty TxId and a failure message will be returned.
5. The AlgoService Class
The AlgoService class allows you to interact with the Algorand blockchain.
For the complete code, please refer to the Github repository: algo-serverless/AlgoService
In this tutorial, we will only review the most important part of the class.
First of all, it has two different constructors. The second one has an extra parameter, the account passphrase, and should be used if you need to sign a transaction (like the SendAlgoHandler
example in #4).
The class has three methods:
getAccountAmount
sendAlgo
waitForConfirmation
GetAlgoAmount
The most important part is the following:
Address destAddress = new Address(address);
accountResponse = algodClient.AccountInformation(destAddress).execute();
This method receives an address string as input and calls the Algorand Java SDK function AccountInformation to retrieve the total amount of Algo stored in the wallet.
SendAlgo
This function receives the destination address and the amount of microAlgo to transfer.
The important part of the code is:
com.algorand.algosdk.transaction.Transaction tx =
com.algorand.algosdk.transaction.Transaction.PaymentTransactionBuilder()
.sender(algoAddress).note(note.getBytes()).amount(amount)
.receiver(address).suggestedParams(params).build();
SignedTransaction signedTx = algoAccount.signTransaction(tx);
byte[] encodedSignedTx = Encoder.encodeToMsgPack(signedTx);
Response<PostTransactionsResponse> txResponse = algodClient.RawTransaction().rawtxn(encodedSignedTx).execute();
As you can see, we prepare the transaction, then we sign it with our private key (with the signTransaction call), and finally, send it to the blockchain (the RawTransaction part).
waitForConfirmation
After submitting a transaction, we must wait until it is accepted and effectively registered in the blockchain. The waitForConfirmation transaction serves this purpose.
We won’t describe it since it is a well-known functionality (you can find it in the official documentation).
6. Deployment on AWS
First of all, we have to package our function a proper way. We can do that using the following Maven command:
mvn clean package
This command produces a jar packet in the “target” folder named as “algoserverless-1.0.jar”. Thanks to the shade plugin in the pom.xml
, this will be in the correct format to be deployed in AWS.
Note: There are many different ways to deploy a java application as a Lambda function. We will use one of the simplest: manual deployment. Consider, however, that this could not be the best choice in many cases… but it’s good enough for this tutorial.
You are now ready to create a Lambda function. Log into the AWS console and select the Lambda service.
Select “Author from Scratch” then, in the “Basic information” write a name and select “Java 11 (Corretto)” for the runtime. Leave the default choice for the other options.
Click “Create” at the bottom of the page. After a while, your new Lambda will be ready, as shown in the image below.
We have to load our source code, now. Select “Upload from” and then “.zip or .jar files” in the source code section. This is not the best option, since our “jar” is larger than 10MB. We should use S3 storage, but we are trying to keep this tutorial as simplest as possible.
You can select our .jar package (algoserverless-1.0.jar) and save it. After a few seconds, we will be ready to test our Lambda.
Now we have to set which handler should be invoked. In the “Code” tab, scroll down to the “Runtime settings” panel and click on the edit button. Write the following text in the Handler text-box (as you can see, it’s formed by the package and class name):
algoserverless.GetAmountHandler::handleRequest
Before proceeding, we need to do one last step. Do you remember the Handler methods? They got a few parameters from environment variables.
To pass these variables to our Lambda, we can select the “Configuration” tab and then “Environment variables” on the left.
The variables to set are:
- ACC_ADDR: <Your account Address>
- ACC_PASSPHRASE: <25 words passphrase of your account>
- CORE_API_ADDR: https://testnet.algoexplorerapi.io/
- CORE_API_PORT: 443
- CORE_API_TOKEN: null
- INDEXER_API_ADDR: https://testnet.algoexplorerapi.io/idx2
- INDEXER_API_PORT: 443
NOTE: you must write the 25 words of the passphrase one after the other, separated by a space - ie:
ACC_PASSPHRASE: tourist wish evoke [...] surprise abstract
The passphrase is only stored on the server as an environment variable and never leaves it. It can only be accessed or modified by the administrator of the lambda account.
To use other services (or even your own node) you only need to change the parameters accordingly (for example: “CORE_API_ADDR: https://node.algoexplorerapi.io/"). You will probably need to set the API token… and of course a valid account address and passphrase.
7. Test the functions
We are now ready to test the Lambda. Go to the test tab and select “new event,” then the “hello-world” template.
In the text-area, you must set the input passed to our Handler. For the GetAmountHandler
we need a string (the address of the wallet you want to get the information. Don’t forget the double quotes).
Finally click on the Test button.
After a few moments (it’s slower the first time you call the function) you will receive the following message: “Execution result: succeeded”. If you click on “details” you can see the result returned by the function (a number representing the amount of microAlgos held in the wallet).
To test the SendAlgo function, we need to make just a few changes.
On the “Code” tab, scroll down to the “Runtime settings” section and click on Edit.
Replace the handler with the following and click “Save”.
algoserverless.SendAlgoHandler::handleRequest
We also need to change the input in the “Test” tab. We have to provide a JSON object (which will be automatically deserialized in the RequestModel java object).
This is an example of input:
{
"address": "HYX5...MKGE",
"amount": 100000
}
You can click again on the “Test” button. This time the SendAlgoHandler will be invoked.
Again, after a while, we get the “Execution result: succeeded” message and in the details, we can see the JSON object we received in response.
{
"txId": "GNMS...RFEQ",
"msg": "Transaction executed"
}
Et voilà! We have learned how to implement a serverless application.
This project is just a template, but you can use it to develop a more complex application on the Algorand Blockchain. Have fun with it!