Suche
Blockchain demystified: Die am schwierigsten zu verstehende Datenbank der Welt

Blockchain für Java-Entwickler: Wie man eine Blockchain in Java implementiert

Marc van den Bogaard, Roland Kahlert

Der Blockchain-Hype geistert nach wie vor durch die Technologiewelt. Mittlerweile sind auch zahlreiche Banken und Softwareunternehmen darauf aufmerksam geworden und versuchen entsprechende Produkte am Markt zu platzieren. Wer sich allerdings eingehender mit der Technik auseinandersetzt und vor allem deren Nachteile kennen lernt, wird feststellen, dass die als disruptive Technologie angesehene Blockchain eher für spezielle Anwendungszwecke geeignet ist als für die breite Masse.

Die Blockchain ist eine vergleichsweise unzugängliche IT-Technologie. Das hängt zum einen mit den zugrunde liegenden komplexen Algorithmen zusammen, zum anderen mit den vielen verschiedenen Konzepten und Technologien, die sie enthält. Trotzdem ist das Thema aufgrund der direkten Beziehung zu den weit verbreiteten Bitcoins ins Rampenlicht gerückt. In vielen Gesprächen mit unseren Kollegen bestätigte sich allerdings der Eindruck, dass zwar die grundlegenden Ideen der Blockchain verstanden wurden (verteilt, öffentlich, …), konkrete Fragen zu den Vor- und Nachteilen, Alternativen und konkreten Projekteinsatzmöglichkeiten aber nicht beantwortet werden konnten.

Der Grund dafür liegt vor allem in der steilen Lernkurve. Denn bis man die Blockchain grob verstanden hat, vergehen im Schnitt einige Tage, in denen man sich intensiv mit dem Thema auseinandersetzen muss. Dieses mangelnde Wissen bietet – wie so oft – Nährboden für hochtrabende Marketingkampagnen. Im Zuge dessen warten bereits große IT-Firmen mit Blockchain as a Service auf. Insbesondere Banken interessieren sich für das Thema. Eine Private Blockchain als Ergebnis von Kooperationsprojekten befindet sich mindestens in der Diskussion. Hier beobachten wir parallele Muster wie beim Thema Big Data. Das spiegelt sich besonders in den Anfragen wider, die wir im Sinne von „Wir wollen mal etwas mit Blockchain machen“ erhalten.

Zu Beginn sollte man sich bewusst machen, dass die Blockchain ein Public Ledger ist. Das bedeutet, sie ist eine öffentliche Datenbank, an der jeder nach Belieben teilnehmen und wieder aussteigen und das System von jedem gelesen und beschrieben werden kann. Darüber hinaus kann das System nur betrieben werden, wenn viele Teilnehmer dazu bereit sind, Ressourcen in Form von Rechnerkapazitäten zu investieren und Blöcke zu erzeugen (Mining). Deshalb muss ein Anreiz geschaffen werden, das zu tun. Bei Bitcoin war das eine Gewinnausschüttung. Sind zu wenige Teilnehmer im Netzwerk, kann dieses leicht manipuliert werden. Wenn ein potenzieller Bösewicht mehr als die Hälfte der Netzwerkknoten kontrolliert, ist es ihm möglich, falsche Informationen in der Blockchain unterzubringen und die Konsistenzprüfungen in seinen Netzwerkknoten auszulassen. Auf der anderen Seite verfehlt eine Blockchain, die man gemeinsam mit Freunden und Partnern betreibt, die eigentliche Idee.

Es existieren zwar genügend öffentliche Informationsquellen, doch viele haben uns nicht zufriedengestellt und waren entweder sehr oberflächlich oder gingen zu sehr ins Detail. Um ein tieferes Verständnis für Entwickler zu schaffen, haben wir uns deshalb dazu entschieden, eine einfache Blockchain in Java zu implementieren. Damit verlässt man die rein konzeptionelle Diskussionsebene und kann die Grundzüge einer Blockchain bereits in wenigen Zeilen Code abbilden.

