[JAVA] I investigated how to write smart contracts for distributed ledger Scalar DLT

This time, I would like to explain how to write smart contracts and sample applications that will be used in the next and subsequent times. The articles up to the last time are as follows.

-Recently talked about distributed ledger Scalar DLT (basic edition) -Recently talked about distributed ledger Scalar DLT (smart contract execution)

Asset management application

Although it is a sample application, I would like to use the following, which I briefly introduced earlier.

-Introduction to Scalar DL by INDETAIL

This application manages assets very simply, and the following 6 smart contracts are prepared.

Contract name function
AddTypeContract Register the asset type
ListTypeContract Get a list of asset types
AddAssetContract Register assets
ListContract Get a list of assets
StateChangeContract Change the state of an asset
AssetHistoryContract Get asset usage history

Assets can be registered, viewed, borrowed, and returned using this smart contract. I would like to introduce the contents of each source immediately, but before that, let's explain the basics of smart contracts.

How to write a smart contract

Scalar DL smart contracts can be written in pure Java. However, it is not good to write it as you like, so I will introduce the guidelines.

--Do not include complex logic in one smart contract. If you want to work with complex logic, break it down and make nested calls. --The use of external Java libraries other than the Scalar DLT library is not allowed. --Inner classes are allowed. --Nested smart contract calls are possible. --ʻInvoke () `method must be implemented.

There is a limitation that the external library cannot be used, but it will not be a problem because it is only necessary for the smart contract side to perform such unique processing. Let's take a look at the base code as well.

import com.scalar.ledger.contract.Contract;
import com.scalar.ledger.ledger.Ledger;
import java.util.Optional;
import javax.json.Json;
import javax.json.JsonObject;

public class MyContract extends Contract {
  @Override
  public JsonObject invoke(Ledger ledger, JsonObject argument, Optional<JsonObject> property) {
    // read or write data and respond to clients here
  }
}

Only the ʻinvoke ()method is described, but since this is the part that is actually called, it must be implemented. The argumentledger is an object for accessing key-value type data stored in Scalar DB. Data can be added / updated / read / acquired. ʻArgument is a parameter passed to the smart contract and is in JSON format. It is recommended to set a string property called nonce on this object. It is said that a value that is unique in the world is good, so I think it is better to use around UUID. property is the parameter set when deploying the smart contract. This cannot be changed later.

Let's talk a bit about the ledger method. The first is ʻOptional get (String key)`, which is used to get the current value of the specified key.

Optional<Asset> opt = ledger.get("somekey");
if (opt.isPresent()) {
  Asset asset = opt.get();
  int age = asset.age();
  JsonObject value = asset.data();
}

The ʻagethat can be obtained here is the number of times the data has been updated, and is automatically counted from 0. Next is aboutvoid put (String key, JsonObject value)`, which registers a JSON object with the specified key.

Optional<Asset> opt = ledger.get(name);
if (opt.isPresent()) {
  return Json.createObjectBuilder()
    .add("message", "Type " + name + " is already registered.")
    .build();
}

ledger.put(name, Json.createObjectBuilder().build());

Implicit writing is not allowed for put, so be sure to get before writing. Next is about List <Asset> scan (String key). It can get the change history of the specified key.

AssetFilter filter = new AssetFilter(key);
List<Asset> history = ledger.scan(filter);
for (Asset asset : history) {
  String id = asset.id();
  int age = asset.age();
  JsonObject value = asset.data();
}

Suddenly, ʻAssetFilter` came out, but this is a class that allows you to specify the range of history.

//age from 5 to 9
new AssetFilter(id).withStartVersion(5, true).withEndVersion(10, false);
//age from 5 to the latest
new AssetFilter(id).withStartVersion(5, true);

And so far is the basic part. It's been a bit long, but if you keep this in mind, you can easily feel the description of smart contracts.

Asset management application smart contract

AddTypeContract AddTypeContract is the process of registering the asset type. The entire source is here You can check it at, so here I would like to extract and explain the points.

  public static final String HOLDER_ID = "holderId";

