Smart Contracts mit .NET Core

Hyperledger Fabric – smarte Programmiersprache für smarte Verträge
19
Nov

Smart Contracts mit .NET Core

Hyperledger Fabric, eine von The Linux Foundation unterstütze Blockchain, erlaubt das Entwickeln von Smart Contracts in Go, Node.js und Java. Mit einem experimentellen Paket lassen sich die Smart Contracts auch in .NET entwickeln. Dank .NET Core können die Smart Contracts via Docker – dem Deploymentmodell von Hyperledger Fabric – gestartet werden. In diesem Artikel wollen wir die Entwicklung von Smart Contracts mit C# genauer beleuchten.

Hyperledger Fabric ist eine unter dem Dachverband Hyperledger mit Go entwickelte Open Source Blockchain. Hyperledger selbst wurde Ende 2015 von The Linux Foundation gegründet. Unter dem Dachverband werden Open Source Blockchains und dazu passende Tools entwickelt. Große Unternehmen wie Intel, IBM, Cisco oder Red Hat unterstützen Hyperledger. Dabei wird nicht nur der Ansatz einer Blockchain, sondern vielmehr verschiedener Blockchain-Plattformen verfolgt.

Genauer handelt es sich bei Hyperledger Fabric (kurz: Fabric) um eine Technologie für Permissioned Blockchains bzw. private Blockchains – also eine Blockchain, die nicht wie Ethereum oder Bitcoin für alle öffentlich, sondern nur bestimmten Teilnehmern zugänglich ist. Dadurch können wir bei diesen Arten von Netzwerken das klassische Mining (Proof-of-Work, PoW) vernachlässigen. Vielmehr kommt hier der Konsens über Proof-of-Authority (PoA) (Kasten: „Proof-of-Authority“) zum Tragen.

Proof-of-Authority (PoA)

Entgegen dem klassischen Mining (Proof-of-Work), das darauf beruht, dass ein Miner ein kryptografisches Rätsel in Form einer Hashberechnung löst, existieren bei Proof-of-Authority (PoA) sogenannte Validator Nodes (oder auch Authorities, als eher physische Entität), denen die Erzeugung von neuen Blöcken erlaubt ist. Wenn die Mehrzahl von Validator Nodes in einem Netzwerk der Erzeugung des neuen Blocks zustimmen, wird dieser ein permanenter Teil des Netzwerks. Vorteil hierbei ist, dass keine CPU-Zeit zur Lösung des kryptografischen Rätsels benötigt wird, eine bessere Performanz erreicht wird und dadurch eine höhere Transaktionsgeschwindigkeit erzielt werden kann. Daher eignet sich PoA für das Enterprise-Umfeld.

Anstelle einer Domänensprache bietet Fabric die Entwicklung von Smart Contracts – oder auch Chaincode, wie das im Hyperledger-Umfeld genannt wird – in den Sprachen Java, Go und JavaScript (via Node.js) an, experimentell kann auch .NET verwendet werden. Folgend werden wir einen Chaincode mit C# entwickeln, der das Standardbeispiel FabCar von Hyperledger implementiert. FabCar dient der Zuordnung von Autos zu ihrem Besitzer. Schreibend erlaubt der Chaincode sowohl das Anlegen eines neuen Eintrags (z. B.: Hat jemand ein neues Auto gekauft?) als auch den Wechsel des Besitzers. Lesend kann die Information über ein bestimmtes Auto oder eine Liste aller Autos erlangt werden.

 

Der .NET-Framework & C# Track auf der BASTA! Spring 2019

 

Vorbereitungen für das Netzwerk

 

Bevor wir mit der Entwicklung starten können, benötigen wir ein Hyperledger-Fabric-Netzwerk. Hierbei bietet sich das Chaincode-Developer-Network (Dev-Netzwerk) an. Normalerweise muss der Chaincode bei einer Änderung deployt und danach instanziiert werden. Bei diesem Vorgang wird von einem Fabric Peer ein Docker-Container mit dem Chaincode gestartet. Dieser Vorgang nimmt durchaus einige Zeit in Anspruch, was die Entwicklung sehr langsam machen würde. Durch das Dev-Netzwerk können wir diesen Vorgang deutlich verkürzen, da wir den Chaincode außerhalb der Docker-Umgebung von Fabric starten.

