This document explains how to get started with Scalar DL by running your first simple contract with the client SDK. Here, we assume that you already have a certificate and a private key required to run contracts.

What is Scalar DL?

Scalar DL is a blockchain-inspired distributed ledger platform, which achieves:

  • High tamper-evidence of data
  • High scalablility and availablity
  • Linearizable consistency and ACID

It manages data as a set of assets, where an asset is composed of a history of data identified by a key called asset_id and a historical version number called age. In this document, you will create a very simple application to manage an asset’s status.

Configure properties

The first thing you will need to do is to configure some properties. The following sample properties are the minimum required to interact with the Scalar DL network. Please update the values depending on your environment.

[client.properties]
# A host name of Scalar DL network server.
scalar.dl.client.server.host=localhost

# An ID of a certificate holder. It must be configured for each private key and unique in the system.
scalar.dl.client.cert_holder_id=foo

# A certificate file path to use.
scalar.dl.client.cert_path=/path/to/foo.pem

# A private key file path to use. 
scalar.dl.client.private_key_path=/path/to/foo-key.pem

Register the certificate

Next, let’s register your certificate in the Scalar DL network. The registered certificate will allow you to register and execute contracts, and will also be used for tamper detection of the data stored in the network.

This time, let’s use a simple tool to register your certificate as follows.

$ client/bin/register-cert --properties client.properties

Note that certificate registration needs a privileged access to the network in production environment since the port is not open to the outside of the network.

Create a contract

Contracts in Scalar DL are simply Java classes which extend the Contract class and override the invoke method. Let’s take a closer look at the StateUpdater.java contract which creates an asset and associates some state with it.

package com.org1.contract;

import com.scalar.dl.ledger.asset.Asset;
import com.scalar.dl.ledger.contract.Contract;
import com.scalar.dl.ledger.exception.ContractContextException;
import com.scalar.dl.ledger.database.Ledger;
import java.util.Optional;
import javax.json.Json;
import javax.json.JsonObject;

public class StateUpdater extends Contract {

  @Override
  public JsonObject invoke(Ledger ledger, JsonObject argument, Optional<JsonObject> properties) {
    if (!argument.containsKey("asset_id") || !argument.containsKey("state")) {
      // ContractContextException is the only throwable exception in a contract and
      // it should be thrown when a contract faces some non-recoverable error
      throw new ContractContextException("please set asset_id and state in the argument");
    }

    String assetId = argument.getString("asset_id");
    int state = argument.getInt("state");

    Optional<Asset> asset = ledger.get(assetId);

    if (!asset.isPresent() || asset.get().data().getInt("state") != state) {
      ledger.put(assetId, Json.createObjectBuilder().add("state", state).build());
    }

    return null;
  }
}

This contract will extract a client-defined asset ID (asset_id) and state (state) from the argument and associate the asset ID with the state in the ledger, if the given state is different from the asset’s current state.

Next we need to compile the contract. This can be done with:

$ ./gradlew assemble

This will generate build/classes/java/main/com/org1/contract/StateUpdater.class.

Register the contract

Let’s register your contract. Here again we use a simple tool.

$ client/bin/register-contract --properties client.properties --contract-id StateUpdater --contract-binary-name com.org1.contract.StateUpdater --contract-class-file build/classes/java/main/com/org1/contract/StateUpdater.class

Please set a globally unique ID for the contract ID (e.g. StateUpdater in the above command). You can set different contract IDs on the same contract to clarify “who did what” in a tamper-evident way. For example, let’s think about a voting application. In the application, anyone can vote with the same voting logic, and hence can use the same Contract, but A’s vote and B’s vote need to be properly and securely distinguished; A cannot vote for B, and vice versa. Having different contract IDs on the same contract can be utilized to achieve such things.

Execute the contract

Now you are ready to execute the contract with the following command.

$ client/bin/execute-contract --properties client.properties --contract-id StateUpdater --contract-argument '{"asset_id":"some_asset", "state":3}'

In the contract argument, the value specified with the key asset_id must be unique globally for each asset. In Scalar DL 1.0, asset_id is not a reserved json key name and you can use any json key name, but this might be changed in future versions.

Create your own contracts

As we explained above, what you need to create your own contracts is extend the Contract class and override the invoke method as you like. We are preparing more sample contracts, so please wait for an update.

To quickly run and test your contrats in your local environment, Scalar DL Emulator might be useful. It uses a mutable in-memory ledger instead of an immutable ledger database and provides an interactive interface, making it easy to do trial and error testing.

Interact with ClientService

The tools we have used above are useful for simple testing purposes, but should not be used for production applications. The Client SDK also provides a service layer called ClientService which should be used for production applications. The following is a code snippet showing how to use ClientService to execute a contract.

  Injector injector = Guice.createInjector(new ClientModule(new ClientConfig(new File(properties))));
 
  try (ClientService service = injector.getInstance(ClientService.class)) {
    JsonObject jsonArgument = Json.createReader(new StringReader(contractArgument)).readObject();
    ContractExecutionResult result = service.executeContract(contractId, jsonArgument);
    result.getResult().ifPresent(System.out::println);
  } catch (ClientException e) {
    System.err.println(e.getStatusCode());
    System.err.println(e.getMessage());
  }

It’s pretty self-explanatory. It creates a ClientService instance using the dependency injection framework called Guice, and creates some json argument with javax.json.JsonObject. Then, you are ready to execute a contract as shown. For more information, please take a look at Javadoc.

References