Smart Contract Javafication: Web3j Wrappers and Other Sorceries

Smart Contract Javafication: Web3j Wrappers and Other Sorceries

Recently I was dabbling with Quorum, an open-source suite of tools, APIs, private key managers and clients to operate a private Ethereum network. My main goal was to figure out how to build applications on top of the Ethereum blockchain and how to correctly interact with Smart Contracts. I chose a Quorum version using Besu, an Ethereum client maintained by the Hyperledger Foundation that implements private transactions for enterprise use cases.

As a first step, we would need to prepare the tools of the trade. Of course, having a running blockchain is of the essence to even start working. The setup of a local Ethereum blockchain is very tricky IMHO, but we are in luck because ConsenSys curates a Quorum quickstart project with everything needed to have a network running in minutes. The Consensys hands-on guide does a nice job of making the network deployment straightforward, so I won’t cover the details here.

However, if everything was done correctly, you will host a lively party of docker images on your machine:

Image from consensys.net

There is a lot to unpack here. Aside from the ELK stack, the monitoring tools and the block explorer, we can find:

  • 4 Ethereum nodes running on QFTP. This is the minimum number required to have a Byzantine-fault resistant network
  • 3 Besu clients, holding their respective members’ info
  • 3 Tessera services, which are required to manage private transactions and their data
  • An RPC service node

Javafying a Smart Contract

The quorum-quickstart project features a smart contract called SimpleStorage.sol and some interesting scripts that showcase web3js, a library to interact with smart contracts on Ethereum networks. However, being more of a Kotlin person, I’d rather want to experiment with web3j (without the "s"), a Java-based library, and rewrite the same scripts to figure out what they actually do.

The first step is, obviously, to set up a project and then import the required dependencies:

// build.gradle (v6)
dependencies {
  implementation("org.web3j:core:4.8.7")
  implementation("org.web3j:besu:4.8.7")
}

Let’s not forget about the plugin as well:

// build.gradle (v6)
plugins {
  id("org.web3j") version "4.8.7"
}

The first step to take, after realizing that the web3j plugin at the moment does not support gradle 7, is to transform the SimpleStorage solidity file into a beautiful Java princess. Let’s take a look at it:

The contract logic is straightforward even for those among us who are not well-versed in the Solidity witchcraft. SimpleStorage.sol just stores a single unsigned integer on the blockchain, exposing some handy get/set methods for interacting with it. However, there is quite some work to do before we can afford to waltz in the company of this contract. Luckily, the web3j plugin can perform the work for us. All we need to do is to put it in a sensible location like the following

src
├── main
│   ├── kotlin
│   ├── solidity
│   │   └── SimpleStorage.sol⭐
│   └── resources
└── test

and then run gradle generateContractWrappers. The web3j plugin will build a Java wrapper calledSimpleStorage.java in the project’s build directory so that we can easily handle the contract in our Kotlin code.

Set up Member Credentials

As we saw above, the Quorum Quickstart created three Besu memberships: for simplicity’s sake, we will call them ALICE, BOB and CHARLIE respectively. The private keys listed are generated by the Quorum quickstart example and stored in the smart_contracts/keys.js file

The main elements are:

  • members’ node URLs: these are the endpoints to reach the Besu clients and submit the requests
  • private keys: members’ credentials to submit transactions, from which we can also derive their public key and address. It’s all hardcoded here for demonstrative purposes, it’s best to rely on a proper key management system for real uses cases
  • Tessera public keys: references to the Tessera’s enclave IDs, needed for managing private transaction data

Now that we looked at the file contents we can, well, translate them to a more JVM-palatable language, like Kotlin:

Everything is finally ready to perform some blockchain trickery.

Running a Public Transaction

A basic starting point would be to deploy our SimpleStorage smart contract and submit a transaction to it. But how can we? Honestly, the Javascript example looks a bit convoluted:

A lot is going on there, and it’s normal to feel a little dizzy at first. In a few words, the example builds a RawTransaction from scratch by submitting the hex encoding of the smart contract, signs the transaction locally with Alice’s private key and submits the transaction. Finally, the contract address is not immediately available, we still have to wait for the transaction to be confirmed to later invoke the contract methods. In short, it’s a drag.

However, it doesn’t have to be this way.

val simpleStorage = SimpleStorage.deploy(
  clientAlice, ALICE, GAS_PRICE, GAS_LIMIT, BigInteger.ONE
).send()

… magic! We just deployed a smart contract on Ethereum with just a one-liner hocus pocus, all thanks to the web3j wrappers and niceties. We can invoke get and set methods in Kotlin idiomatically, as simpleStorage is a regular object. Alice's credentials are embedded in the smart contract instance, so we can submit any transaction and web3j will take care of encoding, signing, sending to Besu and all that jazz. It goes without saying, it’s only fair that Bob and Charlie should have the chance to access to same public contract

val contractForBob = SimpleStorage.load(
  DEPLOYED_CONTRACT_ADDRESS, clientBob, BOB, GAS_PRICE, GAS_LIMIT
)

It’s possible to write a very basic dApp just by accessing the aforementioned set of APIs. Alice and Bob could interact on the same instance of the smart contract by accessing their independent ledger instances, possibly from opposite sides of the world:


Running a Private Transaction 🥷

Now that we all feel a bit godmotherly, we can even think of dipping our feet in private transactions. Please beware: when mentioning private transactions, I mean transactions where the data payload is available to a closed group of participants. This feature in Besu is orthogonal to the target network: it is possible to run private transactions either in the Ethereum mainnet or in a permissioned network.

The enabling technology for the privacy layer is called Tessera: it is a decentralized storage for private transaction data, where each participant is identified by an Enclave key and has access to a reserved Tessera node. A collective of multiple enclaves is called a Privacy Group, an entity that holds a private state collected during private transactions. Of course, any node willing to work with private data must have its Tessera Node. The Quorum quickstart example will set up Enclaves out of the box: just remember to enable privacy groups by configuring environment variables.

https://besu.hyperledger.org/en/stable/Concepts/Privacy/Privacy-Overview/

Running private transactions is a bit more involved than submitting public ones, but web3j still makes a good job of spoiling us. The main caveat is that we now need a private transaction processor to check whether the private transactions are being mined or not, and a private transaction manager instead of plain credentials. But let’s go in order.

We will create a privacy group for Alice and Charlie and wait until it is confirmed onchain

Deploying smart contracts and interacting with them is almost as straightforward as it was with public transactions. All we need is the Privacy Group ID and a special transaction manager that is easily built from information already available

Finally, all we need to do is deploy a Smart Contract privately

val privateSimpleStorage = SimpleStorage.deploy(
  clientAlice, tm, GAS_PRICE, GAS_LIMIT, BigInteger.ONE
).send()

Our private smart contract wrapper will now expose the set and get methods of the Solidity implementation but, unsurprisingly, data will only be available within the privacy group.

Conclusions

JVM-based languages are not first to mind when discussing web3 applications but, honestly, they are pretty ubiquitous and there is a nice ecosystem in place for them already. It’s only up to us to decide what to build.

If you had a good time and would like to read more articles with questionable Disney comparisons, follow me on Medium and let me know what is your opinion.

Well, let’s go now: our Kotlinderellas are waiting for us!

References