Auf der Fabric Website Prerequisites ist beschrieben, welche Software benötigt wird. Windows-Nutzer sollten auf jeden Fall einen Blick auf die Seite werfen, da hier noch einige zusätzliche Software installiert werden muss. Linux- und Mac-User benötigen nur Folgendes:

  • Docker (>= 17.06.2)
  • .NET Core SDK
  • IDE nach Wahl für .NET Core (bspw. VS Code oder JetBrains Rider)

 

Ist die Software installiert, muss das Repository fabric-samples geklont werden. Mit einer Kommandozeile innerhalb von fabric_samples muss folgender Befehl ausgeführt werden:

curl -sSL http://bit.ly/2ysbOFE | bash -s 1.2.0

Dieser Befehl lädt alle benötigen Docker-Images und Binaries für die Zielplattform für die (zum Zeitpunkt des Schreibens aktuelle) Hyperleder-Fabric-Version 1.2.0 herunter.

Wurde der Befehl erfolgreich ausgeführt, wechseln wir in das Verzeichnis chaincode-docker-devmode und öffnen die Datei docker-compose-simple.yaml. Dort finden wir den Eintrag peer: auf Root-Ebene und fügen unter ports: eine weitere Zeile mit dem Inhalt 7052:7052 hinzu. Hierbei bitte auf die Einrückung achten, sodass es genau wie die anderen Ports aussieht. Die fertige Eintragung sollte daher in Listing 1 aussehen.

Listing 1

peer:
  # Zur besseren Lesbarkeit wurden die anderen Attribute entfernt
  ports:
    - 7051:7051
    - 7052:7052
    - 7053:7053

Dieser Port öffnet die Schnittstelle für unseren Chaincode, sodass er außerhalb der Docker-Umgebung gestartet und verbunden werden kann. Mit folgendem Befehl auf der Kommandozeile können wir das Netzwerk starten:

docker-compose -f docker-compose-simple.yaml up

Nach einer kurzen Initialisierungsphase sollte das Fabric-Dev-Netzwerk fehlerfrei (sprich ohne Fehlermeldungen in der Logausgabe) gestartet sein. Jetzt können wir mit der Entwicklung von unserem Chaincode beginnen.

 

Entwicklungsmodelle

Die aktuelle Version der Bibliothek zur Entwicklung von Chaincode bietet zwei Entwicklungsmodelle an. Zum einen ein Low-Level-Modell (Chaincode API), das dem Entwickler die maximale Freiheit zur Entwicklung bietet, aber auch die größte Verantwortung zur fehlerfreien Umsetzung. Zum anderen ein High-Level-Modell (Contract API), das dem Entwickler erlaubt, sich auf die Umsetzung seines Business-Use-Cases zu konzentrieren und etwas Verantwortung an das Framework abzugeben. Innerhalb des Artikels werden wir nur mit dem Contract API – also dem High-Level-Modell – arbeiten.

Zunächst erstellen wir ein neues .NET-Core-Konsolen-Projekt, entweder über die Templates in JetBrains Rider oder via dotnet new console in einer Kommandozeile. Danach öffnen wir die dazugehörige *.csproj-Datei, finden die erste und fügen dort den Eintrag 7.3 hinzu. Falls dieser Eintrag bereits existiert, können wir ihn einfach überschreiben. Mit dem Eintrag stellen wir unser Projekt auf die aktuellste Sprachversion von C# um, um später eine asynchrone Main()-Methode verwenden zu können.

Wieder auf der Kommandozeile führen wir folgenden Befehl zur Installation der Bibliothek zur Chaincode-Entwicklung aus. Es lohnt sich, ein Blick in die NuGet Gallery [6] zu werfen, um die aktuellste Version zu installieren:

dotnet add package Thinktecture.HyperledgerFabric.Chaincode --version 1.0.0-prerelease.78

 

FabCar: Basisklassen

 

Im nächsten Schritt legen wir eine neue Klasse FabCar an und lassen sie von der Klasse ContractBase erben (Listing 2). Diese Klasse ContractBase stellt die Basisklasse für alle Contract API basierten Chaincodes dar. Ihr Pendant für die Low-Level-Programmierung wäre das Interface IChaincode. Alle Klassen, die von ContractBase erben, nennen wir Contract (nicht zu verwechseln mit Smart Contract). Ein Chaincode (der eigentliche Smart Contract) kann aus mehreren Contracts bestehen. Hierbei würde jeder Contract eine bestimmte Funktionalität des gesamten Chaincodes zur Verfügung stellen – sie dienen daher einer Art Gruppierung.

