Kluge Kontrakte auf Basis von Ethereum

Stefan Tilkov, Marc Jansing

Ethereum ist ein Blockchain-basiertes, offenes System, das im Gegensatz zu Bitcoin die Idee von „Smart Contracts“ als zentrales Thema hat. Damit können beliebige Geschäftsmodelle durch eine Form automatischer Agenten abgebildet werden, die auf Transaktionen reagieren und deren Code für jeden einsehbar ist. Die Regeln, die man normalerweise in juristischen Verträgen abbildet, werden damit zu ausführbaren Programmen – und die Akteure, die sich an diese Regeln halten, durch „autonome Organisationen“ ersetzt. In diesem Artikel stellen wir die Technik hinter Ethereum kurz vor und zeigen die Möglichkeiten an einem einfachem Beispiel auf.

Ethereum ist neben dem Urvater Bitcoin die zweite bekannte öffentliche Blockchain-Implementierung. Im Kern beruht sie auf den selben Konzepten: Der Zugang ist nicht begrenzt, für die Validierung von Blöcken kommt das Proof-of-work-Konzept zum Einsatz und mit Ether gibt es analog zu Bitcoin eine Währung, die universell akzeptiert wird. Der wesentliche Unterschied zwischen Ethereum und Bitcoin ist die Rolle, die ausführbarer Code spielt. Bereits in Bitcoin gibt es die Möglichkeit, als Teil von Transaktionen kleine Skripte in die Blockchain zu stellen. Tatsächlich werden diese sogar bei den Basistransaktionen wie dem Transfer von Bitcoins benutzt: Noch nicht ausgegebene „Outputs“ aus Transaktionen (UTXOs) kann nur ausgeben, wer einen Private Key besitzt, der für die Übergabe eines Parameters an ein Skript benutzt werden muss, damit dieses „true“ zurückliefert. Dieses Skript wird vom Absender in die Transaktion geschrieben, der das virtuelle Geld damit nur für gewünschten Empfänger zugänglich macht. Die Skriptsprache in Bitcoin ist eine bewusst limitierte, stackbasierte Sprache, die nicht Turing-vollständig ist. Damit wird zwar sichergestellt, dass keine Endlosschleifen entstehen und damit die Sicherheitsrisiken begrenzt sind, allerdings sind genau damit den Möglichkeiten, intelligente Skripte zu implementieren, klare Grenzen gesetzt.

An genau dieser Stelle setzt Ethereum an und stellt eine virtuelle Maschine zur Verfügung, die beliebig komplexe Programme ausführen kann. Innerhalb der Ethereum-Plattform gibt es damit neben Accounts (Konten für klassische, in der Regel menschliche Benutzer) auch Contracts. Dabei handelt es sich um eine besondere Art von Konto, eine Art Agent, der basierend auf einem Programm agiert, das in den Bytecode der Ethereum VM übersetzt wurde. Um Kontrakte in die Ethereum-Plattform zu stellen, verwenden Entwickler eine Programmiersprache, die in Ethereum-Bytecode übersetzt wird. Dieser Bytecode wird dann im Rahmen einer Transaktion in die Plattform übertragen und als Teil eines Blockes validiert. Die so erstellte Anwendung wird so selbst Teil der Plattform. Sie wird dezentral ausgeführt und daher auch als Distributed App (kurz „DApp“) bezeichnet.

Selbst das Wallet, das verwendet wird, um (im übertragenen Sinne) das eigene virtuelle Geld zu verwalten, ist nur eine solche DApp. Damit erklärt sich auch der Unterschied zwischen den beiden wichtigsten Ethereum-Clients, der am Anfang verwirrend sein kann: Mist ist der Name eines DApp-Browsers, einer Anwendung, mit der man mit beliebigen Kontrakten der Ethereum-Plattform interagieren kann. Ethereum Wallet ist der Name eines Clients, der auf Mist basiert, aber spezifisch den Zugriff auf eine voreingestellte Anwendung erlaubt, nämlich Wallet.

Ethereum ist damit eine programmierbare Blockchain-Plattform, die DApps oder auch „Smart Contracts“ unterstützt. Der Gedanke ist außerordentlich reizvoll, weil sich so mit Hilfe einer relativ einfachen Programmierumgebung sehr leicht Anwendungen erstellen lassen, die von den Blockchain-Vorteilen profitieren. Gleichzeitig sorgt die Möglichkeit zum unkomplizierten, öffentlichen Zugriff dafür, dass sich sehr leicht Experimente starten und auf Akzeptanz im Ethereum-Markt prüfen lassen.