Das Blockchain Dossier auf JAXenter:

X

JBlockchain: Die ersten Schritte

Unsere Blockchain auf Java-Basis ist bewusst einfach gehalten und verfügt aus Benutzersicht nur über die Funktion, Nachrichten zu versenden und sich diese in der Blockchain anzuschauen, ähnlich einem öffentlichen Chatroom. Grundsätzlich unterscheidet man zwischen Anwendern (die z. B. im Bitcoin-Netzwerk Geld austauschen) und Netzwerkteilnehmern, welche die Infrastruktur stellen, die Daten speichern und für die Kommunikation unter den Teilnehmern sorgen. Je nachdem, ob man Benutzer oder Netzwerkteilnehmer ist, müssen unterschiedliche Dinge gemacht werden. Um die Beispielimplementierung direkt ausprobieren zu können, muss lediglich das GitHub-Projekt geklont und mit Maven gebaut werden. Da wir zur Kommunikation mit anderen Anwendern die Infrastruktur benötigen, müssen wir einen Node hochfahren. Das Kommando zeigt, wie es geht:

java -jar node/target/node-0.0.1-SNAPSHOT.jar

Zur Kommunikation mit anderen Teilnehmern werden ein privater und ein öffentlicher Schlüssel sowie ein Benutzername benötigt. Schlüssel und Benutzername ergeben später eine eindeutige, öffentliche Adresse, über die sich unsere Nachrichten identifizieren lassen. Dieser Befehl erzeugt das Schlüsselpaar:

java -jar client/target/client-0.0.1-SNAPSHOT.jar –keypair

Es existieren zwei Dateien key.priv und key.pub. Als Nächstes muss die eindeutige öffentliche Adresse generiert werden:

java -jar client/target/client-0.0.1-SNAPSHOT.jar --address –-node "http://localhost:8080" --name "Max Mustermann" --publickey key.pub

Hierfür wird die Adresse eines Netzwerkknotens benötigt, der öffentliche Schlüssel und ein frei wählbarer Name. Der Aufruf liefert als Ergebnis die eindeutige, öffentliche Adresse für den Benutzer zurück. Unter der Ressource http://localhost:8080/address kann kontrolliert werden, dass diese im System angelegt wurde.

Um eine Nachricht an das System zu schicken, werden nun die im vorherigen Schritt erstellte Adresse und der private Schlüssel benötigt. Listing 1 zeigt, wie die Nachricht „Hallo Welt!“ versendet wird. Unter der Ressource http://localhost:8080/transaction kann die Nachricht nun so lange angeschaut werden, bis der Netzwerkknoten diese in der eigentlichen Blockchain festgeschrieben hat. Festgeschriebene Nachrichten sind dann unter der Ressource http://localhost:8080/block verfügbar.

java -jar client/target/client-0.0.1-SNAPSHOT.jar --transaction --node "http://localhost:8080" --sender "Tdz0bKDfca3QjFAe5Ccuj9Noy6ah8n+R8DnZznvjic4=" --message "Hallo Welt" --privatekey key.priv

Node-Kommunikation aufbauen

Die Netzwerkknoten müssen miteinander kommunizieren, damit jeder den gleichen Stand der Blockchain hat. Damit das mit einer großen Anzahl an Teilnehmern funktioniert, hat sich der Peer-to-Peer-Ansatz durchgesetzt. Bei diesem Ansatz haben alle Netzwerkknoten den gleichen Stand und verständigen sich untereinander ohne eine zentrale Kontrollinstanz. In unserem Beispiel verwenden wir anstelle des Peer-to-Peer-Ansatzes eine einfache Kommunikation über HTTP. Sobald ein Netzwerkknoten neue Informationen erhält, z. B. eine neue Transaktion oder einen neuen Block, sendet er die Information an alle anderen Netzwerkknoten (Broadcast all). Beispielsweise implementiert in Listing 2 der AddressController eine Methode, mit der eine neue Adresse hinzugefügt werden kann, falls sie nicht bereits existiert. Mit dem optionalen Parameter publish kann der Knoten angewiesen werden, alle anderen Knoten über die neue Adresse zu informieren.

