lastminute.com logo

Technology

Blockchain Dapp and Modern Software Engineering

fabio_fiorella
fabio fiorella

Block'n'poll a complete example of a full stack web application interacting with Algorand Blockchain


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 emoji-laughing), 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 the save() 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 the IndexerClient 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.

home
home

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_creation
poll_creation

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 visualization
poll visualization

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/


Read next

A Monorepo Experiment: reuniting a JVM-based codebase

A Monorepo Experiment: reuniting a JVM-based codebase

luigi_noto
luigi noto

Continuing the Monorepo exploration series, we’ll see in action a real-life example of a monorepo for JVM-based languages, implemented with Maven, that runs in continuous integration. The experiment of reuniting a codebase of ~700K lines of code from many projects and shared libraries, into a single repository. [...]

Exploring the Monorepo

Exploring the Monorepo

luigi_noto
luigi noto

Monorepo has become quite popular in Javascript world, and yet not fully explored for JVM-based development. The lack of tools and practical examples slow down its adoption in everyday software projects. In this series of articles, we’ll try to uncover the full potential of a monorepo with Java and Maven. [...]