(image)

Programming Hotmoka
A tutorial on Hotmoka and smart contracts in Takamaka

3.3 Creation of an object of our class

(See the io-hotmoka-tutorial-examples-family-storage and the io-hotmoka-tutorial-examples-runs projects in https://github.com/Hotmoka/hotmoka)

The jar of our program is in the store of the node now: the moka jars install command has installed it at ef42935a11dd60a7… and our code at 8e176c37d3fa6113…. We can use either of them, interchangeably, as class path for the execution of a transaction that tries to run the constructor of our class Person and add a brand new Person object into the store of the node. We can perform this through the moka tool:

moka objects create 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.family.Person Einstein 14 4 1879 null null --classpath=ef42935a11dd60a715925afc4eb5339b475a3a241433edb17571672269221af0 --
password-of-payer --uri=ws://panarea.hotmoka.io:8001
Enter value for --password-of-payer (the password of the key pair of the payer account): chocolate
Do you really want to call constructor
  public ...Person(java.lang.String,int,int,int,...Person,...Person)
  spending up to 1000000 gas units at the price of 1 pana per unit (that is, up to 1000000 panas) [Y/N] Y
Adding transaction 820a943e09e462923d1d8c8187533adc51f748d0b226842eeb7e072184845edc... failed.
The transaction failed with message io.hotmoka.node.local.SerializationException: An object of class io.hotmoka.tutorial.examples.family.Person cannot be serialized into a storage value since it does not implement io.takamaka.code.lang.
Storage


Gas consumption:
 * total: 1000000
   * for CPU: 14936
   * for RAM: 4990
   * for storage: 17840
   * for penalty: 962234
 * price per unit: 1 pana
 * total price: 1000000 panas

The moka objects create command requires to specify who pays for the object creation (our account 3fcbb8889b77be34…#0), then the fully-qualified name of the class that we want to instantiate (io.hotmoka.tutorial.examples.Person) followed by the actual arguments passed to its constructor. The classpath refers to the jar that we have installed previously. The moka objects create command asks for the password of the payer account and checks if we really want to proceed (and pay). But then it ends up in failure (SerializationException). Note that all offered gas has been spent. This is a sort of penalty for running a transaction that fails. The rationale is that this penalty should discourage potential denial-of-service attacks, when a huge number of failing transactions are thrown at a node. At least, that attack will cost a lot. Moreover, note that the transaction, although failed, does exist. Indeed, the nonce of the caller has been increased, as you can check with moka objects show on our account.

But we still have not understood why the transaction failed. The reason is in the exception message: An object of class ...Person cannot be serialized into a storage value since it does not implement io.takamaka.code.lang.Storage. Takamaka requires that all objects stored in a node extend the io.takamaka.code.lang.Storage class. That superclass provides all the machinery needed in order to keep track of updates to such objects and persist them in the store of the node, automatically.

Do not get confused here. Takamaka does not require all objects to extend the class io.takamaka.code.lang.Storage. You can use objects that do not extend that superclass in your Takamaka code, both instances of your classes and instances of library classes from the java.* hierarchy, for instance. What Takamaka does require, instead, is that objects that must be kept in the store of a node do extend io.takamaka.code.lang.Storage. This must be the case, for instance, for objects created by the constructor invoked through the moka objects create command.

Let us then modify the io.hotmoka.tutorial.examples.Person.java source code, inside the io-hotmoka-tutorial-examples-family project:

package io.hotmoka.tutorial.examples.family;


import io.takamaka.code.lang.Storage;


public class Person extends Storage {
    ... unchanged code ...
}

Extending io.takamaka.code.lang.Storage is all a programmer needs to do in order to let instances of a class be stored in the store of a node. There is no explicit method to call to keep track of updates to such objects and persist them in the store of the node: Hotmoka nodes will automatically deal with them.

We can use the io.takamaka.code.lang.Storage class and we can run the resulting compiled code since that class is inside io-takamaka-code, that has been included in the class path as a dependency of io-hotmoka-tutorial-examples-family-1.11.5.jar (Fig. 3.3).

Since class Person has been modified, you need to regenerate the previousley generated jar file io-hotmoka-tutorial-examples-family-1.11.5.jar, by running mvn install again inside the io-hotmoka-tutorial-examples-family project. Install this new version in blockchain:

cd io-takamaka-code-examples-family
mvn clean install
cd ..
moka jars install 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io-hotmoka-tutorial-examples-family/target/io-hotmoka-tutorial-examples-family-1.11.5.jar --yes --password-of-payer --uri=ws://panarea.hotmoka.io:8001
Enter value for --password-of-payer (the password of the key pair of the payer account): chocolate
The jar has been installed at e65b791ab8708e367e2a8f3c5f6a00af0ca6415052989947aed683f9d9a32e9f.


Gas consumption:
 * total: 490544
   * for CPU: 15363
   * for RAM: 5181
   * for storage: 470000
   * for penalty: 0
 * price per unit: 1 pana
 * total price: 490544 panas

then run the moka objects create command again. This time, the execution should complete without exception:

moka objects create 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 io.hotmoka.tutorial.examples.family.Person Einstein 14 4 1879 null null --classpath=e65b791ab8708e367e2a8f3c5f6a00af0ca6415052989947aed683f9d9a32e9f --
password-of-payer --uri=ws://panarea.hotmoka.io:8001
Enter value for --password-of-payer (the password of the key pair of the payer account): chocolate
Do you really want to call constructor
  public ...Person(java.lang.String,int,int,int,...Person,...Person)
  spending up to 1000000 gas units at the price of 1 pana per unit (that is, up to 1000000 panas) [Y/N] Y
A new object 841187943edc312d9297f470fc6c47f1b4745bf0325ffb8d7727d3d61d4cf1f3#0 has been created.


Gas consumption:
 * total: 58707
   * for CPU: 14950
   * for RAM: 5037
   * for storage: 38720
   * for penalty: 0
 * price per unit: 1 pana
 * total price: 58707 panas

The new object has been allocated at a storage reference 841187943edc312d…#0 that can be used to refer to it, also in the future. You can verify that it is actually there and that its fields are correctly initialized, by using the moka objects show command:

moka objects show 841187943edc312d9297f470fc6c47f1b4745bf0325ffb8d7727d3d61d4cf1f3#0 --uri ws://panarea.hotmoka.io:8001
class io.hotmoka.tutorial.examples.family.Person (from jar installed at e65b791ab8708e367e2a8f3c5f6a00af0ca6415052989947aed683f9d9a32e9f)
  day:int = 14
  month:int = 4
  name:java.lang.String = "Einstein"
  parent1:io.hotmoka.tutorial.examples.family.Person = null
  parent2:io.hotmoka.tutorial.examples.family.Person = null
  year:int = 1879

Compared with Solidity, where contracts and accounts are just untyped addresses, objects (and hence accounts) are strongly-typed in Takamaka. This means that they are tagged with their run-time type (see the output of moka objects show above), in a boxed representation, so that it is possible to check that they are used correctly, i.e., in accordance with the declared type of variables, or to check their run-time type with checked casts and the instanceof operator. Moreover, Takamaka has information to check that such objects have been created by using the same jar that stays in the class path later, every time an object gets used (see the information from jar installed at in the output of moka objects show above).

We can perform the same object creation in code, instead of using the moka objects create command. Namely, the following code builds on the previous example and installs a jar by adding a further transaction that calls the constructor of Person:

package io.hotmoka.tutorial.examples.runs;


import static io.hotmoka.helpers.Coin.panarea;
import static io.hotmoka.node.StorageTypes.INT;
import static java.math.BigInteger.ONE;


import java.math.BigInteger;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;


import io.hotmoka.constants.Constants;
import io.hotmoka.crypto.api.Signer;
import io.hotmoka.helpers.GasHelpers;
import io.hotmoka.helpers.SignatureHelpers;
import io.hotmoka.node.Accounts;
import io.hotmoka.node.ConstructorSignatures;
import io.hotmoka.node.MethodSignatures;
import io.hotmoka.node.StorageTypes;
import io.hotmoka.node.StorageValues;
import io.hotmoka.node.TransactionRequests;
import io.hotmoka.node.api.Node;
import io.hotmoka.node.api.requests.SignedTransactionRequest;
import io.hotmoka.node.api.transactions.TransactionReference;
import io.hotmoka.node.api.types.ClassType;
import io.hotmoka.node.api.values.StorageReference;
import io.hotmoka.node.remote.RemoteNodes;


public class FamilyStorage {


    private final static ClassType PERSON = StorageTypes.classNamed("io.hotmoka.tutorial.examples.family.Person");


    public static void main(String[] args) throws Exception {
        // 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/"
        + Constants.HOTMOKA_VERSION
        + "/io-hotmoka-tutorial-examples-family-" + Constants.HOTMOKA_VERSION + ".jar");


        var dir = Paths.get(args[1]);
        var payer = StorageValues.reference(args[2]);
        var password = args[3];


        try (var node = RemoteNodes.of(new URI(args[0]), 150000)) {
            // we get a reference to where io-takamaka-code-X.Y.Z.jar has been stored
            TransactionReference takamakaCode = node.getTakamakaCode();


            // we get the signing algorithm to use for requests
            var signature = node.getConfig().getSignatureForRequests();


            KeyPair keys = loadKeys(node, dir, payer, password);


            // we create a signer that signs with the private key of our account
            Signer<SignedTransactionRequest<?>> signer = signature.getSigner
            (keys.getPrivate(), SignedTransactionRequest::toByteArrayWithoutSignature);


            // we get the nonce of our account: we use the account itself as caller and
            // an arbitrary nonce (ZERO in the code) since we are running
            // a @View method of the account
            BigInteger nonce = node
            .runInstanceMethodCallTransaction(TransactionRequests.instanceViewMethodCall
            (payer, // payer
            BigInteger.valueOf(100_000), // gas limit
            takamakaCode, // class path for the execution of the transaction
            MethodSignatures.NONCE, // method
            payer)).get() // receiver of the method call
            .asBigInteger(__ -> new ClassCastException());


            // we get the chain identifier of the network
            String chainId = node.getConfig().getChainId();


            var gasHelper = GasHelpers.of(node);


            // we install the family jar in the node: our account will pay
            TransactionReference family = node
            .addJarStoreTransaction(TransactionRequests.jarStore
            (signer, // an object that signs with the payer's private key
            payer, // payer
            nonce, // payer's nonce: relevant since this is not a call to a @View method!
            chainId, // chain identifier: relevant since this is not a call to a @View method!
            BigInteger.valueOf(1_000_000), // gas limit: enough for this small jar
            gasHelper.getSafeGasPrice(), // gas price: at least the current gas price of the network
            takamakaCode, // class path for the execution of the transaction
            Files.readAllBytes(familyPath), // bytes of the jar to install
            takamakaCode)); // dependencies of the jar that is being installed


            // we increase our copy of the nonce, ready for further
            // transactions having the account as payer
            nonce = nonce.add(ONE);


            // call the constructor of Person and store in einstein the new object in blockchain
            StorageReference einstein = node.addConstructorCallTransaction
            (TransactionRequests.constructorCall
            (signer, // an object that signs with the payer's private key
            payer, // payer
            nonce, // payer's nonce: relevant since this is not a call to a @View method!
            chainId, // chain identifier: relevant since this is not a call to a @View method!
            BigInteger.valueOf(300_000), // gas limit: enough for a small object
            panarea(gasHelper.getSafeGasPrice()), // gas price, in panareas
            family, // class path for the execution of the transaction


            // constructor Person(String,int,int,int)
            ConstructorSignatures.of(PERSON, StorageTypes.STRING, INT, INT, INT),


            // actual arguments
            StorageValues.stringOf("Einstein"), StorageValues.intOf(14),
            StorageValues.intOf(4), StorageValues.intOf(1879)
            ));


            System.out.println("new object allocated at " + einstein);


            // we increase our copy of the nonce, ready for further
            // transactions having the account as payer
            nonce = nonce.add(ONE);
        }
    }


    private static KeyPair loadKeys(Node node, Path dir, StorageReference account, String password) throws Exception {
        return Accounts.of(account, dir)
        .keys(password, SignatureHelpers.of(node).signatureAlgorithmFor(account));
    }
}

The new transaction is triggered by the addConstructorCallTransaction() call, that expands the node with a new transaction that calls a constructor. We use our account as payer for the transaction, hence we sign the request with its private key. The class path includes path jars io-hotmoka-tutorial-examples-family-1.11.5.jar and its dependency io-takamaka-code. The signature of the constructor specifies that we are referring to the second constructor of Person, the one that assumes null as parents. The actual parameters are provided; they must be instances of the io.hotmoka.node.api.values.StorageValue interface. We provide \( 300000 \) units of gas, which should be enough for a constructor that just initializes a few fields. We are ready to pay panarea(gasHelper.getSafeGasPrice()) units of coin for each unit of gas. This price could have been specified simply as gasHelper.getSafeGasPrice(), but we used the static method io.hotmoka.helpers.Coin.panarea() to generate a BigInteger corresponding to the smallest coin unit of Hotmoka nodes, a panarea. Namely, Hotmoka uses the units of coin shown in Fig. 3.4.

.
value (in panas) exponent name short name
\( 1 \) \( 1 \) panarea pana
\( 1000 \) \( 10^3 \) alicudi ali
\( 1000000 \) \( 10^6 \) filicudi fili
\( 1000000000 \) \( 10^9 \) stromboli strom
\( 1000000000000 \) \( 10^{12} \) vulcano vul
\( 1000000000000000 \) \( 10^{15} \) salina sali
\( 1000000000000000000 \) \( 10^{18} \) lipari lipa
\( 1000000000000000000000 \) \( 10^{21} \) moka moka
Figure 3.4: The coin units of Hotmoka.

with corresponding static methods in io.hotmoka.helpers.Coin.

You can run FamilyStorage now, and see that, this time, the object of class Person gets created in blockchain:

mvn compile exec:java -Dexec.mainClass="io.hotmoka.tutorial.examples.runs.FamilyStorage" -Dexec.args="ws://panarea.hotmoka.io:8001 hotmoka_tutorial 3fcbb8889b77be347c3bfe0019683d3fefe2f58712085208c58cfc4d91add793#0 chocolate"
new object allocated at 3bb5bdb7126f7a3de5dc9cd3f9ad66fe6a85152a418c3fb86add9053e553292d#0

The exact address where the Person object gets allocated will change at each run.