(image)

Programming Hotmoka
A tutorial on Hotmoka and smart contracts in Takamaka

8.4 Node decorators

(See the io-hotmoka-tutorial-examples-runs project in https://github.com/Hotmoka/hotmoka)

There are some frequent actions that can be performed in code on a Hotmoka node. Typically, these actions consist in a sequence of transactions. A few examples are:

  • 1. The creation of an externally owned account. This requires the creation of its private and public keys and the instantiation of an io.takamaka.code.lang.ExternallyOwnedAccount. It is not a difficult procedure, but it is definitely tedious and occurs frequently.

  • 2. The installation of a jar in a node. This requires a transaction for installing code in the node. It requires also to parse the jar into bytes and identify the number of gas units for the transaction, depending on the size of the jar.

  • 3. The initialization of a node. Namely, local nodes start empty, that is, their store does not contain anything at the beginning, not even their manifest object. This initialization is rather technical and detail might change in future versions of Hotmoka. Performing this initialization by hand leads to fragile and error-prone code.

In all these examples, Hotmoka provides decorators, that is, implementations of the Node interface built from an existing Node object. A decorator is just an alias of the decorated node, but adds some functionality or performs some action on it. Fig. 8.1 shows that there are decorators for each of the three situations enumerated above.

In order to understand the use of node decorators and appreciate their existence, let us write a Java class that creates a DiskNode, initially empty; then it initializes that node; subsequently it installs our io-hotmoka-tutorial-examples-family-1.11.5.jar file from Sec. 3.4 in the node and finally creates two accounts in the node. We stress the fact that these actions can be performed in code by using calls to the node interface (Fig. 8.1) or through the moka tool. Here, however, we want to perform them in code, simplified with the use of node decorators.

Create the following Decorators.java class inside the io.hotmoka.tutorial.examples.runs package of the io-hotmoka-tutorial-examples-runs project:

package io.hotmoka.tutorial.examples.runs;


import static io.hotmoka.constants.Constants.HOTMOKA_VERSION;
import static io.takamaka.code.constants.Constants.TAKAMAKA_VERSION;


import java.math.BigInteger;
import java.nio.file.Paths;
import java.security.KeyPair;


import io.hotmoka.crypto.Entropies;
import io.hotmoka.crypto.SignatureAlgorithms;
import io.hotmoka.helpers.AccountsNodes;
import io.hotmoka.helpers.JarsNodes;
import io.hotmoka.node.ConsensusConfigBuilders;
import io.hotmoka.node.disk.DiskInitializedNodes;
import io.hotmoka.node.disk.DiskNodeConfigBuilders;
import io.hotmoka.node.disk.DiskNodes;


public class Decorators {


    public static void main(String[] args) throws Exception {
         var config = DiskNodeConfigBuilders.defaults().build();


         // the path of the runtime Takamaka jar, inside Maven's cache
         var takamakaCodePath = Paths.get
             (System.getProperty("user.home")
             + "/.m2/repository/io/hotmoka/io-takamaka-code/" + TAKAMAKA_VERSION
             + "/io-takamaka-code-" + TAKAMAKA_VERSION + ".jar");


         // the path of the user jar to install
         var familyPath = Paths.get(System.getProperty("user.home")
             + "/.m2/repository/io/hotmoka/io-hotmoka-tutorial-examples-family/"
             + HOTMOKA_VERSION
             + "/io-hotmoka-tutorial-examples-family-" + HOTMOKA_VERSION + ".jar");


         // create a key pair for the gamete
         var signature = SignatureAlgorithms.ed25519();
         var entropy = Entropies.random();
         KeyPair keys = entropy.keys("mypassword", signature);
         var consensus = ConsensusConfigBuilders.defaults()
             .setInitialSupply(BigInteger.valueOf(1_000_000_000))
             .setPublicKeyOfGamete(keys.getPublic()).build();


        try (var node = DiskNodes.init(config)) {
             // first decorator: store the io-takamaka-code jar
             // and create manifest and gamete
             var initialized = DiskInitializedNodes.of(node, consensus, takamakaCodePath);


             // second decorator: store the family jar: the gamete will pay for that
             var nodeWithJars = JarsNodes.of(node, initialized.gamete(), keys.getPrivate(), familyPath);


             // third decorator: create two accounts, the first with 10,000,000 coins
             // and the second with 20,000,000 units of coin; the gamete will pay
             var nodeWithAccounts = AccountsNodes.of
               (node, initialized.gamete(), keys.getPrivate(),
               BigInteger.valueOf(10_000_000), BigInteger.valueOf(20_000_000));


             System.out.println("manifest: " + node.getManifest());
             System.out.println("family jar: " + nodeWithJars.jar(0));
             System.out.println("account #0: " + nodeWithAccounts.account(0) +
               "\n with private key " + nodeWithAccounts.privateKey(0));
             System.out.println("account #1: " + nodeWithAccounts.account(1) +
               "\n with private key " + nodeWithAccounts.privateKey(1));
         }
    }
}

Run class Decorators:

cd io-hotmoka-tutorial-examples-runs
mvn clean install exec:exec -Dexec.executable="java" -Dexec.args="-cp %classpath io.hotmoka.tutorial.examples.runs.Decorators"
manifest: b0dfc3b4ee0c5976b94ea8a587081c71d9d4ef390e85a9ae2fb73f33fa5692cc#0
family jar: 4f06030c70db89dbb295a2f63436470ab37a3eca89e4baaa7907d734a3b19edd
account #0: 1c77c5476eebec8a2b2c030ae752ad655f1153796910fae2064e3ccf7a301a7d#2
  with private key Ed25519 Private Key [96:2c:78:7e:ca:1a:fd:68:ce:17:80:c4:a4:d7:63:10:df:bd:12:9b]
    public data: 6539f2fe1e68228c342ba3fd47e5f6c16e28c7b4cf801acad75f550666bcc03a


account #1: 1c77c5476eebec8a2b2c030ae752ad655f1153796910fae2064e3ccf7a301a7d#4
  with private key Ed25519 Private Key [f4:46:71:84:31:9a:28:1d:d0:a3:ac:02:44:6c:19:29:3e:4c:65:6d]
    public data: 1392cc01b610084cbf36de1d8c2d3c5b515de8d9cb28cda0e1ae10db4c290e38

As you can see, the use of decorators has avoided us the burden of programming transaction requests, explicitly, and makes our code more robust, since future versions of Hotmoka will update the implementation of the decorators, while their interface will remain untouched, shielding our code from modifications.

As we have already said, decorators are views of the same node, just seen through different lenses (Java interfaces). Hence, further transactions can be run on node or initialized or nodeWithJars or nodeWithAccounts, with the same effects. Moreover, it is not necessary to close all such nodes: closing node at the end of the try-with-resource will actually close all of them, since they are the same node.

There exist classes for initializing other kinds of Hotmoka nodes in code, such as the classes MokamintInitializedNodes and TendermintInitializedNodes.