Listing 2: Die Klasse „FabCar“

using Thinktecture.HyperledgerFabric.Chaincode.Contract;

public FabCar(): base("FabCar")
{
}

Der String FabCar beim Aufruf von base() ist der Namespace, der später zum Aufruf unseres Chaincode benötigt wird. Übrigens, die Klasse integriert sich später in den durch .NET Core bereitgestellten Dependency Injection Container. Es kann daher wie gewohnt die Dependency Injection benutzt werden, bspw. könnten wir einen ILogger hinzufügen, wenn wir in unserem Contract loggen möchten.

Wie eingangs erwähnt, wollen wir mit FabCar Autos mit ihren Besitzern speichern und abrufen. Dazu erstellen wir mit dem Code aus Listing 3 eine neue Klasse Car.

Listing 3: Die Klasse „Car“

  public class Car
{
  public Car()
  {
  }

  public Car(string make, string model, string color, string owner)
  {
    Make = make;
    Model = model;
    Color = color;
    Owner = owner;
  }

  public string Make { get; set; }
  public string Model { get; set; }
  public string Color { get; set; }
  public string Owner { get; set; }

  public override string ToString()
  {
    return $"{Make} {Model} in {Color} with owner {Owner}";
  }
}

Die Klasse Car hat einige Eigenschaften wie den Hersteller (Make), das Modell (Model), die Farbe (Color) und den aktuellen Besitzer (Owner). Hier können gerne nach Belieben weitere Eigenschaft hinzugefügt werden, bspw. die Pferdestärken oder das Gewicht. Der parameterlose Konstruktor wird für die (De-)Serialisierung von und nach JSON benötigt.

Als nächstes legen wir alle Methoden aus Listing 4 an, die wir nach und nach implementieren werden. Beim späteren Start des Chaincodes werden via Reflection alle Methoden mit folgendem Schema gesucht:

public [async] Task MethodName(IContractContext context [, string param1]*)

Schnittstellenbedingt werden alle Parameter, die der Methode übergeben werden, als String übergeben. Ein Parsen in den richtigen Datentyp muss die Methode selbst vornehmen. Die gefundenen Methoden werden öffentlich zur Verfügung gestellt und können vom Blockchain-Netzwerk aufgerufen werden.

Listing 4: Methodenrümpfe des Chaincodes

{
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html"
        ],
        "versionedFiles": [
          "/*.bundle.css",
          "/*.bundle.js",
          "/*.chunk.js"
        ]
      }
    },
    {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**"
        ]
      }
    }
  ],
  "dataGroups": [
    {
      "name": "pokemon",
      "urls": [
        "https://pokemon.co/*"
      ],
      "cacheConfig": {
        "strategy": "performance"
      }
    }
  ]
}

 

FabCar: „Init“-Methode

Starten wir mit der Implementierung der Init()-Methode. Sie wird bei der Instanziierung unserer Chaincodes aufgerufen. Wenn wir nichts zu initialisieren hätten, müssten wir diese Methode nicht implementieren. Listing 5 zeigt die Implementierung unserer Methode, die einige Autos mit ihren Besitzern initialisiert.

Listing 5: Die Methode Init()

public async Task Init(IContractContext context)
{
  var cars = new List
  {
    new Car("Toyota", "Prius", "blue", "Tomoko"),
    new Car("Ford", "Mustang", "red", "Brad"),
    new Car("Hyundai", "Tucson", "green", "Jin Soo"),
    new Car("Volkswagen", "Passat", "yellow", "Max"),
    new Car("Tesla", "S", "black", "Michael"),
    new Car("Peugeot", "205", "purple", "Michel")
  };

  for (var index = 0; index < cars.Count; index++)
  {
    var car = cars[index];
    await context.Stub.PutStateJson($"CAR{index}", car);
  }

  return ByteString.Empty;
}