The first is an important property throughout this asset management application. Each smart contract has a property called holderId, which can only be set at deployment time.

    if (!property.isPresent() || !property.get().containsKey(HOLDER_ID)) {
      throw new ContractContextException("property: `" + HOLDER_ID + "` is mandatory.");
    }

When the smart contract is called, the property is checked as described above to prevent execution by anyone other than the deployed user. The rest is simple and the same name is checked.

    Optional<Asset> type = ledger.get(holderId + "-" + name);
    if (type.isPresent()) {
      return Json.createObjectBuilder()
          .add(RESULT, FAILURE)
          .add(MESSAGE, "Type " + name + " is already registered.")
          .build();
    }

    ledger.put(holderId + "-" + name, Json.createObjectBuilder().build());

And finally, the asset type is added to the value of the key of holderId +"-"+ TYPE.

    JsonObject newType = Json.createObjectBuilder().add(NAME, name).build();
    ledger.get(holderId + "-" + TYPE);
    ledger.put(holderId + "-" + TYPE, newType);

ListTypeContract ListTypeContract is a process that returns a list of registered asset types. The source will be here I will.

Since the processing of holderId is common, if you omit the explanation, the processing is simple because it is only data acquisition.

    AssetFilter filter = new AssetFilter(holderId + "-" + TYPE);
    List<Asset> history = ledger.scan(filter);
    if (history.isEmpty()) {
      return Json.createObjectBuilder()
          .add(RESULT, FAILURE)
          .add(MESSAGE, "No types were registered. Use am add-type to create one.")
          .build();
    }

    JsonArrayBuilder builder = Json.createArrayBuilder();
    for (Asset h : history) {
      JsonObject type =
          Json.createObjectBuilder().add(TYPE, h.data().getString(NAME)).add(AGE, h.age()).build();
      builder.add(type);
    }
    JsonArray types = builder.build();

If there is no data, an error is returned, and if there is data, it is returned as an array.

AddAssetContract The following AddAssetContract is similar to AddTypeContract, but with the process of registering an asset by type. The source is here.

It's basically the same as AddTypeContract, but it has many required arguments.

    if (!argument.containsKey(TYPE)
        || !argument.containsKey(ASSET)
        || !argument.containsKey(TIMESTAMP)
        || !argument.containsKey(ID)) {
      throw new ContractContextException("wrong argument.");
    }

I won't explain the type and asset existence check, but after that you save the loan status and name.

    JsonObject assetStatusJson =
        Json.createObjectBuilder().add(TIMESTAMP, timestamp).add(STATUS, IN_STOCK).build();
    ledger.put(holderId + "-" + id, assetStatusJson);

    JsonObject assetNameJson = Json.createObjectBuilder().add(ID, id).add(NAME, name).build();
    ledger.put(holderId + "-" + type, assetNameJson);

ListContract As you can imagine, ListContract is basically the same as ListTypeContract. So is here Will be.

Compared to ListTypeContract, the argument type is required, and it returns the asset list of the specified type. The reason why the processing is a little longer is that the lending status is acquired by another processing.

    JsonArrayBuilder assetsBuilder = Json.createArrayBuilder();
    for (Asset asset : assetList) {
      JsonObject data = asset.data();
      if (data.size() == 0) { // initiated one, ignore it
        continue;
      }
      String id = data.getString(ID);
      String name = data.getString(NAME);

      Optional<Asset> borrowingStatus = ledger.get(holderId + "-" + id);
      if (!borrowingStatus.isPresent()) {
        /**
         * Abnormal case. We found an asset in list but no borrowing status record. Just ignore it
         */
        continue;
      }

      JsonObjectBuilder statusBuilder = Json.createObjectBuilder();
      statusBuilder
          .add(ID, id)
          .add(NAME, name)
          .add(TIMESTAMP, borrowingStatus.get().data().getJsonNumber(TIMESTAMP).longValue())
          .add(STATUS, borrowingStatus.get().data().getString(STATUS));

      if (borrowingStatus.get().data().containsKey(HOLDER_ID)) {
        statusBuilder.add(HOLDER_ID, borrowingStatus.get().data().getString(HOLDER_ID));
      }

      assetsBuilder.add(statusBuilder.build());
    }