@RestController()
@RequestMapping("address")
public class AddressController {
  private final AddressService addressService;
  private final NodeService nodeService;

  @RequestMapping(method = RequestMethod.PUT)
  void addAddress(@RequestBody Address address, @RequestParam(required = false) Boolean publish, HttpServletResponse response) {
    if (addressService.getByHash(address.getHash()) == null) {
      addressService.add(address);
      if (publish != null && publish) {
        nodeService.broadcastPut("address", address);
      }
      response.setStatus(HttpServletResponse.SC_ACCEPTED);
    } else {
      response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
    }
  }
}

Die Implementierung der Methode broadcastPut des NodeService sendet einfach parallel Put-Requests an alle bekannten Netzwerkknoten. Der Einfachheit halber gehen wir bewusst davon aus, dass die Knoten immer erreichbar sind und die Anfragen auch verarbeiten.

public void broadcastPut(String endpoint, Object data) {
  knownNodes.parallelStream().forEach(
    node -> restTemplate.put(node.getAddress() + "/" + endpoint, data));
  }

Beim Start eines Netzwerkknotens werden initial folgende Aktionen getätigt, um die lokalen Daten auf den aktuellen Stand des Netzwerks zu setzen:

  1. Anfrage des Master-Netzwerkknotens, um folgende Daten herunterzuladen: alle Knoten, die der Master-Knoten kennt, alle Adressen der Clients, die Blockchain (Datenbankdaten) und den kompletten Transaktionspool (temporäre Daten, die noch nicht in die Blockchain geschrieben wurden)
  2. Broadcast an alle Knoten, dass ein neuer Netzwerkknoten im System ist.

Danach ist der Knoten bereit und kann von Nutzern angesprochen werden.

Nachrichten versenden und verifizieren

Zuvor haben wir bereits gezeigt, wie eine Nachricht mithilfe des Clients versendet werden kann. Unter der Haube wird dabei die Nachricht in einer Transaktion verpackt (Listing 3). Das Attribut hash bildet hier den Identifier der Transaktion und wird gebildet, indem alle Attribute zusammen gehasht werden. Dadurch kann eine Transaktion eindeutig identifiziert werden: Bei gleichem Hash muss also auch der Inhalt gleich sein. Im Feld text wird die Nachricht hinterlegt und der senderHash referenziert die eindeutige Absenderadresse. Als timestamp wird der Zeitpunkt der Transaktionserstellung gewählt. Im Attribut signature wird die Signatur hinterlegt, die in Listing 4 erzeugt wird.

public class Transaction {
  private byte[] hash;
  private String text;
  private byte[] senderHash;
  private long timestamp;
  private byte[] signature;
}

Die Signatur wird aus dem Nachrichtentext und dem privaten Schlüssel des Absenders gebildet. Da der private Schlüssel nur dem Absender bekannt ist, kann daraufhin jeder bestätigen, dass die Nachricht tatsächlich von der Adresse versendet wurde, die hinter dem senderHash steckt.

byte[] signature = SignatureUtils.sign(text.getBytes(), Files.readAllBytes(privateKey));
Transaction transaction = new Transaction(text, senderHash, signature);

Daraufhin wird die Transaktion an einen Netzwerkknoten übermittelt. In Listing 5 ist zu sehen, wie eine Transaktion vom Knoten entgegengenommen wird und falls sie einer Verifikation genügt, in den Transaktionspool gelangt. Bei dem Pool handelt es sich um einen Zwischenspeicher für noch nicht in der Blockchain verankerte Transaktionen.

public synchronized boolean add(Transaction transaction) {
  if (verify(transaction)) {
    transactionPool.add(transaction);
    return true;
  }
  return false;
}