Schauen wir uns im Detail an, was genau hier passiert. Zunächst erstellen wir eine Liste von Car mit einigen Beispielinhalten. Danach iterieren wir über die Liste und rufen context.Stub.PutStateJson() auf. Die Variable context, die als Parameter in die Methode gereicht wird, beinhaltet den aktuellen Ausführungskontext unserer Contracts. In ihr befindet sich der Stub, der ein API zwischen Chaincode und dem Fabric Peer bereitstellt. Mit Hilfe des Stubs können Daten im Asset Store (Kasten: „Asset Store“) gespeichert, ausgelesen, geändert und gelöscht werden. Und genau das machen wir mit PutStateJson(): Wir speichern unter dem Schlüssel CAR0 einen JSON-String mit dem dazugehörigen Auto. Konnten wir alle Einträge erfolgreich speichern, geben wir einen leeren ByteString zurück. Falls zwischendurch ein Fehler auftritt, wird dieser durch die Bibliothek verarbeitet.

Asset Store

Bei Fabric ist der Asset Store oder auch State Database, also der aktuelle Zustand unserer Daten der Blockchain, zunächst eine LevelDB, ein einfacher Key-Value-Speicher. Dieser erlaubt es nicht, komplexere Query-Abfragen (ähnlich SQL) entgegen zu nehmen. Für diesen Fall kann LevelDB gegen eine CouchDB ausgewechselt werden, die auf Basis von JSON-Strukturen komplexe Query-Abfragen erlaubt. Weitere Informationen hierzu sind in der Dokumentation zu finden.

 

FabCar: „CreateCar“-Methode

Als nächstes implementieren wir die Methode CreateCar() mit dem Inhalt aus Listing 6.

Listing 6: Die Methode „CreateCar()“

public async Task CreateCar(IContractContext context, string carNumber, string make, string model, string color, string owner)
{
  var car = new Car(make, model, color, owner);

  await context.Stub.PutStateJson(carNumber, car);

  return ByteString.Empty;
}

Hier sehen wir, dass die Methode zusätzliche String-Parameter übergeben bekommt. Nämlich genau die, die wir benötigen, um ein neues Auto mit seinem Besitzer zu erstellen. Hier sei angemerkt, dass wir innerhalb der Methode keine Prüfung vornehmen, ob dieses Auto bereits existiert. Das kann gerne als kleine Hausaufgabe selbst implementiert werden. Die Methode ist daher recht einfach gehalten: Neues Auto erstellen, über PutStateJson() speichern und bei Erfolg einen leeren ByteString zurückgeben.

FabCar: „ChangeCarOwner“-Methode

Die Methode ChangeCarOwner() veranlasst einen Besitzerwechsel eines Autos. In Listing 7 ist die Implementierung zu finden.

Listing 7: Die Methode „ChangeCarOwner()“

public async Task ChangeCarOwner(IContractContext context, string carNumber, string owner)
{
  var car = await context.Stub.GetStateJson(carNumber);
  car.Owner = owner;

  await context.Stub.PutStateJson(carNumber, car);

  return ByteString.Empty;
}

Die nächste zu implementierende Methode ist QueryCar(). Sie ruft einen einzelnen Wagen mit all seinen Eigenschaften aus dem Asset Store auf und gibt ihn an den Aufrufer zurück. Die Implementierung ist in Listing 8 zu finden.

FabCar: „QueryCar“-Methode

Die Methode ChangeCarOwner() veranlasst einen Besitzerwechsel eines Autos. In Listing 7 ist die Implementierung zu finden.

Listing 8: Die Methode „QueryCar()“

public async Task QueryCar(IContractContext context, string carNumber)
{
  var carBytes = await context.Stub.GetState(carNumber);

  if (carBytes == null || carBytes.Length == 0) throw new Exception($"Car {carNumber} does not exist.");

  return carBytes;
}

Hier ist anzumerken, dass wir die Methode GetState() verwenden, die uns einen ByteString liefert, und nicht GetStateJson(). Die Methode ist nicht am deserialisierten Zustand des Autos interessiert. Sie prüft daher nur, ob ein Ergebnis vom Asset Store geladen werden konnte (sprich: es ist weder null noch hat der ByteString eine Länge gleich 0). Falls ja, gibt sie das Ergebnis an den Aufrufer zurück. Möchte man die Ausführung der Methode abbrechen (bspw. bei falschen Eingabeparametern oder wie im Beispiel: Auto nicht gefunden) werfen wir eine Exception. Die Bibliothek übersetzt jede geworfene Exception in einen Fehlerstatus und gibt diesen an den Aufrufer zurück, sodass die Ausführung des Chaincodes nicht abgebrochen wird.

