A tech company like lastminute.com, whose vision is to design the future of travel and tourism using digital technologies, always has an eye on every type of innovation in its tools and methodologies. For this reason, tech people are always encouraged to explore new technological horizons, even those that may seem more distant at first. In recent years, the blockchain is becoming an increasingly disruptive technology whose uses are increasingly concrete. Industry insiders predict that the impact of this technology on the world will be similar to what the internet had in the 90s. It includes from the most disparate fields of application, from finance, to intellectual property management or video games, but often with a single common denominator: decentralization. The inherent characteristics of the blockchain will allow it to overcome intermediaries by allowing individuals to have full control over their finances, intellectual and digital properties.
Although in lastminute.com there are still no initiatives that include blockchain, the following post aims to be an example of how the tools for interaction and development of blockchain-based applications are now ready to be integrated into complex business contexts and architectures such as ours. The example application source code discussed below can be found here.
Why Algorand?
Algorand is one of the most promising blockchains. Although it still not as famous as of the most established blockchains, it has already hinted at how great its potential can be. Its founder is Silvio Micali, Italian and Turing award winner (oh it seems that we are not only champions in sports ), who with his team has solved the age-old problem that all blockchains have to deal with: the blockchain trilemma. This problem states that many blockchain have to sacrifice at least one of the key properties of security, scalability, and decentralization. Micali and his Algorand team solved the blockchain trilemma with the Pure Proof of Stake (PPoS). In addition, the Algorand ecosystem provides mature and well-documented tools for approaching this world, which you can find here.
Block’n’Poll
Block’n’Poll is a blockchain polling web application that allows users to easily create permission-less polls and vote on Algorand blockchain.
Why voting on blockchain?
Although voting processes require particular attention to privacy, especially in the government sphere, simple polls are instead a good use case for blockchain technology, which ensures a public, verifiable, tamper-proof and uncensored global voting process. Furthermore, a blockchain polling system enables use cases in which response incentive mechanisms can be built. Even if the web app hosting domain is shut down, anyone still has the possibility to express his/her vote just using an Algorand node.
Algorand Java Web App
This solution aims to show how easily is to integrate Algorand into a full-stack Java web application through Algorand Java SDK. Polls are implemented as Algorand Stateful Smart Contract: their TEAL logic validates both polls creation, opt-in and voting, avoiding double-voting for a same account. The Stateful ASC1 TEAL logic is derived from the Permissionless Voting Solution published on Algorand Developer Portal. Block’n’Poll expands the original TEAL source code in order to allow the creation of polls with more than two options.
Architecture
The web application is based on Java, integrating Spring Boot and Algorand Java SDK in order to interact with the blockchain. The back-end of the application has been developed following the test drive development approach and the Hexagonal Architecture principles. A PostgreSQL database is used just to speed up polls retrieving operations, while the blockchain is always the real source of truth. The front-end has been developed using ReactJS and communicates with the backend by calling REST API.
Blockchain into Hexagonal Architecture
Accordingly, with hexagonal architecture principles the interactions with the blockchain have been put in a dedicated repositories at the edge of the hexagon. We have three repositories:
AlgorandASCPollRepository
responsible for saving the smart contracts of the poll. Below is reported thesave()
method that orchestrates the other collaborators in order to save the smart contract on the blockchain.
@Override
public BlockchainPoll save(Poll poll) {
try {
Account account = accountCreatorService.createAccountFrom(poll.getMnemonicKey());
Transaction unsignedTx = unsignedASCTransactionService.createUnsignedTxFor(poll, account);
String transactionId = transactionWriterService.write(account, unsignedTx);
Long appId = algorandApplicationService.getApplicationId(transactionId);
return pollBlockchainAdapter.fromPollToBlockchainPoll(poll, appId);
} catch (IllegalArgumentException e) {
logger
.error("Something gone wrong creating account from mnemonic key creating poll {}.", poll,
e);
throw new InvalidMnemonicKeyException(e.getMessage());
}
}
AlgorandReadRepository
is responsible for retrieving information from blockchain using theIndexerClient
class. This repository has three methods that allow to the application to:- check if an account is subscribed to a poll
- check if an account has already voted for a poll
- find poll information (number of the accounts opted in, number of votes for each available options) from the blockchain
@Override
public Boolean isAccountSubscribedTo(OptinAppRequest optinAppRequest) {
Response<AccountResponse> response;
try {
response = indexerClient
.lookupAccountByID(optinAppRequest.getAccount().getAddress()).execute(headers, values);
} catch (Exception e) {
throw new AlgorandInteractionError(e.getMessage());
}
if (response.code() == 200) {
return response.body().account.appsLocalState.stream()
.anyMatch(app -> app.id == optinAppRequest.getAppId());
} else {
logger.error(
"An error occurs calling algorand blockchain in order to retrieve account subscripted. Response code {}. Message {}. AppId {}. Address {}",
response.code(), response.message(), optinAppRequest.getAppId(),
optinAppRequest.getAccount().getAddress().toString());
throw new AlgorandInteractionError(response.code(), response.message());
}
}
@Override
public Boolean hasAddressAlreadyVotedFor(long appId, Address address) {
Response<AccountResponse> response;
try {
response = indexerClient.lookupAccountByID(address)
.execute(headers, values);
} catch (Exception e) {
throw new AlgorandInteractionError(e.getMessage());
}
if (response.code() == 200) {
return response.body().account.appsLocalState.stream().filter(app -> app.id.equals(appId))
.anyMatch(app -> app.keyValue.stream()
.anyMatch(tealKeyValue -> tealKeyValue.key.equals(VOTED_REPRESENTATION)));
}
throw new AlgorandInteractionError(response.code(), response.message());
}
@Override
public ApplicationInfoFromBlockchain findApplicationInfoBy(BlockchainPoll poll) {
Map<String, BigInteger> optionsVotes = findOptionsVotes(poll);
// Introduced because of the limit imposed by purestake.
// In an ipotetical production environment this problem should not exist
try {
Thread.sleep(API_TIME_DELAY);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
List<Address> subscribedAddress = findAddressSubscribedToApplicationBy(poll.getAppId());
return new ApplicationInfoFromBlockchain(optionsVotes, subscribedAddress.size());
}
AlgorandWriteRepository
responsible to write on blockchain the vote and the opt-in transactions.
@Override
public void optin(OptinAppRequest optinAppRequest){
try{
Transaction unsignedTransaction = buildOptinTransactionService.buildTransaction(optinAppRequest);
transactionWriterService.write(optinAppRequest.getAccount(),unsignedTransaction);
}catch(Exception e){
logger.error("Something goes wrong sending transaction subscribing for app id {} from address {}",
optinAppRequest.getAppId(),optinAppRequest.getAccount().getAddress().toString(),e);
throw e;
}
}
@Override
public void vote(VoteAppRequest voteAppRequest){
try{
Transaction unsignedTransaction = buildVoteTransactionService.buildTransaction(voteAppRequest);
transactionWriterService.write(voteAppRequest.getAccount(),unsignedTransaction);
}catch(Exception e){
logger.error("Something goes wrong sending vote transaction for app id {} from address {}",
voteAppRequest.getAppId(),voteAppRequest.getAccount().getAddress().toString(),e);
throw e;
}
}
TDD
The application has been developed with a TDD approach that guided the evolution of the application itself.
The creation of several collaborators, each one responsible for creating a type of transaction, is a need that has
emerged thanks to a TDD approach. For example, the creation of several collaborators each responsible for creating a
type of transaction is something that emerged thanks to a TDD approach, separating them from the collaborator
responsible for signing and sending the transaction to the blockchain. In this way we have classes with a single
responsibility and more essential and specific tests.
Below is reported the method responsible for opt-in transaction creation and its respective test.
public Transaction buildTransaction(OptinAppRequest optinAppRequest) {
return Transaction.ApplicationOptInTransactionBuilder()
.suggestedParams(blockchainParameterService.getParameters())
.sender(optinAppRequest.getAccount().getAddress())
.applicationId(optinAppRequest.getAppId())
.build();
}
@Test
public void happyPath() {
Transaction expectedOptinTransaction = aTransaction();
context.checking(new Expectations(){{
oneOf(blockchainParameterService).getParameters();
will(returnValue(aTransactionParametersResponse()));
}});
Transaction transaction = buildOptinTransactionService
.buildTransaction(new OptinAppRequest(APP_ID, account));
assertThat(transaction, is(expectedOptinTransaction));
}
Key Examples
One interesting thing is the way in which the smart contract is read, compiled and sent to the blockchain like an
application. The teal file with the teal code is read from the resources by the class TealTextGenerator
. After reading
the text the options sent during the poll creation are inserted in the text. In this way Block’n’Poll is able to create
poll with more than two options.
public static final String OPTIONS_PLACEHOLDER = "OPTIONS_PLACEHOLDER\n";
private final String APPROVAL_PROGRAM_PATH = "/teal/vote.teal";
private final String firstOptionTemplate = "txna ApplicationArgs 1\n"
+ "byte \"OPTION\"\n"
+ "==\n";
private final String optionTextTemplate = "txna ApplicationArgs 1\n"
+ "byte \"OPTION\"\n"
+ "==\n"
+ "||\n";
public String generateTealTextWithParams(List<String> options) {
String firstOption = firstOptionTemplate.replace("OPTION", options.get(0));
String otherOptions = options.stream().skip(1)
.map(option -> optionTextTemplate.replace("OPTION", option))
.collect(joining());
return readFile(APPROVAL_PROGRAM_PATH)
.replace(OPTIONS_PLACEHOLDER, firstOption.concat(otherOptions));
}
After the insertion of the options in the smart contract text, the smart contract is compiled through the blockchain and a TEALProgram is returned, ready to be included in an ApplicationCall transaction.
public TEALProgram createApprovalProgramFrom(PollTealParams pollTealParams){
return compileProgram(tealTextGenerator.generateTealTextWithParams(pollTealParams.getOptions()));
}
private TEALProgram compileProgram(String tealProgramAsStream){
Response<CompileResponse> compileResponse;
try{
compileResponse=algodClient.TealCompile()
.source(tealProgramAsStream.getBytes(UTF_8)).execute(headers,values);
}catch(Exception e){
throw new CompileTealProgramException(e);
}
return new TEALProgram(compileResponse.body().result);
}
The compiled TEALProgram
together with Poll params and sender address are passed to
BuildApplicationCreateTransactionService
responsible for the creation of the unsigned application creation
transaction. Below is reported the buildTransaction()
method.
public Transaction buildTransaction(PollTealParams pollTealParams,
TEALProgram approvalProgram,TEALProgram clearStateProgram,String sender){
Transaction transaction;
try{
transaction=Transaction.ApplicationCreateTransactionBuilder()
.sender(sender)
.note(Bytes.concat(APPLICATION_TAG.getBytes(StandardCharsets.UTF_8),pollTealParams.getQuestion()))
.args(arguments(pollTealParams))
.suggestedParams(blockchainParameterService.getParameters())
.approvalProgram(approvalProgram)
.clearStateProgram(clearStateProgram)
.globalStateSchema(new StateSchema(
STATIC_GLOBAL_VARIABLES_NUMBER + pollTealParams.getOptions().size(),1))
.localStateSchema(new StateSchema(0,1))
.build();
}catch(IllegalArgumentException e){
logger.error("Something goes wrong with Sender Address transaction",e);
throw new InvalidSenderAddressException(e.getMessage());
}catch(Exception e){
logger.error("Something goes wrong getting blockchain parameters transaction",e);
throw new BlockChainParameterException(e.getMessage());
}
return transaction;
}
User Experience
Block’n’Poll web-app implements the following use-cases:
- Landing page
- Poll creation
- Poll visualisation
- Poll opt-in
- Poll voting
Landing page
In the landing page a dashboard shows created polls in small frames, highlighting basic polls’ information. By clicking on any frame the user can open the poll’s page with more details.
Poll creation
The user can create a poll, entering the required information like: poll question, poll name and poll options,
specifying the time frame in which it is allowed to opt in and vote for the poll. In this first version of the web-app,
users interaction happens on the front-end through their mnemonic key (which is not stored by the web
app), signing required transactions to create poll’s Smart Contract application on blockchain. On poll creations the
question is saved on chain into transaction note field starting with the tag [blockandpoll][permissionless]
. You can
retrieve all the polls searching for the tag
with Algorand Indexer.
Poll visualisation
The user can open a poll displayed in the landing page in order to get a detailed view of poll’s information, like: poll name, poll question, opt-in time frame, vote time frame, number of Algorand accounts subscribed to the poll and the number of votes received by each option. An histogram chart shows the votes summary.
Poll opt-In
Opt-In action is performed by the user entering the mnemonic key and clicking the opt-in button, if the opt-in phase has not expired yet and the user has not yet opt-in this poll, the application returns a green confirmation message. Otherwise a red error message is shown.
Poll voting
Voting action is performed by the user selecting an option, entering the mnemonic key and clicking on the vote button in order to express the vote. If the user has not voted for the poll yet and the voting phase is still open, the application returns a green confirmation message. In case the user votes before the opt-in, Block’n’Poll try to automatically perform opt-in on user behalf: if opt-in phase is still open, Block’n’Poll performs opt-in and vote actions in sequence.
Managing your polls
Algodesk Application Manager is a very useful tool to manage your polls. From the Algodesk dashboard you can delete your own polls or close-out polls you no longer want to participate in. As per polls’ TEAL logic, if users close-out their participation in a poll before the vote is over, their votes will be nullified.
Conclusions
Block’n’Poll wants to be an example of integration of a java web application and Algorand Blockchain. The development of this web application has brought out some considerations on how to use the blockchain, in particular on which information to save locally and which should be read from the blockchain, bearing in mind that it represents the only source of truth, but that it does not should be used as a large database. So it has been decided to locally save some information about the survey, but that are never designed to change (such as the start and end date of the survey) and to systematically read the information that can change on the blockchain, such as the number of accounts they have did opt in or the number of votes each option received. In this way, anyone could vote for a survey by doing it directly on the blockchain (according to the public nature of the blockchain ), but Block’n’poll would keep the consistency of the data shown as it is always read by the blockchain.
Other relevant considerations were made on how to include the smart contract code. It was chosen to simply read the template from the resources and substitute the parameters that change for each poll (the options you can vote on). For more complex smart contracts, in production environments, where perhaps there are substantial changes based on the parameters used in the construction, one could think of delegating the construction of the smart contract to a specific class or to a microservice with this responsibility.
It is evident that the tools made available by Algorand are ripe to be easily integrated into a more complex architecture, adapting well to existing architectural principles and good practices related to the testability of the code. This project, which at the moment is only a proof of concept, could lead the way for new use cases of use of the blockchain in the world of travel. Stay tuned for next evolutions.
Originally posted on https://developer.algorand.org/solutions/blocknpoll-blockchain-polling-web-application/