Da die Ethereum-VM beliebig komplexe Programme ermöglicht, die dezentral validiert und damit ausgeführt werden müssen, muss sichergestellt sein, dass niemand über Gebühr Ressourcen verbraucht und z.B. die gesamte Ethereum-Plattform in eine Endlosschleife schickt. Dazu müssen Interaktionen mit Kontrakten vom Aufrufer mit „Gas“ (im Sinne von „Treibstoff“) ausgestattet werden. Die Ausführung des Programmes verbraucht diesen Treibstoff – vereinfacht gesagt kostet jede Instruktion eine bestimmte Menge. Ist der gesamte Treibstoff verbraucht, wird die Programmausführung beendet.

Sehen wir uns die Nutzung an einem einfachen Beispiel an.

Hello ShortR

Die App-Entwicklung mit Ethereum demonstrieren wir anhand von ShortR: ShortR ist ein einfacher URL-Shortener (Link-Verkürzer) auf Basis von Ethereum. Im Vergleich zu ähnlichen Diensten speichert ShortR das Mapping zwischen verkürzter und vollständiger URL unveränderlich und transparent auf der Ethereum-Blockchain. Die Daten sind somit im dezentralen Netzwerk gespeichert und selbst nach potenzieller Insolvenz eines solchen Dienste-Anbieters weiter verfügbar. Außerdem muss sich kein Nutzer Sorgen machen dass der Anbieter einen Link manipuliert und ihm somit ein anderes Ziel unterschiebt.

Die Stunde 0

Neben dem produktiven Main Network betreibt das Ethereum-Projekt auch ein Testnet. Keines der beiden öffetlichen Netzwerke ist für den Einstieg in die Entwicklung optimal. Die ersten Schritte wollen wir stattdessen mit einem eigenen Ethereum-Node auf unserem Entwicklungsrechner durchführen. Somit ersparen wir uns die fortlaufende gigabyte-zerrende Synchronisation und agieren autark vom Netzwerk.

Der Start einer jeden Blockchain ist der Genesis-Block - der erste Block in der Kette und der einzige Block ohne Vorgänger. Während im Bitcoin-Netzwerk der Genesis-Block in der Software festgeschrieben ist, kann dieser bei Ethereum selbst definiert werden. Andere Nodes validieren hierüber die Blockchain und akzeptieren neue Blöcke nur bei identischen Genesis-Block. Über die Definition eines eigenen Genesis-Block lassen sich also beliebig viele private Blockchains erstellen. Nach Installation von Ethereum und dem Mist-Browser definieren wir den Genesis-Block in einem von uns erstellten Verzeichnis in der Datei genesis-block.json:


{
    "nonce": "0x0000000000000042",
    "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "difficulty": "0x4000",
    "alloc": {},
    "coinbase": "0x0000000000000000000000000000000000000000",
    "timestamp": "0x00",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "extraData": "My dev-chain",
    "gasLimit": "0xffffffff"
}

Anschließend initialisieren wir unseren Ethereum-Node mit dem bereitgestellten Kommandozeilenwerkzeug geth: geth --datadir "dev-chainData" init genesis-block.json.

Nach erfolgter Initialisierung kann der Ethereum-Node gestartet werden:


geth --identity "DevChain" --datadir "dev-chainData" --networkid 42 --nodiscover --rpc --rpcapi "db,eth,net,web3,personal" --rpccorsdomain "*" console

Über die übergebenen Kommandozeilenparameter starten wir den Node in einem privaten Modus, aktivieren das RPC-Interface und öffnen die interaktive JavaScript-Konsole. Über das RPC-Interface können Ethereum-Clients wie Mist oder die später vorgestellte Web-App mit dem Node interagieren.

Im nun laufenden Ethereum-Node fehlt uns jetzt noch ein Account um Ether zu verwalten und Verträge zu deployen. Wir erstellen einen Account mit dem Passwort secret, setzen diesen Account als Begünstigten für das Mining und starten anschließend das Mining unseres Nodes. Dadurch sollte der von uns erstellte Account nach und nach Belohnungen in Form der Ethereum-Währung Ether erhalten. All das führen wir in der JavaScript-Konsole aus:


Welcome to the Geth JavaScript console!

> account = personal.newAccount('secret')
[...]
"0xb7ED41adC352a3752F5c695bD7a97546c11d0977 "
> miner.setEtherbase(account)
true
> miner.start()
[...]

Anschließend starten wir den Mist-Browser und verbinden diesen mit dem RPC-Interface unseres Ethereum-Nodes. Unter macOS machen wir das mit folgendem Kommandozeilenbefehl: /Applications/Mist.app/Contents/MacOS/Mist --rpc http://localhost:8545.

