Der Markt an Blockchain-Technologien ist hart umkämpft. Seit der Einführung von Bitcoin kam es zu einer Proliferation von Forks, Neuentwicklungen und Innovationen. Doch im Unternehmenskontext ist es selten sinnvoll, interne Geschäftsvorgänge auf öffentlichen Blockchains zu verwalten. Daher etabliert sich im Moment eine neue Nische, die der privaten oder zugangsbeschränkten (permissioned) Blockchains.
Während die meisten öffentlichen Kryptowährungen alle ein recht ähnlichen Peer-to-Peer-Ansatz verfolgen, deren Transaktionen sich grob in Bitcoin- oder Ethereum-ähnlich einteilen lassen, gehen die private Blockchains teilweise radikal andere Wege. In diesem Artikel wollen wir die beiden Platzhirsche Hyperledger Fabric und Corda miteinander vergleichen. Als repräsentatives Fallbeispiel einer öffentlichen Blockchain mit Smart Contracts soll Ethereum dienen.
Basiswissen über Blockchain-Technologien ist zum Verständnis hilfreich. Dazu eignet sich der Blockchain-Podcast mit Stefan Tilkov und Lucas Dohmen hervorragend. Eine Einführung in Corda hat Christian Koller geschrieben.
Installation
Ein Ethereum-Netzwerk wird dadurch aufgespannt, dass eine beliebige Menge von Knoten mit vergleichbarer Konfiguration an beliebigem Ort gestartet werden. Alle Knoten sind gleichberechtigt und können unterschiedliche Implementierungen des Protokolls benutzen. Fabric und Corda hingegen bestehen aus verschiedenen Arten von Knoten, für die es auch bis dato nur jeweils eine Implementierung gibt. Diese Knoten haben unterschiedliche Aufgaben und Berechtigungen.
Für Fabric sind das diese Komponenten:
- Clients: möchten Transaktionen durchführen lassen
- Peers: speichern die Daten der Blockchain; spezielle Peers (sogenannte Endorsers) validieren Transaktionen, führen zugeordneten Chaincode (Smart Contracts) aus, signieren das Resultat
- Ordering Service: Infrastruktur, die signierte Transaktionsergebnisse reihen und zu Blöcken zusammen fassen; meist Kafka und ZooKeeper
- Membership Service Provider: stellen Authentifizierung für Peers und Orderers bereit
Bei Corda besteht eine Installation aus den folgenden Komponenten:
- Notary Service: verhindert “double spending”, überprüft Zeitfenster für Transaktionen und kann optional auch Transaktionen validieren
- Node: eindeutige Identität innerhalb des Netzwerks und dient als Ablaufumgebung für CorDapps (Corda Distributed Application)
- Network Map Service: optionaler Dienst, der für die Adressierung von Nodes zuständig ist
Bei Fabric und Corda sind diese Komponenten aus Betriebssicht jeweils unabhängig voneinander.
Datenmodell
Fabric verwaltet als Zustand des Systems eine Menge von Tripeln: Schlüssel, Wert und Version. Wenn ein Schlüssel mit einem neuen Wert überschrieben wird, dann muss die Version inkrementiert werden. Wenn während einer Transaktion aus einem Schlüssel gelesen wird, der von einer parallelen Transaktion geschrieben wird, dann zählt das Lesen des Schlüssels durch die erste Transaktion als veraltet (stale read). Die Smart Contracts in Fabric haben keinen Zustand, sondern greifen ausschließlich auf die Schlüssel/Wert-Daten zurück. Fabric ist daher vergleichbar mit einer verteilten Key-Value-Datenbank, wobei diese Key-Value-Paare auf den Peers gespeichert werden.
In Corda wird Zustand durch unveränderliche Objekte repräsentiert, die beliebige Daten enthalten können und auf dem Ledger als shared facts gespeichert werden. Soll Zustand verändert werden, so wird ein neue Version des Objekts erzeugt und die alte Version als historisch markiert. Contracts in Corda haben ebenfalls keinen Zustand, sondern greifen nur auf die Daten zu, die durch die Transaktion zur Verfügung gestellt werden. Dies entspricht grob dem UTxO-Modell von Bitcoin.
Smart Contracts & Applikationen
In Fabric werden Smart Contracts als Chaincode bezeichnet. Sie können in einer beliebigen Sprache geschrieben sein und laufen auf den Endorser-Knoten in Docker-Containern. Gängige Sprachen sind Java, JavaScript und Go. Für die Makroarchitektur von Applikationen macht Fabric keine weiteren Vorgaben; diese können in traditionelle Architekturen und Systeme eingegliedert werden.
Bei Corda hingegen handelt es sich um eine Plattform, auf der sogenannte CorDapps betrieben werden können. Dabei handelt es sich um verteilte Anwendungen, die in Java oder Kotlin geschrieben werden und auf der JVM laufen. Smart Contracts sind dabei nur ein Aspekt einer CorDapp. Sie verifizieren Transaktionen und stellen somit sicher, dass die Geschäftsregeln eingehalten werden. Ein weiterer Bestandteil einer CorDapp ist der Flow, der für die Koordination des Ablaufs zwischen den beteiligten Parteien verantworlich ist. Außerdem gehören folgende Bestandteile dazu:
- States repräsentieren Fakten auf dem Ledger. Jeder Knoten besitzt einen Vault, in dem alle relevanten States gespeichert sind.
- Static Web Content wie HTML Seiten, JavaScript Libraries und Assets.
Eine Applikation wird auf eine Node deployed und kann folgende Dienste der Laufzeitumgebung in Anspruch nehmen:
- Identity Service: dient der Ermittlung eines Knoten, mit der kommuniziert werden soll; zudem synchronisiert sich dieser Service mit der Network Map, in der diese Informationen gespeichert sind
- Key Management Service: ermittelt geeignete private Schlüssel um eine Transaktion zu signieren und erzeugt neue Schlüssel, wenn diese in Flow benötigt werden.
- Artemis Messaging Service: ist als AMQP Broker für die zuverlässige Kommunikation verantwortlich ist
- Network Map Service: verwaltet die registrierten Nodes in einem Netzwerk
- DB Services: persistieren Flows und Transaktionen; somit wird ein Neustart einer Anwendung möglich, die dann dort fortgesetzt wird, wo sie gestoppt wurde
- Attachment Service: dient dem Hinzufügen von Dokumenten zu einer Transaktion; die Daten werden persistiert und können mittels eingebauten Webserver gelesen werden.
- Vault Services: speichert noch nicht verarbeiteten State
- Web Server: ein eingebetteter Jetty-Server; produktive Anwendung benutzen aber eher Spring Boot und greifen auf CorDapps mittels RPC-Client zu
Beiden Systemen ist gemein, dass sie General Purpose Languages für Smart Contracts benutzen. Insbesondere kann man dadurch auf Umgebungszustand zugreifen und damit nichtdeterministisches Verhalten erzeugen (z.B. Zufallswerte). Dies steht im starken Kontrast zu Ethereum, wo Operationen nur einen stark kontrollierten Zugriff auf Zustand außerhalb des Ledgers haben, z.B. auf die aktuelle Uhrzeit. Corda versucht dieses Problem durch eine deterministische JVM zu lösen, die aber derzeit noch in der Experimentierphase ist.
Transaktionen
In Ethereum besteht eine Transaktion aus Ziel- und Quelladresse, Betrag und Payload. Anschließend wird diese von allen teilnehmenden Knoten ausgeführt. Konsens wird durch die “ganze Welt” gebildet, was Ethereum den Beinamen World Computer eingebracht hat.
Im Unternehmenskontext ist das im Regelfall nicht erwünscht; Smart Contracts sollten lokal und zur Einsparung von Rechenzeit möglichst nicht redundant ausgeführt werden. Daher sind Transaktionen sowohl bei Fabric als auch Corda komplizierter ausgestaltet.
Fabric
Eine Transaktion in Fabric verfügt über keine formelle Struktur. Im Regelfall kann man sie sich aber als Methodenaufruf (RPC) vorstellen. Die Ausführung folgt einem mehrstufigen Prozess:
-
Ein Client reicht eine Transaktion ein. Dazu muss er einer bestimmten Teilmenge der Peers (welche Teilmenge, ist durch Konfiguration festgelegt) diese Transaktion präsentieren. Das Ergebnis einer Transaktion besteht aus zwei Komponenten:
- eine Liste der gelesenen Schlüssel und ihre Versionen zum Zeitpunkt des Lesens
- eine Liste zu schreibender Schlüssel
Die Peers protokollieren alle Lese- und Schreibvorgänge, führen aber die Schreibvorgänge nicht aus. Lediglich in dem Transaktionsresultat sind die neuen Werte der Schlüssel ersichtlich. Wenn ein Peer dieses Resultat produziert hat, sendet er es signiert zum Client zurück. In diesem Zusammenhang wird dann ein Peer zu einem Endorser.
Der Client muss nun warten, bis er genügend viele validierte Transaktionsresultate gesammelt hat. Für den Client ist es nicht nachvollziehbar, welcher Chaincode gelaufen ist; er erhält nur eine Sicht auf die Ergebnisse. Da der Chaincode als Docker-Image beliebige Operationen durchführen kann, z.B. auch externe Ereignisse abfragen, kann es durchaus sein, dass sich die gesammelten Resultate voneinander unterscheiden. In diesem Fall muss der Client die Resultate verwerfen und die Transaktion neu starten. Eine erfolgreiche Beendigung der Transaktion ist nicht garantiert.
Wenn der Client übereinstimmende Resultate gesammelt hat, dann werden diese Resultate an den Ordering Service geschickt. Dieser sammelt mehrere Transaktionen und bildet daraus einen Block.
Sobald der Block abgeschlossen ist, wird er an die Peers propagiert, die daraufhin ihren internen Zustand aktualisieren, nachdem sie den Block erneut validiert haben. Eine Transaktion wird ungültig, wenn sie veraltete Lesezugriffe durchgeführt hat oder der Client nicht genügend viele Endorsers angefragt hatte. Diese Semantik ähnelt stark dem Konzept des Software Transactional Memory. Sofern die Transaktion ungültig ist, wird sie von den Endorsers verworfen, bleibt aber trotzdem im Block enthalten. Der Client muss die Transaktion neu starten. Auch hierbei ist die erfolgreiche Beendigung nicht garantiert.
Corda
Ähnlich wie in Bitcoin nehmen Transaktionen einen oder mehrere Input-States entgegen (oder auch keine) und erzeugen daraus keine oder mehrere Output-States. Weiterhin enthält sie folgende Komponenten:
- Attachments: Anhänge in einer ZIP-Datei, wie z.B. AGBs, Vertragserläuterungen
- Commands: beschreiben die Absicht der Transaktion, wie z.B. bezahlen, kaufen. Zudem sind die Pulbic Keys der an der Transaktion beteiligten Parteien beigefügt.
- Time Window: gibt die Gültigkeit der Transaktion an
- Signaturen: der beteiligten Parteien an der Transaktion und des Notary Service.
In Corda werden Transaktion nicht global versendet, sondern werden immer zwischen einer definierten Anzahl von Parteien durchgeführt. Für die Coordination dieses Workflows ist der Flow der CorDapp zuständig. Dieser gewährleistet wann, wer und was jemand präsentiert bekommt.
Nachdem das Transaktionsobjekt erzeugt und signiert wurde, wird es durch den implementierten Workflow dem Vertragspartner zugestellt, der seinerseits die Transaktion verifiziert. Sollte das Ergebnis positiv sein, so wird dieser seine Signatur an die Transaktion hängen.
Wenn nun alle beteiligten Parteien die Transaktion singiert haben, wird diese dem Notary Service zugestellt, der folgende Prüfungen durchführt.
- wurde das angegebene Zeitfenster für die Transaktion eingehalten
- ist die Transaktion durch alle beteiligten Parteien signiert worden
- sind von allen beteiligten Parteien die Public Keys vorhanden.
- wurde der Input-State bereits in anderen Transaktionen verwendet (double spend)
- Optional kann der Notary Service auch die Transaktion selbst noch einmal validieren, dazu benötigt dieser aber den vollen Zugriff auf die Daten der Transaktion.
Wenn alle Prüfungen erfolgreich durchgeführt wurden, so signiert der Notary Service diese Transaktion und gibt diese Signatur zurück. Zudem wird der Input-State als historisch gekennzeichnet und somit wird der Output-State der aktuell gütltige State in dem Ledger.
Die folgende Grafik zeigt noch einmal den Verlauf einer Transaktion.
Skalierbarkeit
Fabric versucht eine höhere Skalierbarkeit dadurch zu erreichen, dass die Ausführung von Transaktion und der Commit ihrer Ergebnisse in die Blockchain voneinander entkoppelt sind. Die Ausführung des Chaincodes lässt sich sehr einfach vertikal skalieren, in dem man einen Load Balancer zwischen Client und Validator schalten kann.
Corda erreicht eine höhere Skalierbarkeit dadurch, dass Transaktionen nicht global an alle Teilnehmer des Netzwerkes verteilt werden, sondern nur an die beteiligten Parteien einer Transaktion.
Datenschutz
In Fabric kann man mehrere Blockchains auf der gleichen Infrastruktur betreiben. Untereinander können diese Chains nicht kommunizieren. Separierung wird mittels Topics in Kafka erzielt.
In Corda teilen sich Parteien nur die Daten, die für die beteiligten Parteien vorgesehen sind. Es gibt keinen globalen Ledger, bei dem alle Daten auf allen Nodes gespeichert werden.
Danksagung: Die Autoren bedanken sich bei Christian Koller und Ingo Rammer für Kommentare zur Verbesserung dieses Artikels. Die Grafik einer Transaktion in Fabric basiert auf dem Artikel von Androulaki et al..