[JAVA] J'ai essayé de savoir comment rédiger un contrat intelligent pour le grand livre distribué Scalar DLT.

Cette fois, je voudrais expliquer comment rédiger un contrat intelligent et des exemples d'applications qui seront utilisées dans les prochaines fois et les suivantes. Les articles jusqu'à la dernière fois sont les suivants.

Application de gestion d'actifs

Bien qu'il s'agisse d'un exemple d'application, j'aimerais utiliser ce qui suit, que j'ai brièvement présenté précédemment.

Cette application gère les actifs très simplement et les 6 contrats intelligents suivants sont préparés.

Nom du contrat une fonction
AddTypeContract Enregistrer le type d'actif
ListTypeContract Obtenez une liste des types d'actifs
AddAssetContract Enregistrer les actifs
ListContract Obtenez une liste d'actifs
StateChangeContract Changer l'état d'un actif
AssetHistoryContract Obtenez l'historique d'utilisation des ressources

Vous pouvez utiliser ce contrat intelligent pour enregistrer, afficher, emprunter et restituer des actifs. Je voudrais présenter le contenu de chaque source immédiatement, mais avant cela, expliquons les bases des contrats intelligents.

Comment rédiger un contrat intelligent

Les contrats intelligents Scalar DL peuvent être écrits en Java pur. Cependant, il n'est pas bon de l'écrire comme vous le souhaitez, je vais donc vous présenter les lignes directrices.

Il existe une limitation selon laquelle la bibliothèque externe ne peut pas être utilisée, mais cela ne posera pas de problème car il est uniquement nécessaire que le côté du contrat intelligent effectue un tel traitement unique. Jetons également un coup d'œil au code de base.

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
  }
}

Seule la méthode ʻinvoke () est décrite, mais comme c'est la partie qui est réellement appelée, elle doit être implémentée. L'argument ledger est un objet permettant d'accéder aux données de type clé-valeur stockées dans Scalar DB. Les données peuvent être ajoutées / mises à jour / lues / acquises. ʻArgument est un paramètre passé au contrat intelligent et est au format JSON. Il est recommandé de définir une propriété de chaîne appelée «nonce» sur cet objet. On dit qu'une valeur unique au monde est bonne, donc je pense qu'il vaut mieux l'utiliser autour de l'UUID. propriété est le jeu de paramètres lors du déploiement du contrat intelligent. Cela ne peut pas être changé plus tard.

Expliquons un peu la méthode du «grand livre». Le premier est ʻOptional get (String key) `, qui est utilisé pour obtenir la valeur actuelle de la clé spécifiée.

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

L'âge qui peut être obtenu ici est le nombre de fois que les données ont été mises à jour, et est automatiquement compté à partir de 0. Ensuite, il s'agit de void put (String key, JsonObject value), qui enregistre un objet JSON avec la clé spécifiée.

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());

L'écriture implicite n'est pas autorisée pour «put», alors assurez-vous de «get» avant d'écrire. Ensuite, il s'agit de Analyse de la liste <Asset> (clé de chaîne). Il peut obtenir l'historique des modifications de la clé spécifiée.

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();
}

Soudain, ʻAssetFilter` est sorti, mais c'est une classe qui vous permet de spécifier la plage de l'historique.

//de 5 à 9 ans
new AssetFilter(id).withStartVersion(5, true).withEndVersion(10, false);
//âge de 5 ans au plus tard
new AssetFilter(id).withStartVersion(5, true);

Et jusqu'à présent, c'est la partie de base. Cela fait un peu long, mais si vous gardez cela à l'esprit, vous pouvez facilement sentir la description des contrats intelligents.

Contrat intelligent pour l'application de gestion d'actifs

AddTypeContract AddTypeContract est le processus d'enregistrement du type d'actif. La source entière est ici Vous pouvez le vérifier à, donc ici je voudrais extraire et expliquer les points.

  public static final String HOLDER_ID = "holderId";

Le premier est une propriété importante dans cette application de gestion d'actifs. Chaque contrat intelligent a une propriété appelée holderId, qui ne peut être définie qu'au moment du déploiement.

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

Lorsque le contrat intelligent est appelé, la propriété est vérifiée comme décrit ci-dessus pour empêcher l'exécution par quiconque autre que l'utilisateur déployé. Le reste est simple et le même nom est vérifié.

    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());

Et enfin, le type d'actif est ajouté à la valeur de la clé de holderId +" - "+ TYPE.

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

ListTypeContract ListTypeContract est un processus qui renvoie une liste de types d'actifs enregistrés. La source est ici Je vais.

Puisque le traitement de «holderId» est courant, si vous omettez l'explication, le traitement est simple car il ne s'agit que d'une acquisition de données.

    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();

S'il n'y a pas de données, une erreur est renvoyée et s'il y a des données, elles sont renvoyées sous forme de tableau.

AddAssetContract Le AddAssetContract suivant est similaire à AddTypeContract, mais avec le processus d'enregistrement d'un actif par type. La source est ici.

C'est fondamentalement le même que AddTypeContract, mais il a de nombreux arguments requis.

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

Je n'expliquerai pas le type et la vérification de l'existence des actifs, mais après cela, vous enregistrez le statut et le nom du prêt.

    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 Comme vous pouvez l'imaginer, ListContract est fondamentalement le même que ListTypeContract. C'est ici Sera.

Par rapport à ListTypeContract, l'argument «type» est obligatoire et renvoie une liste d'actifs du «type» spécifié. La raison pour laquelle le traitement est un peu plus long est que le statut de prêt est acquis par un autre traitement.

    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 Le StateChangeContract suivant est la partie principale de cette application. Responsable de la gestion du statut des emprunts / retours. La source est ici.

Le processus est simple même s'il est appelé main, et si le même statut que celui en cours est spécifié, une erreur est renvoyée.

    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();
    }

Le reste est le processus de changement de statut au moment de l'emprunt / du retour.

    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());

Ici, la vérification est effectuée à l'aide de holderId, et un message d'avertissement est également émis si l'utilisateur est différent.

AssetHistoryContract Le contrat AssetHistoryContract final est un processus qui renvoie l'historique du prêt jusqu'à présent en spécifiant l'ID de l'actif. La source sera ici Je vais.

C'est une spécification qui renvoie une erreur lorsqu'il n'y a pas d'historique.

    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();
    }

S'il existe un historique, il sera renvoyé sous forme de tableau.

    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());
    }

Résumé

C'était donc une explication sur la façon de rédiger un contrat intelligent et la source d'une application de gestion d'actifs. À partir de la prochaine fois, j'aimerais aborder la façon d'utiliser l'émulateur en utilisant cette source.

Recommended Posts

J'ai essayé de savoir comment rédiger un contrat intelligent pour le grand livre distribué Scalar DLT.
J'ai enquêté sur le registre distribué Scalar DLT (Smart Contract Execution) récemment discuté
J'ai essayé de toucher l'application de gestion d'actifs en utilisant l'émulateur du grand livre distribué Scalar DLT
Comment écrire un test unitaire pour Spring Boot 2
Comment écrire des rails
Comment écrire docker-compose
Comment écrire Mockito
Comment écrire un fichier de migration
A étudié comment appeler des services avec Watson SDK pour Java
Comment écrire du bon code
Comment rédiger un commentaire java
[Refactoring] Comment écrire le routage
Comment écrire Junit 5 organisé
Comment écrire des graines de Rails
Comment écrire le routage Rails
[R Spec on Rails] Comment écrire du code de test pour les débutants par les débutants