FabCar: „QueryAllCars“-Methode

Zu guter Letzt wollen wir die Methode QueryAllCars() implementieren. Sie soll alle gespeicherten Autos abrufen. Die Implementierung ist in Listing 9 zu finden.

Listing 9: Die Methode QueryAllCars

public async Task QueryAllCars(IContractContext context)
{
  var startKey = "CAR0";
  var endKey = "CAR999";

  var iterator = await context.Stub.GetStateByRange(startKey, endKey);

  var result = new List();

  while (true)
  {
    var iterationResult = await iterator.Next();

    if (iterationResult.Value != null && iterationResult.Value.Value.Length > 0)
    {
      var queryResult = new CarQueryResult
      {
        Key = iterationResult.Value.Key
      };

      try
      {
        queryResult.Record = JsonConvert.DeserializeObject(iterationResult.Value.Value.ToStringUtf8());
                
        result.Add(queryResult);
      }
      catch (Exception ex)
      {
        // Error loggen
      }
    }

    if (iterationResult.Done)
    {
      await iterator.Close();
      return JsonConvert.SerializeObject(result).ToByteString();
    }
  }
}

public class CarQueryResult
{
  public string Key { get; set; }
  public Car Record { get; set; }
}

Wie erwähnt handelt es sich beim Asset Store per Standard um eine LevelDB, einen einfachen Key-Value-Speicher. Dieser erlaubt keine komplexeren Abfragen (Kasten: „Asset Store“). Um dennoch alle Autos abzufragen, iterieren wir selbst über den Asset Store. Hierzu definieren wir einen Start- (CAR0) und Endpunkt (CAR999). Das bedeutet, dass wir versuchen, die ersten 1 000 Autos aus dem Asset Store zu laden. Wer möchte, kann diese Parameter auch als Methodenparameter übergeben. Via GetStateByRange() erzeugen wir uns einen Iterator auf den Asset Store innerhalb unserer Grenzen.

Über eine while(true)-Schleife wird eine Iteration über den Asset Store gestartet. Via iterator.Next() erhalten wir das nächste mögliche Ergebnis aus dem Store. Wir prüfen als nächstes, ob wir ein Ergebnis haben und die Länge größer 0 ist. Ist das der Fall, versuchen wir den erhaltenen ByteString via StringToUtf8() in seine UTF-8-Repräsentation umzuwandeln und danach zu deserialisieren. Falls hierbei ein Fehler auftritt, wird dieser in unserem Beispiel ignoriert.

Zum Schluss prüfen wir, ob die Iteration abgeschlossen ist. Wenn ja, schließen wir den Iterator via iterator.Done() und geben unsere Liste an den Aufrufer zurück. Damit ist die Entwicklung unseres Contract abgeschlossen.

Anpassung der „Main“-Methode

Zu Beginn haben wir ein neues Konsolenprojekt erstellt, was uns eine Datei Program.cs mit einer Main()-Methode generiert hat. Diese passen wir gemäß Listing 10 an.

Listing 10: Die Methode „Main()“

class Program
{
  static async Task Main(string[] args)
  {
    using (var provider = ChaincodeProviderConfiguration.ConfigureWithContracts(args))
    {
      var shim = provider.GetRequiredService();
      await shim.Start();
    }
  }
}

Um den Chaincode zu starten, benötigen wir ChaincodeProviderConfiguration.ConfigureWithContracts(args). Hinter ChaincodeProviderConfiguration verbirgt sich der Aufbau der Dependency Injection des Chaincodes. Mit ConfigureWithContracts() teilen wir dem Container mit, dass wir gerne den Contract FabCar in unserem Chaincode nutzen wollen. Via Methodenüberladung können bis zu vier Contracts über eine generische Methode hinzugefügt werden. Alternativ kann der Methode auch ein Array aller Contracts übergeben werden. Damit sind auch mehr als vier Contracts pro Chaincode möglich.

Aus unserem Provider lassen wir uns eine Instanz von Shim via GetRequiredService() erzeugen. Der Shim ist die Schnittstelle zwischen dem Fabric Peer und unserem Chaincode. Er übernimmt jegliche Nachrichten- und Fehlerbehandlungen. Zu guter Letzt starten wir via shim.Start() das Kommunikationsprotokoll zwischen Chaincode und Fabric Peer. Damit sind alle Codearbeiten abgeschlossen.