In der verify-Methode in Listing 6 wird zunächst überprüft, ob der Absender der Transaktion überhaupt bekannt ist. An der Adresse ist der öffentliche Schlüssel allen zugänglich, sodass zusammen mit dem Nachrichtentext (getSignableData) und der Signatur die Echtheit der Nachricht bestätigt werden kann. Abschließend wird noch überprüft, ob der übermittelte Hash der Transaktion auch korrekt berechnet wurde.

private boolean verify(Transaction transaction) {
  // known address
  Address sender = addressService.getByHash(transaction.getSenderHash());
  if (sender == null) {
    return false;
  }
  // correct signature
  if (!SignatureUtils.verify(transaction.getSignableData(), transaction.getSignature(), sender.getPublicKey())) {
    return false;
  }
  // correct hash
  if (!Arrays.equals(transaction.getHash(), transaction.calculateHash())) {
    return false;
  }
  return true;
}

Transaktionspool und das Mining

In einer relationalen Datenbank würden die Nachrichten über einen INSERT gespeichert werden. Transaktionseigenschaften (ACID) und Transaktionslevel (READ UNCOMMITTED oder READ COMMITTED) sorgen dafür, dass parallele Schreib- und Lesezugriffe bestimmten Anforderungen genügen und definieren, wie sicher sie ablaufen sollen. So wird vor dem INSERT eine Transaktion geöffnet und bei Fehlerfreiheit über einen COMMIT in der Datenbank festgeschrieben.

Da in der Blockchain die Daten nicht zentral gespeichert werden, sondern eine Kopie aller Daten auf beliebig vielen Netzwerkknoten liegt und beliebige Benutzer parallel Daten speichern wollen, benötigen wir einen anderen Mechanismus, um Transaktionssicherheit zu bekommen. Hier kommen der Transaktionspool und das Mining ins Spiel. In der Blockchain gibt es grundsätzlich zwei Bereiche, in denen sich Daten aufhalten. Zum einen ist das der Transaktionspool, in dem sich die noch festzuschreibenden Daten befinden, und zum anderen die Blockchain selbst mit den nicht mehr zu ändernden Daten (Abb. 1). Damit die Netzwerkknoten nicht gleichzeitig Transaktionen in die Blockchain schreiben, gibt es eine mathematische Herausforderung, die es zu bewältigen gilt. Sie nehmen sich beliebige, frei wählbare Transaktionen aus dem Transaktionspool und generieren daraus einen Hash. Dieser Hash muss nun mit einer, zwei oder drei Nullen beginnen – je nach Schwierigkeitsgrad.

Abb. 1: Die beiden Bereiche der Blockchain, in denen sich Daten aufhalten: der Transaktionspool und die Blockchain selbst

Abb. 1: Die beiden Bereiche der Blockchain, in denen sich Daten aufhalten: der Transaktionspool und die Blockchain selbst

Die Schwierigkeit richtet sich dabei nach der zur Verfügung stehenden Mining-Kapazität. Vereinfacht ausgedrückt: Wenige Netzwerkknoten bedeuten eine kleine Anzahl an Nullen, viele Netzwerkknoten bedeuten eine große Anzahl an Nullen. Ziel dabei ist, dass ein passender Hash erst nach einer bestimmten Zeit gefunden wird, z. B. alle fünf Minuten. In Listing 7 ist dargestellt, wie mit einer Brute-Force-Methodik ein passender Block gesucht wird. Solange der Miner aktiv sein soll, wird ein neues Blockobjekt konstruiert. Der Miner referenziert den letzten bereits in der Blockchain verankerten Block und enthält die zuvor ausgewählten Transaktionen. Außerdem hat jeder Block das Attribut tries, bei dem es sich um eine frei wählbare Zahl handelt und die auch in die Berechnung des Block-Hashes eingeht. Falls der Hash des neu erzeugten Blocks also nicht genügend führende Nullen aufweist, so wird tries einfach um eins erhöht und man erstellt einen neuen Block.