Die grafische Oberfläche des Mist-Browser startet nun (s. Abb. 1). Unser Node ist bereit für die Entwicklung.

Abb. 1: Mist-Browser
Abb. 1: Mist-Browser

Kontrakt-Entwicklung

Zur Entwicklung unseres Kontraktes verwenden wir Solidity, eine JavaScript-ähnliche, allerdings statisch getypte, objektorientierte Ethereum-Sprache, die praktische Datentypen für die Entwicklung von Verträgen enthält. Für die Entwicklung stehen sowohl Kommandozeilenwerkzeuge wie auch eine IDE-Unterstützung zur Verfügung. Wir benutzen der Einfachheit halber den Browser Mist und erstellen unseren einfachen Kontrakt direkt im dafür vorgehesehen Editorfenster.

Unser Kontrakt ist folgendermaßen aufgebaut:


pragma solidity ^0.4.8;

contract Owned {
  ...
}

contract Shortr is Owned {
  ...
}

Die erste Zeile (das so genannte Version-Pragma) beschreibt mit welcher Compiler-Version unser Kontrakt kompiliert werden soll (in diesem Fall mindestens 0.4.8). Mit dem Schlüsselwort contract definieren wir die Verträge Owned und Shortr. Funktionen eines Kontraktes sind in der Ethereum-VM von jedermann aufrufbar. Unser Kontrakt hingegen besitzt eine Funktion die dem Vertagsbesitzer als Administrator vorbehalten sein soll. Die hierfür notwendige technische Logik definieren wir in dem Kontrakt Owned und geben diese mit Hilfe des Vererbungskonzepts von Soldity an den Shortr-Kontrakt weiter. Dieses Muster findet man in diversen Kontrakten der Ethereum-Blockchain.

Bereits hier wird die hohe Verantwortung der Entwickler deutlich da sie in Gänze für die Verträge verantwortlich sind. Dies schließt die Sicherheit explizit mit ein. Nach dem Deployment leben die Kontrakte in der Ethereum-Blockchain, führen dort ein Eigenleben und können nicht ohne weiteres geändert werden. Diese Erfahrung haben auch die Entwickler von “The Dao” gemacht (siehe Kasten, Das DAO-Desaster). Die Solidity-Dokumentation beschreibt unter dem Punkt Security Considerations gängige Probleme sowie Empfehlungen für Entwickler.

weitere Informationen zum Thema: Das DAO-Desaster

Die Business-Logik unseres Linkverkürzers ist im Shortr-Kontrakt definert:


contract Shortr is Owned {
    string public baseUrl;
    struct UriRecord { string uri; address creator; }
    mapping (string => UriRecord) private uriRegistry;

    event NewShortUri(string shortUri, string fullUri);

    // constract constructor
    function Shortr(string initialBaseUrl) {
        baseUrl = initialBaseUrl;
    }

    function setBaseUrl(string url) onlyOwner {
        baseUrl = url;
    }

    function shorten(string linkId, string fullUri) {
        uriRegistry[linkId] = UriRecord({uri: fullUri, creator: msg.sender});

        var shortUri = concatString(baseUrl, linkId);
        NewShortUri(shortUri, fullUri);
    }

    function getUri(string linkId) returns (string) {
        return uriRegistry[linkId].uri;
    }

    // soldity does not have concat() so we have to do it manually :-(
    function concatString(string s1, string s2) internal returns (string) {
      ...
    }
}

Die wichtigsten Funktionen des Kontraktes sind shorten(linkId, fullUri) zum Hinzufügen eines neuen bzw. getUri(linkId) zum Lookup eines bestehenden Links. Die Funktion mit dem gleichen Namen wie der Kontrakt (shortr(initialBaseUrl)) ist automatisch der Konstruktur unseres Vertrages. Auf Grundlage des Konstruktors bietet uns der Mist-Browser Formularfelder für die initialen Werte beim Deployment an (s. Abb. 2).

Beim Hinzufügen eines neuen Links wird eine Struct-Datenstruktur UriRecord bestehend aus der vollen Adresse des Links und der Account-Adresse des Erstellers unter der verkürzenden linkId in das interne Mapping uriRegistry geschrieben. Der Einfachheit halber generieren wir hier keine kryptische LinkId sondern der Nutzer kann diese in unserem Beispiel selbst wählen. Dieser Vorgang wird abschließend über das Event NewShortUri publiziert sodass Clients auf dieses Ereignis reagieren können.

