Skip to content

A few words about connecting Vert.x with an Ethereum blockchain

While working on our Codesity’s first very own project, I faced a problem of connecting existing Vert.x microservices architecture with blockchain storage. As there is no publications on how to do it with Vert.x (only with Spring Boot), I decided to write this small case study.

Blockchain is just a database

Blockchains seem to be a very complex concept, but in a nutshell there is nothing hard to understand. Two crucial characteristics are immutability and peer-to-peer nature. However, under the hood, blockchain is a database, that is validated by network, rather one single authority. This is important – blockchain is basically an another type of database, so consider Web3j as a database connector, like JDBC or Mongo connector. In terms of software architecture there is no difference. I use Web3j in this case study, but you can use EthereumJ or use another blockchain with respected access library without changes of an architecture.

Application structure

As any normal Vert.x application, our Ethereum project starts from Verticles – independent blocks of computation. We have here two Verticles – one for API endpoint (routing and so on), and second for abstracting all Ethereum-related tasks. They communicate via Vert.x eventbus messaging in reactive and asynchronous way. (or you can use other messaging provider if you need). Take a look on the graph below:

Graph 1. Sample application architecture

You could also note, that there is a special EthereumService. It has same purpose as Repository pattern or DAO pattern – to encapsulate implementation-specific code from main logic and increase decoupling and maintainability. We can define as many abstract methods as we need, for example to add data or to retrieve data to/from blockchain. In our example, for demonstration purposes, there is only one method.

EthereumVerticle and IEthereumService

EthereumVerticle is responsible for connections to Ethereum network and make transaction operations. It has a reference for EthereumService, that, as I mentioned before, abstracts implementation from main logic of Verticle. Enough talk, take a look for following code snippets:

IEthereumService

public interface IEthereumService{

	void start(Future f);

	void shutdown();

	void addTransaction (JsonObject payload);

}

This just an interface, e.g. a general code contract and it has 3 methods: start, shutdown and addTransaction. We would talk about first two methods in the next section.

EthereumVerticle

public class EthereumVerticle extends AbstractVerticle{

	private IEthereumService service;

	public EthereumVerticle (IEthereumService es){
		this.service = es;
	}

	@Override
	public void start(){
		super.start();
		vertx.executeBlocking(service::start, r->{
			if (r.succeeded()){
				System.out.println("Ethereum service started");
			} else {
				System.out.println(r.cause().getLocalizedMessage());
			}
		});
	}

	@Override
	public void stop(){
		super.stop();
		service.shutdown();
	}
}

The importance of start() and shutdown() methods

I highlighted these two methods separately – and – for the reason. Starting and stopping web3j client is a crucial task. First, we have a start() method – note it accepts Vert.x Future as an argument:

public void start(Future future){
		try{
            web3j = Web3j.build(new HttpService(endpoint));
            Web3ClientVersion clientVersion = web3j.web3ClientVersion().send();
            String version = clientVersion.getWeb3ClientVersion();
            System.out.println(version);
            credentials = WalletUtils.loadCredentials(password, walletFile);
            future.complete();
        } catch (Exception e){
            future.fail(e);
        }
}

We use Future here (again, pay your attention, that it is Vert.x’s future), because the connection is resource-consuming operation, and we need to process it asynchronously, better outside of main thread, using executeBlocking method. Also, we retrieve the client’s version – just for proof-of-concept purpose, to assert that everything is up and running. You could note that we also retrieve credentials – this question deserve its own section, so please be patient, we would talk about it in the “Auth” section below.

When you no longer need a Web3j instance you need to call the shutdown method to close resources used by it.

public void shutdown(){
	web3j.shutdown();
}

Connecting to network

I mentioned in the previous section about start method, that encapsulates required logic for connection to the Ethereum network. But we need to inject an Endpoint argument. For quick start, I suggest you to create a free account with Infura (but you could also run a local geth client) and obtain API token:

You need Endpoint value. The final url, you should pass as an argument, will look like https://<endpoint>. If you did everything correctly, when you would run, you would get a web3j client version in the STDOUT.

Add new transaction to blockchain

And here is the most interesting part. How to process a new transaction to blockchain? I need to remind you over and over again, that Ethereum connection is resource-hungry code, and we have two ways:

  • Use Web3j built-in async methods (relied on RxJava)
  • Explicitly execute blocking code outside of main thread using executeBlocking.

What is a Ethereum transaction? Basically, it is a fundamental message structure in blockchain. Some data we want to add to the network. Transaction is a data, so we encapsulate it as a JsonObject, but we need to “unpack” payload and create Transaction object before we would add anything:

public void addTransaction(JsonObject payload){

	/*Step 1. Get next available nonce*/

	EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
             address, DefaultBlockParameterName.LATEST).send();
	BigInteger nextNonce = ethGetTransactionCount.getTransactionCount();

	/*Step 2. Extract payload */
	String toAddress = payload.getString("to_addtess");
  String data = payload.getString("data");
	BigInteger gasPrice = BigInteger.valueOf(payload.getLong("gas_price"));
	BigInteger gasLimit = BigInteger.valueOf(payload.getLong("gas_limit"));

	/*Step 3. Create WEB3J TRANSACTION */
	RawTransaction transaction = RawTransaction.createEtherTransaction(nonce, gasPrice, gasLimit, toAddress, data);

	/*Step 4. Sign */
	byte[] signed = TransactionEncoder.signMessage(transaction, credentials);
	String hex = Numeric.toHexString(signed);

	/*Step 5. Send*/
	EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hex).send();
}

Hey, what are gas price and gas limit? Gas price is the number of instructions used to execute a transaction in the Ethereum Virtual Machine. Web3j uses a default price of 22,000,000,000 Wei (22 x 10-8 Ether). Gas limit in its regard is the total amount of gas you are happy to spend on the transaction execution. There is an upper limit of how large a single transaction can be in an Ethereum block which restricts this value typically to less then 6,700,000.

These parameters taken together dictate the maximum amount of Ether you are willing to spend on transaction costs. Where do you take these params? On the Ethereum Network Status website:

Wooh! I admit, Ethereum is not MySQL and process transaction there is far harder, but if you are curious, you can figure out, that basically we perform same steps as we do with other databases.

NB! There is another way to send transactions, but we use RawTransaction here, as we use Infura – you could find more in official docs.

Auth

You noted already (twice) the mysterious field credentials. Ethereum authentication involves a bit more stuff, then, for example, JDBC. But it is not hard to get. First, you need to obtain Wallet file. Lucky, we can do it with Web3j. If you already have a Wallet, all what you need is to load credentials (as we did inside start method). But I guess, you read this section, as you don’t have a wallet :). It is not a big problem!

One way is to install web3j command line tools. Then just generate a new wallet using CLI:

web3j wallet create

Other approach, is to use Web3j. We need to provide two arguments here:

  • password = your password
  • path = path to destination directory
String password = ... //
String path = ... //
String walletFile = WalletUtils.generateNewWalletFile(password, new File(path));

And then just load it, as we did before.

Conclusion

Working with blockchain from Java is not a scary thing. There is a handy Web3j library, but if you choose correct software design, you would find that you code does not depend on particular blockchain implementation. Because, basically, blockchain is just a fancy database, and we can use same approaches, we know.

References

  • Jon Martindale. What is a Blockchain? (2019), Digital Trends, read here
  • Piotr Minkowski. Introduction to Blockchain with Java using Ethereum, web3j and Spring Boot (2018), read here
  • Conor Svensson. Reactive Java on the blockchain with web3j (2017), Oracle Community Directory, read here
Copy link
Powered by Social Snap