It compiles, ship it!

Der letzte Schritt ist das Deployment bzw. die Instanziierung des Chaincode. Hierzu benötigen wir ein neues Kommandozeilenfenster, in dem wir folgenden Befehl ausführen:

docker exec -it cli bash

Dieser Befehl verbindet uns zum Fabric CLI, das uns die Kommunikation mit einem Fabric Peer erlaubt. Wie eingangs erwähnt, wird der Chaincode normalerweise in einem Docker-Container gestartet, namentlich innerhalb von fabric-ccenv (ccenv: Chaincode Environment). Dieser bietet aktuell Unterstützung für Go und Node.js Chaincodes, aber noch nicht für .NET Core. Zumal wollen wir nicht zur Entwicklungszeit den Chaincode in einem Docker-Container starten. Dennoch müssen wir ein normales Deployment anstoßen, damit unser Chaincode erkannt wird. Innerhalb der eben geöffneten Kommandozeile, die mit dem Fabric CLI verbunden ist, tippen wir folgende Befehle ein:

mkdir /opt/gopath/src/chaincodedev/chaincode/netcore

echo "dev" > /opt/gopath/src/chaincodedev/chaincode/netcore/dev

peer chaincode install -p /opt/gopath/src/chaincodedev/chaincode/netcore -n fabcar -v 0 -l node

Die ersten beiden Befehle erstellen einen neuen Ordner netcore mit einer Datei namens dev und dem Inhalt dev. Das dient als reiner Fake-Chaincode, damit Fabric etwas deployen kann, denn ein leerer Ordner/Chaincode kann nicht deployed werden. Der letzte Befehl installiert bzw. deployt den Chaincode. Via peer chaincode install teilen wir mit, dass wir gerne neuen Chaincode installieren möchten. -p mit Pfadangabe gibt an, wo unser Fake-Chaincode liegt, -n gibt den Namen, -v die Version und -l die Sprache des Chaincodes an. Hier merkt man stark, wie experimentell unser .NET-Beispiel noch ist, da wir hier tatsächlich node als Sprache angeben. Das führt prinzipiell nur dazu, dass Fabric den angegebenen Ordner in den Peer kopiert. Damit ist unser Fake-Chaincode installiert. Würden wir hier ein reales Deployment anstreben, würden wir selbstverständlich den echten Code bzw. dessen Binary kopieren. Der Fake-Chaincode dient nur zur Entwicklungszeit. Auch wird, sobald Fabric offiziell .NET unterstützt, die Angabe von node in netcore geändert.

Jetzt ist es an der Zeit, unseren Chaincode zu starten. Wichtig ist, dass folgende Umgebungsvariablen gesetzt sind:

  • CORE_PEER_ADDRESS=localhost:7052
  • CORE_CHAINCODE_ID_NAME=fabcar:0

CORE_PEER_ADDRESS gibt an, mit welchem Peer sich unser Chaincode für die Kommunikation verbinden soll. CORE_CHAINCODE_ID_NAME ist die Identifikation unseres Chaincode. Mit fabcar:0 zeigen wir hier auf den eben installierten fabcar mit Version 0. Nach dem Start sollte innerhalb kurzer Zeit im Log des Chaincodes der Eintrag Successfully established communication with peer node erscheinen. Ist das der Fall, hat sich der Chaincode erfolgreich zum Peer verbunden, und wir können diesen jetzt instanziieren. Der Befehl hierzu, der wieder innerhalb des Fabric CLI eingegeben werden muss, lautet wie folgt:

peer chaincode instantiate -n fabcar -v 0 -c '{"Args": ["FabCar.Init"]}' -C myc

Mit den Argumenten -n und -v sprechen wir wieder unseren Chaincode an. -c ist die sogenannte Constructor-Message, ein String mit JSON-Format, der unserem Chaincode gesendet, durch den Shim geparst und dem Contract zur Verfügung gestellt wird. Die Argumente sind eine Liste von Strings. Das erste Argument ist immer die Methode, die im Chaincode aufgerufen werden soll. Es setzt sich zusammen aus dem Namespace (FabCar) und dem Methodennamen (Init) getrennt durch einen Punkt. Den Namespace hatten wir zuvor im Konstruktor der Klasse FabCar gesetzt, und Init meint die tatsächliche Methode Init() der FabCar-Klasse. Wichtig: Die Bezeichnung beachtet Groß- und Kleinschreibung. Das letzte Argument -C bestimmt den Fabric Channel.