Die Funktion setBaseUrl(url) ist die bereits erwähnte Funktion deren Aufruf ausschließlich dem Vertragsbesitzer vorbehalten werden soll. Mit dieser Funktion kann der Administrator nachträglich die baseUrl von ShortR ändern (z.B. von http://url.to/abc auf http://shortr.to/abc). Dies geschieht mit Hilfe des an die Funktion herangestellten Modifier onlyOwner. Hierbei handelt es sich um eine besondere Funktion aus unserem Owned-Kontrakt:


modifier onlyOwner {
    if (msg.sender != owner) throw;
    _;
}

Die Funktion stellt sicher dass nur der Vertragsbesitzer berechtigt ist die annotierte Funktion aufzurufen. Andernfalls wird der Funktionsaufruf unterbrochen. Der Unterstrich hier wird zur Laufzeit durch den Funktionskörper der annotierten Funktion ersetzt.

Etwas auf die Kette kriegen

Den von uns verfassten Soldity-Quellcode deployen wir mit Hilfe des Mist-Browser in unseren lokalen Ethereum-Node. Hierzu wählen wir in Mist die Schaltflächen CONTRACTS, DEPLOY NEW CONTRACT und kopieren den Quellcode in das SOLIDITY CONTRACT SOURCE CODE-Fenster. Nach erfolgreicher Syntaxprüfung können wir den Kontrakt deployen.

Rechts neben dem Sourcecode-Fenster wählen wir unter SELECT CONTRACT TO DEPLOY (s. Abb. 2) den Kontrakt mit dem Namen Shortr und übergeben darunter als Konstruktor-Parameter eine initiale Base-Url (z.B: http://url.to/).

Abb. 2: Kontrakt-Deployment mit dem Mist-Browser
Abb. 2: Kontrakt-Deployment mit dem Mist-Browser

Wir starten das Deployment über die Schaltfläche DEPLOY. Dabei legen wir die Menge an Gas fest, die uns dies wert ist und bestätigen die Transaktion mit dem Passwort unseres Accounts (secret, s. Abb. 3).

Abb. 3: Kontrakt-Transaktion erstellent
Abb. 3: Kontrakt-Transaktion erstellent

Damit wird unser Kontrakt der Plattform als Teil einer Transaktion bekannt gemacht, auf deren Validierung wir nun warten müssen. Dies sollte nicht länger als eine Minute dauern. Danach können wir mit dem Kontrakt interagieren, indem wir seine Methoden aufrufen:

Contract Usage
Contract Usage

Zugriff über Drittanwendungen

Neben der Kontrakt-Interaktion mit dem Mist-Browser wollen wir zeigen wie Drittanwendungen mit Verträgen und der Ethereum-Blockchain interagieren können. Hierzu haben wir eine auf NodeJS basierende Webb-App entwickelt (s. Abb. 5):

Abb. 5: ShortR Web-App
ShortR Web-App

Die App erlaubt neben dem Blockchain-Lookup nach linkId und anschließender Weiterleitung auf die zugehörige URL auch die Anlage neuer Links. Die Anlage neuer Links kann der Nutzer über ein HTML-Formular vornehmen. Der Lookup und Weiterleitung einer linkId erfolgt über den Aufruf von http://localhost:300/abc.

Für JavaScript-Anwendungen stellt das Ethereum-Projekt mit web3.js eine Ethereum-API als NPM-Modul zur Verfügung. Durch Nutzung des Moduls können wir in unserer Anwendung das gleiche web3-Objekt nutzen wie der Ethereum Mist-Browser.

Der folgende Quellcode zeigt den Aufruf der getUri('abc')-Funktion unseres Shortr-Kontrakts:

const fs = require('fs');
const solc = require('solc');
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));

const contractInput = fs.readFileSync('contract/shortr.sol');
const compiledContract = solc.compile(contractInput.toString(), 1);
const abi = JSON.parse(compiledContract.contracts[':Shortr'].interface);

// creation of contract object
const shortrContract = web3.eth.contract(abi);

// initiate contract for an address
const contractAddress = 'MY_CONTRACT_ADDRESS'; // replace with your address
const shortrContractInstance = shortrContract.at(contractAddress);

const linkId = 'abc';
const target = shortrContractInstance.getUri.call(linkId);
console.log(target); // => http://www.example.org