StateChangeContract The following StateChangeContract is the main part of this application. Responsible for borrowing / returning status management. The source is here.

The process is simple even if it is called main, and first, if the same status as the current one is specified, an error is returned.

    if (data.getString(STATUS).equals(newStatus)) {
      return Json.createObjectBuilder()
          .add(RESULT, FAILURE)
          .add(
              MESSAGE,
              String.format(
                  "Asset is already %s.", ON_LOAN.equals(newStatus) ? "borrowed" : "returned"))
          .build();
    }

The rest is the process of changing the status at the time of borrowing / returning.

    JsonObjectBuilder newDataBuilder = Json.createObjectBuilder();
    if (newStatus.equals(ON_LOAN)) {
      newDataBuilder.add(HOLDER_ID, holderId).add(TIMESTAMP, timestamp).add(STATUS, newStatus);
    } else if (newStatus.equals(IN_STOCK)) {
      if (!data.containsKey(HOLDER_ID)) {
        return Json.createObjectBuilder()
            .add(RESULT, FAILURE)
            .add(MESSAGE, "Can not return asset without holderId")
            .build();
      }
      if (!data.getString(HOLDER_ID).equals(holderId)) {
        return Json.createObjectBuilder()
            .add(RESULT, FAILURE)
            .add(MESSAGE, "Can not return asset borrowed by another user")
            .build();
      }
      newDataBuilder.add(TIMESTAMP, timestamp).add(STATUS, newStatus);
    }
    ledger.put(holderId + "-" + id, newDataBuilder.build());

Here, the check is performed using holderId, and a warning message is also issued if the user is different.

AssetHistoryContract The final AssetHistoryContract is a process that returns the loan history so far by specifying the asset ID. The source will be here I will.

It is a specification that returns an error when there is no history.

    AssetFilter filter = new AssetFilter(holderId + "-" + id);
    List<Asset> borrowingHistory = ledger.scan(filter);

    if (borrowingHistory.isEmpty()) {
      return Json.createObjectBuilder()
          .add(RESULT, FAILURE)
          .add(MESSAGE, "This asset is not registered")
          .build();
    }

If there is a history, it will be returned as an array.

    JsonArrayBuilder builder = Json.createArrayBuilder();
    JsonObjectBuilder borrowingRecordBuilder = Json.createObjectBuilder();
    for (Asset history : borrowingHistory) {
      borrowingRecordBuilder
          .add(TIMESTAMP, history.data().getJsonNumber(TIMESTAMP).longValue())
          .add(STATUS, history.data().getString(STATUS))
          .add(AGE, history.age());

      if (history.data().containsKey(HOLDER_ID)) {
        borrowingRecordBuilder.add(HOLDER_ID, history.data().getString(HOLDER_ID));
      }
      builder.add(borrowingRecordBuilder.build());
    }

Summary

So it was a commentary on how to write smart contracts and the source of asset management applications. From the next time, I would like to touch on how to use the emulator using this source.

Recommended Posts

I investigated how to write smart contracts for distributed ledger Scalar DLT
I investigated the recently talked about distributed ledger Scalar DLT (smart contract execution)
I tried to touch the asset management application using the emulator of the distributed ledger Scalar DLT
How to write a unit test for Spring Boot 2
How to write Rails
How to write dockerfile
How to write docker-compose
How to write Mockito
How to write migrationfile
Investigated how to call services with Watson SDK for Java
How to write good code
Bit Tetris (how to write)
How to write java comments
[Refactoring] How to write routing
Great poor (how to write)
[Note] How to write Dockerfile/docker-compose.yml
How to write Junit 5 organized
How to write Rails validation
How to write Rails seed
[Ruby] How to write blocks
How to write Rails routing
[RSpec on Rails] How to write test code for beginners by beginners