Ab diesem Zeitpunkt können wir unseren Chaincode nach Belieben stoppen, Code ändern und neu starten, ohne dass wir ein neues Deployment oder eine neue Instanziierung durchführen müssen, was zu einer erheblichen Entwicklungsgeschwindigkeit beiträgt.

 

Chaincode benutzen

Nachdem unser Chaincode läuft, wollen wir ihn benutzen. Alle folgenden Befehle werden wieder im Fabric CLI eingetippt. Wenn wir uns beispielsweise das Auto mit der Nummer CAR2 anzeigen lassen wollen, benutzen wir folgenden Befehl:

peer chaincode query -n fabcar -c '{"Args":["FabCar.QueryCar","CAR2"]}' -C myc

query gibt an, dass wir nur lesend auf das Netzwerk zugreifen und die Methode FabCar.QueryCar mit dem Parameter CAR2 aufrufen. Wenn alles erfolgreich durchläuft, erhalten wir folgendes Ergebnis:

{"Make": "Hyundai", "Model": "Tucson", "Color": "green", "Owner": "Jin Soo"}

Super! Allerdings gefällt uns der Wagen von Jin Soo so gut, dass wir ihm diesen abkaufen wollen. Diesen Kauf wollen wir vermerken:

    peer chaincode invoke -n fabcar -c 
'{"Args":["FabCar.ChangeCarOwner","CAR2","Manuel Rauber"]}' -C myc

invoke stellt einen schreibenden Zugriff auf das Netzwerk dar, und zwar soll die Methode FabCar.ChangeCarOwner mit den zwei angegebenen Parameter CAR2 und Manuel Rauber aufgerufen werden. Erhalten wir keine Fehlermeldung bei der Ausführung (denn gemäß Listing 7 geben wir einen leeren ByteString zurück), war der Aufruf erfolgreich. Um das zu prüfen, können wir entweder wieder QueryCar benutzen, oder wir lassen uns alle Einträge ausgeben:

peer chaincode query -n fabcar -c '{"Args":["FabCar.QueryAllCars"]}' -C myc

Als Ergebnis erhalten wir eine serialisierte Liste aller Autos und siehe da: Der grüne Hyundai Tucson gehört fortan dem Autor des Artikels.

 

Fazit

Herzlichen Glückwunsch, wir haben es geschafft! Wir haben soeben das Dev-Netzwerk von Hyperleder Fabric gestartet, unseren ersten experimentellen .NET Chaincode entwickelt, ihn deployt, instanziiert und dessen Methoden aufgerufen. Auch haben wir gesehen, dass wir zwar .NET benutzen können, es aber noch an einigen Ecken und Enden fehlt, damit es sich auch wirklich gut anfühlt (und wir nicht via node deployen müssen). Wer sich hier für den Fortschritt interessiert, kann einen Blick auf den entsprechenden JIRA-Task und das dazugehörige GitHub-Repo werfen. Weitere Informationen zum Chaincode in .NET Core sind auch auf dem Thinktecture-Blog zu finden. Ich wünsche viel Spaß beim weiteren Ausprobieren!

 

Hyperledger Fabric und Smart Contracts auf der BASTA!


Passend zum Thema dieses Artikels finden Sie auf der BASTA! z. B. den Power Workshop „Von Null auf Hundert – Blockchain-Anwendungen mit Hyperledger Fabric“ von Ingo Rammer am Freitag.

Ihr aktueller Zugang zur .NET- und Microsoft-Welt.
Der BASTA! Newsletter:

Behind the Tracks

.NET Framework & C#
Visual Studio, TFS, C# & mehr

Agile & DevOps
Best Practices & mehr

Web Development
Alle Wege führen ins Web

Data Access & Storage
Alles rund um´s Thema Data

HTML5 & JavaScript
Leichtegewichtig entwickeln

User Interface
Alles rund um UI- und UX-Aspekte

Microservices & APIs
Services, die sich über APIs via REST und JavaScript nutzen lassen

Security
Tools und Methoden für sicherere Applikationen

Cloud, Azure, Serverless
Cloud-basierte & Native Apps