Für den Aufruf von Kontrakt-Funktionen sind zwei Informationen notwendig: Die Kontrakt-Adresse sowie das Kontrakt-ABI (Application Binary Interface). Die Kontrakt-Adresse wird uns nach erfolgreichen Deployment im Mist-Browser angezeigt. Das Application Binary Interface ist eine Schnittstellenbeschreibung im JSON-Format. Die Beschreibung ist bereits zur Kompilierzeit bekannt und somit statisch. In unserem Beispiel kompilieren wir unseren Vertrag einmal zur Laufzeit und referenzieren das Kontrakt-ABI. Im Mist-Browser kann die Schnittstellenbeschreibung beim deployten Kontrakt über die Schaltfläche Show Interface eingesehen werden.

Der Aufruf der Funktion getUri(linkId) hat keinen Einfluss auf den Zustand des Vertrages. Aus diesem Grund ist für den Aufruf der Funktion keine Transaktion notwendig. Der Aufruf ist damit “kostenlos” und verbraucht kein Gas.

Das Speichern eines neuen Links (shorten(linkId, fulluri)) hingegen persistiert einen neuen Eintrag in der Ethereum-Blockchain. Bei dieser Aktion ist eine Blockchain-Transaktion notwendig:

// same shortrContractInstance-instantiation as above

const sender = web3.personal.listAccounts[0];
web3.personal.unlockAccount(sender, 'secret');

const linkId = 'js';
const fullUri = 'https://www.sigs-datacom.de/fachzeitschriften/javaspektrum.html';

shortrContractInstance.shorten.sendTransaction(linkId, fullUri, { from: sender }, function(error, result) {
  web3.personal.lockAccount(sender); // lock account after transaction
});

Zwischenfazit nach Beispiel

Das Beispiel ist mit Absicht sehr simpel gehalten: So wird unter anderem bei der Erstellung eines neuen Mappings ein bestehendes einfach überschrieben. In einer realistischen Implementierung würde man genau das ausschließen wollen. Aber es demonstriert den Prozess, Anwendungen in die Ethereum-Blockchain zu deployen und erfüllt damit den Zweck: Wie bei den meisten echten Szenarien wird nicht alles mit Blockchain-Mitteln umgesetzt, sondern nur der Teil, der davon profititiert. Ebenso üblich ist der Mechanismus, mit dem sich der Besitzer besondere Rechte einräumt. Und schließlich: Ist sie erst einmal produktiv, verhält es sich mit der Ethereum-Anwendung wie mit jeder Blockchain-Transaktion – sie ist nicht mehr rückgängig zu machen und führt ein Eigenleben.

Fazit

Der Ansatz von Ethereum, eine programmierbare Plattform zur Verfügung zu stellen, ist innovativ und ermöglicht zahlreiche Lösungen, die auf anderen Plattformen nicht oder nur mit großem Aufwand möglich sind. Der Code, der als Teil des Kontraktes deployt ist, spiegelt dabei die verbindliche Wahrheit wider – im Guten wie im Schlechten. Dessen sollte man sich unbedingt bewusst sein und je nach Risiko und Kritikalität geeignete Qualitätssicherungsmaßnahmen durchführen. Die modern wirkende, ansprechend gestaltete Plattform und die gute Dokumentation können nicht darüber hinwegtäuschen, dass es sich noch um ein sehr junges Projekt handelt, das durchaus noch unter Stabilitätsproblemen leidet. Wie bei allen anderen Distributed Ledger-Lösungen auch ist es nicht angeraten, ohne Not die Lebensersparnisse darin anzulegen. Für Experimente mit Smart Contracts in der Blockchain ist Ethereum jedoch hervorragend geeignet.

Thumb dsc01793

Stefan Tilkov ist Geschäftsführer und Principal Consultant bei der innoQ Deutschland GmbH, wo er sich vorwiegend mit der strategischen Beratung von Kunden im Umfeld von Softwarearchitekturen beschäftigt. Er ist Autor des Buchs “REST und HTTP”, Mitherausgeber von “SOA-Expertenwissen” (beide dpunkt.verlag), Autor zahlreicher Fachartikel und häufiger Sprecher auf internationalen Konferenzen.

Weitere Inhalte

Thumb dsc01911

Marc Jansing arbeitet als Senior Consultant bei innoQ und entwickelt seit mehreren Jahren Webanwendungen mit leichtgewichtigen Frameworks. Sein Schwerpunkt liegt in der Implementierung von ergonomischen Anwendungen auf REST-Basis und verteilten Systemen.

Weitere Inhalte

Java spektrum
Dieser Artikel ist ursprünglich in Ausgabe 04/2017 der Zeitschrift JavaSPEKTRUM erschienen. Die Veröffentlichung auf innoq.com erfolgt mit freundlicher Genehmigung des SIGS-Datacom-Verlags.

Kommentare

Um die Kommentare zu sehen, bitte unserer Cookie Vereinbarung zustimmen. Mehr lesen