long tries = 0;
while (runMiner.get()) { // atomicBoolean zur Steuerung aus anderen Threads heraus
  Block block = new Block(previousBlockHash, transactions, tries);
  if (block.getLeadingZerosCount() >= Config.DIFFICULTY) {
    return block;
  }
  tries++;
}

Aktuelle Beschränkungen und Erweiterungsmöglichkeiten

Die JBlockchain-Implementierung ist sehr einfach gehalten, denn das Verständnis der Technologie soll hier im Vordergrund stehen. Für einen produktiven Einsatz fehlen viele Konzepte, von denen einige hier beispielhaft erwähnt werden sollen:

  • Der Broadcast all skaliert nicht für eine große Anzahl an Netzwerkknoten. Hier muss eine intelligentere Nachrichtenverteilung stattfinden.
  • Es müssten mindestens einige Master-Knoten im Netzwerk existieren (bspw. statisch hinterlegt), zu denen sich die Benutzer verbinden können.
  • Die naive Betrachtung eines verteilten Systems ohne Nebenläufigkeiten, Nachrichtenverlust etc. wird rasch zu Problemen führen. Das könnte man mit einem speziell für diesen Bereich zugeschnittenen Framework lösen, z. B. Atomix.
  • Generell ist zu überlegen ob die Peer-to-Peer-Komponente durch ein Framework wie GNUnet abzulösen ist.
  • Die mathematische Herausforderung muss mit der Anzahl der Netzwerkknoten wachsen. Aktuell ist diese statisch konfiguriert.
  • Der Anwendungszweck müsste näher spezifiziert werden, denn dadurch ergeben sich eventuell weitere Anforderungen, beispielsweise, dass die Transaktionen nicht beliebig zu einem Block zusammengesetzt werden dürfen, sondern bestimmte Transaktionen Priorität haben (z. B. nach Eingangsdatum).

Fazit

Die Blockchain ist eine hochinteressante und spannende Technologie, die uns noch länger begleiten wird. Dass sie allerdings die Welt verändern wird, wagen wir zu bezweifeln. Zumindest nicht in der aktuellen Form. Viel wahrscheinlicher ist, dass bestimmte Teile in anderer Form wiederverwendet werden oder sie für Marketingzwecke genutzt wird. Für konkrete Projekte müsste ein Entscheider einiges in Kauf nehmen: Wenig Kontrolle über die Anzahl der Teilnehmer, hohe Fluktuation der Teilnehmer, aufwendiges Programmiermodell, schwierige Anbindung externer Schnittstellen sowie Anreiz und Marketing für das System müssen geschaffen werden. Diese Punkte machen eine realistische Abschätzung für ein Projekt sehr schwierig, da die meisten Unternehmen verlässliche Angebotszahlen und ein konkretes Ergebnis erwarten. Wer bereit ist, diese Herausforderungen anzunehmen, bekommt die Vorteile einer hochverfügbaren, skalierbaren Datenbank ohne zentrale Kontrollstelle und damit ohne eigene Hardwarekosten.

Hintergrund zum Thema:

Blockchain und die Bitcoin-Plattform – eine Einführung

Geschrieben von
Marc van den Bogaard
Marc van den Bogaard
Marc van den Bogaard ist Geschäftsführer der NEOZO GmbH & Co. KG und berät Kunden bei der Umsetzung von E-Commerce-Projekten. Daneben beschäftigt er sich mit der Hochverfügbarkeit, Skalierung und Performance von komplexen Systemen. E-Mail: bogaard@neozo.de
Roland Kahlert
Roland Kahlert
Roland Kahlert ist als Softwareentwickler bei der NEOZO GmbH & Co. KG tätig und beschäftigt sich mit verschiedenen Technologien aus den Bereichen Datenbanken, Data Mining und maschinellem Lernen. E-Mail: kahlert@neozo.de
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.