Die Ethereum-Blockchain unterstützt Smart Contracts: global verteilte, manipulationssichere Computerprogramme. Wie man so einen Vertrag programmiert, haben wir anhand eines kleinen Schere-Stein-Papier-Spiels im vorherigen Teil der Serie gezeigt. Jetzt ist es Zeit, dem Vertrag eine Bedienoberfläche zu verpassen, damit Freunde bequem mitspielen können, ohne sich mit Entwicklungsumgebungen und Blockchain-Details auseinandersetzen zu müssen.

Zu diesem Zweck dienen „dezentrale Applikationen“, sogenannte DApps. Darunter versteht man die Kombination aus einem Smart Contract und einem Frontend, das eine Bedienoberfläche zur Verfügung stellt. Viele DApps nutzen Weboberflächen, die man im Browser öffnet. Mangels nativer Unterstützung in den meisten Browsern dient eine Browsererweiterung wie MetaMask als Bindeglied zwischen beiden Welten.

Zu low-level

Aber warum braucht es überhaupt so ein Frontend, können Smart Contracts nicht einfach ein Bedieninterface anzeigen? Das grundlegende Problem ist, dass kompilierte und auf der Blockchain platzierte Verträge aus bloßem Bytecode für die Ethereum Virtual Machine (EVM) bestehen. Die Blockchain bietet keine Möglichkeit, die Funktionen eines Vertrages direkt anzusprechen, und erst recht keine Schnittstellen, um etwa Bedienelemente zu rendern.

Wenn man eine Transaktion an einen Vertrag schickt, dann führt die EVM einfach den Vertrag ab der ersten Bytecode-Instruktion aus. Die einzige Möglichkeit, Benutzereingaben vorzunehmen, ist ein Datenfeld in der Transaktion, mit dem man einen Wert übertragen kann. Aber woher soll man wissen, wie ein bestimmter Smart Contract auf bestimmte Werte reagiert?

Um dieses Problem zu lösen, nutzt Ethereum ein standardisiertes Application Binary Interface (ABI, siehe Kasten). Das ABI definiert, welche Funktionen ein Vertrag kennt und wie man Funktionsname und Parameterwerte in das Datenfeld einer Transaktion kodieren muss. Das Ganze erinnert an die Header-Dateien von Sprachen wie C oder C++. Ethereum-Werkzeuge wie die Remix-IDE verstehen diesen Standard und können so auch fremde Smart Contracts aufrufen.

Das Application Binary Interface

Auf der Blockchain existieren Smart Contracts als Bytecode für die Ethereum Virtual Machine (EVM), den spezielle Compiler produzieren. Der Bytecode besteht aus einer reinen Abfolge von Instruktionen; von außen aufrufbare Schnittstellen oder Funktionsnamen gibt es nicht.

Allerdings kann ein Vertrag bei seiner Ausführung über spezielle Instruktionen auf die Daten zugreifen, die an eine aufrufende Transaktion angehängt sind. Ein standardisiertes Application Binary Interface (ABI) erlaubt, in diesen Daten sowohl den gewünschten Funktionsnamen als auch die zu übergebenden Parameterwerte zu kodieren. Ein EVM-Compiler stellt jedem kompilierten Programm ein Bytecode-Fragment voran, das zunächst diese Transaktionsdaten dekodiert und anschließend an die richtige Stelle im Vertragscode springt – dorthin, wo der Code der aufgerufenen „Funktion“ beginnt. Beim Kompilieren eines Vertrags können die Compiler auch eine JSON-Repräsentation des ABI erzeugen, die die Kodierung spezifiziert.

Als Beispiel betrachten wir die folgende Funktionsdefinition in Solidity, der verbreitetsten EVM-Sprache:

function f(uint8 x) public

Die zugehörige JSON-Spezifikation, die der offizielle Solidity-Compilter per solc --abi program.sol erzeugt, sieht wie folgt aus:

{
  "inputs": [{
    "internalType": "uint8",
    "name": "x",
    "type": "uint8"
  }],
  "name": "f",
  "outputs": [],
  "stateMutability": "nonpayable",
  "type": "function"
}

Mit so einer JSON-Beschreibung hat man alles Nötige, um einen kompilierten Smart Contract anzusprechen, der den ABI-Standard einhält. Zum Aufruf einer bestimmten Funktion berechnet man die ersten vier Bytes eines kryptografischen Hashs aus dem Funktionsnamen (im Beispiel f) und den Parametertypen (uint8). Daran hängt man die übergebenen Parameterwerte in einer standardisierten binären Kodierung an und schreibt all das in das Datenfeld der Transaktion. Üblicherweise macht man das aber nicht von Hand, sondern nutzt Bibliotheken wie web3.js, die den Prozess automatisieren. Wenn ein Vertrag durch eine Transaktion mit einem so spezifizierten Funktionsaufruf angesprochen wird, dekodiert er ihn und springt an die passende Stelle im Code.

Nutzerfreundlichkeit

Allerdings ist auch der Umgang mit einem ABI nichts, was man Endnutzern zumuten kann. Daher gibt es DApps, die diese Aufgabe übernehmen, eine schöne Oberfläche anzeigen und die technischen Details so verstecken, dass man Smart Contracts genauso einfach benutzen kann wie andere Software auch.

Technisch gleichen webbasierte DApps weitgehend gewöhnlichen Webseiten, bestehend aus HTML, CSS und JavaScript. Im Unterschied zu herkömmlichen Seiten ist das Backend einer DApp aber kein Server, der irgendwo gehostet wird, sondern eben ein Smart Contract auf der global verteilten Blockchain. In einem Browser, der – zum Beispiel via MetaMask – Ethereum-DApps unterstützt, steht ein zusätzliches Objekt namens window.ethereum zur Verfügung. Darüber kann die DApp mit der Blockchain kommunizieren. So funktioniert zum Beispiel auch die Remix IDE, die auf diese Art Smart Contracts auf der Blockchain platziert und aufruft.

Um die Interaktion mit der Blockchain zu vereinfachen, gibt es Bibliotheken, die oft „web3“ im Namen tragen – eine Anspielung auf das (mehr oder weniger hypothetische) Web 3.0, in dem Daten nicht auf zentralisierten Servern sondern in der Blockchain gespeichert werden. Ein JavaScript-Framework, das sich gut für DApps im Browser eignet, ist „web3.js“. Damit lässt sich recht einfach eine Oberfläche für den erwähnten Schere-Stein-Papier-Vertrag bauen. Sie können den – ausführlich kommentierten – Vertrag hier herunterladen. Er ist ein wenig erweitert worden, um einige im vorherigen Teil angesprochene Unzulänglichkeiten zu beheben. Unter anderem müssen jetzt fairerweise beide Spieler einen Spieleinsatz leisten.

Auf gehts

Laden Sie das Archiv mit dem Projektcode herunter und entpacken Sie es. Die Schere-Stein-Papier-DApp besteht aus zwei HTML-Seiten samt JavaScript-Code: eine zur Initialisierung, die den Vertrag auf der Blockchain platziert, (deploy.html und deploy.js) und eine für das tatsächliche gemeinschaftliche Spielen (play.html und play.js). Beide HTML-Seiten nutzen außerdem das CSS-Stylesheet styles.css und ein paar Hilfsfunktionen aus der Datei common.js. Die web3.js-Bibliothek lädt die Seiten automatisch von unpkg.com herunter.

Einer der beiden Spieler muss den Vertrag über deploy.html auf die Blockchain laden, danach können beide über play.html miteinander spielen. Diese Aufteilung ist technisch nicht zwingend notwendig, aber sie vereinfacht das Beispielprojekt. Ebenfalls der Einfachheit halber verwenden wir Node.js und den zugehörigen Paketmanager npm. Beides müssen Sie installieren, wenn Sie diesem Artikel eins zu eins folgen wollen. Wer etwas Erfahrung mit Webtechniken hat, kann die Anleitung aber auch anpassen.

Navigieren Sie zunächst im Terminal in das Verzeichnis mit dem entpackten Archiv und rufen Sie das Kommando npm install auf, um die nötigen Abhängigkeiten zu installieren. Das sind ein Solidity-Compiler (solcjs) und ein Webserver zum lokalen Testen (live-server) – allerdings haben auch diese Pakete Abhängigkeiten mit weiteren Abhängigkeiten und so weiter. npm kümmert sich automatisch darum, dass alles Nötige zusammenkommt.

Der Live Server dient dazu, die DApp per HTTP auf localhost bereitzustellen. Wenn Sie npm oder den Live Server nicht mögen, können Sie auch einen beliebigen anderen Webserver nutzen. Was leider nicht klappt, ist die DApp einfach als lokale Datei im Browser zu öffnen. Sicherheitsbeschränkungen für Browser-Extensions verhindern, dass MetaMask mit lokalen Dateien kommunizieren kann. Den Solidity-Compiler brauchen Sie nur, wenn Sie Änderungen am Vertragscode vornehmen wollen. Auch hier ist die npm-Variante lediglich ein Vorschlag von uns, jeder andere Solidity-Compiler sollte ebenfalls funktionieren.

Wenn Sie den Live Server nutzen, können Sie ihn per npm run start im entpackten Archiv aufrufen. Das Kommando öffnet auch gleich Ihren Standardbrowser, kontaktiert den lokalen Server und navigiert zu deploy.html. Praktischerweise lädt der Live Server bei jeder Änderung an den Dateien automatisch das Browser-Tab neu. Dadurch sieht man die Auswirkungen von Änderungen direkt, was das Programmieren bequem macht. Sie können den Server wieder stoppen, indem Sie im Terminal, aus dem Sie ihn gestartet haben, Strg+C drücken.

Das Archiv enthält sowohl den Solidity-Quelltext des Smart Contracts (game.sol) als auch dessen Kompilat (game_sol_RockPaperScissors.bin) und die JSON-Repräsentation des ABI (game_sol_RockPaperScissors.abi). Wenn Sie keine Änderungen am Vertragscode vornehmen möchten, brauchen Sie nichts weiter zu tun. Andernfalls lässt sich der Vertrag per npm run compile neu kompilieren.

Verbindung aufbauen

Bevor man web3.js benutzen kann, muss man zunächst eine Verbindung zum Browser-Plugin MetaMask herstellen. Starten Sie den Live Server per npm run start, wenn er nicht schon läuft. Im Browser sollten Sie unter http://127.0.0.1:8080/deploy.html die Deploy-Seite mit einem Button „Connect!“ sehen. Der Button verweist per onclick-Attribut auf die Funktion connectToMetaMask(). Attribute wie onclick mischen HTML- und JavaScript-Code und sind deshalb eigentlich keine gute Praxis, aber sie halten das Beispielprojekt schlank. connectToMetaMask() ist in common.js definiert und besteht im Kern aus diesen beiden Aufrufen:

await window.ethereum.request({ method: 'eth_requestAccounts' });
window.web3 = new Web3(window.ethereum);

window.ethereum.request() stellt eine Verbindung zu MetaMask her. Beim ersten Aufruf fragt MetaMask, auf welche Ethereum-Accounts die DApp zugreifen darf. Zum Experimentieren empfiehlt es sich, Accounts in Ethereum-Testnetzen wie Ropsten anzulegen und diese für die DApp zu verwenden. Auf diese Weise müssen Sie nicht mit echtem Geld spielen.

Per Klick auf „Connect!“ baut die DApp eine Verbindung zu MetaMask auf. Die Browsererweiterung fragt sicherheitshalber nach, welche Ethereum-Accounts freigegeben werden sollen.
Per Klick auf „Connect!“ baut die DApp eine Verbindung zu MetaMask auf. Die Browsererweiterung fragt sicherheitshalber nach, welche Ethereum-Accounts freigegeben werden sollen.

Die zweite Zeile im Listing oben initialisiert web3.js mit der MetaMask-Verbindung. So eine Verbindung ist übrigens immer nur im aktuellen Browser-Tab gültig. Auch bei einem Neuladen der Seite (egal ob manuell oder vom Live Server ausgelöst) müssen Sie erneut auf „Connect“ klicken.

Ans Eingemachte

Der erste Spieler unseres Schere-Stein-Papier-Spiels ist immer der aktuell in MetaMask ausgewählte Account. Spieler 2 müssen Sie manuell festlegen, indem Sie die Ethereum-Adresse seines Accounts angeben. deploy.html bietet dafür das Eingabefeld „Player 2 address“. Sie müssen das auch machen, wenn Sie die DApp im selben Browser mit zwei Ethereum-Accounts testen, denn aus Sicherheitsgründen gestattet MetaMask keinen gleichzeitigen Zugriff auf mehrere Accounts.

Unter dem Eingabefeld sehen Sie den Button „Deploy!“, der den Vertrag auf der Blockchain platziert. Er ruft dazu die Funktion deployContract() auf, die Sie in der Datei deploy.js finden. Der Funktionsrumpf ist simpel:

async function deployContract() {
  try {
    // ...
  }
  catch (err) {
    show("dply-fail");
    throw err;
  }
}

Die Funktion ist mit async markiert, wodurch man den Operator await in der Funktion benutzen darf. Jegliche Kommunikation mit MetaMask und der Ethereum-Blockchain erfolgt asynchron, Ausdrücke mit await erlauben, das in einfacherem Code auszudrücken.

Das Grundgerüst der Funktion sieht vor, dass die eigentlichen Instruktionen innerhalb des try-Blocks ausgeführt werden. Im Fehlerfall blendet show() eine normalerweise unsichtbare Nachricht ein. Über throw err wird der genaue Fehler in der Web-Konsole des Browsers geloggt.

Die ersten Instruktionen im try-Block sind folgende:

const player1 = await getAccount();
const player2 = document.getElementById("player2").value;

Damit werden die Ethereum-Adressen der beiden Spieler extrahiert. Die Funktion getAccount() finden Sie wieder in common.js. Sie ruft per web3.js die verfügbaren Accounts ab. Genau ein Account sollte verfügbar sein und wird von der Funktion zurückgegeben. Die Adresse von Spieler 2 wird einfach dem Textfeld entnommen.

Um den eigentlichen Smart Contract im Browser zu verwalten, muss zunächst die ABI-Definition aus der Datei game_sol_RockPaperScissors.abi geladen werden. Das erledigt die Funktion fetchABI(), die Sie wieder in common.js finden und die im Grunde nur das von modernen Browsern unterstützte Fetch-API nutzt, um die Datei vom Server zu laden. Anschließend wird die JSON-Repräsentation des ABI geparsed und von web3.js in ein Vertrags-Objekt konvertiert:

const abi = await fetchABI();
const contract = new window.web3.eth.Contract(JSON.parse(abi));

Vertrag auf die Blockchain

So lässt sich der Smart Contract allerdings noch nicht benutzen. Das ABI ist ein Interface und beschreibt nur die Schnittstellen des Vertrages. Es enthält nicht den eigentlichen Code. Den lädt die Hilfsfunktion fetchContract() – ganz analog zu fetchABI() – aus der Datei game_sol_RockPaperScissors.bin, die den Bytecode in hexadezimaler Schreibweise enthält:

const bin = await fetchContract();
const tx = contract.deploy({
  data: `0x${bin}`,
  arguments: [player2]
});

Diesem Bytecode wird „0x“ vorangestellt, um ihn als hexadezimal notierten Wert auszuzeichnen. Anschließend wird er zusammen mit der Adresse des zweiten Players an die deploy()-Methode des Vertragsobjekts übergeben. Spieler 1 muss man nicht spezifizieren, weil der Vertrag selbst automatisch denjenigen Account als Spieler 1 definiert, der den Smart Contract auf die Blockchain lädt. Die Adresse von Spieler 2 erwartet der Vertrag dagegen als Konstruktor-Parameter, weshalb sie hier im arguments-Array übergeben wird. (Auch dieser Wert muss mit „0x“ beginnen, aber so sollte er schon im Textfeld angegeben werden.)

Die deploy()-Methode gibt ein Transaktionsobjekt tx zurück. Zu diesem Zeitpunkt wurde die Transaktion aber noch nicht an das Ethereum-Netzwerk übermittelt, und der Vertrag befindet sich noch nicht auf der Blockchain. Das ändert dieser Code:

show("dply-pend");
const receipt = await tx.send({
  from: player1,
  value: stake
});

Zuerst zeigt show() eine Warte-Meldung an, weil Transaktionen mitunter etwas Zeit brauchen. Der value-Parameter gibt an, welcher Betrag als Spieleinsatz überwiesen wird. Sie können den Wert in common.js anpassen; standardmäßig haben wir 1 Finney (0,001 Ether) festgelegt. Beim Aufruf von send(), erscheint erneut eine Rückfrage von MetaMask, ob die Transaktion abgeschickt werden soll. Sollten Sie die Bestätigung verweigern, wirft MetaMask eine Exception und die DApp loggt einen Fehler.

Der letzte Schnipsel in deploy.js holt sich die Adresse des neu erzeugten Vertrags und zeigt diese in einer Erfolgsmeldung an. Außerdem wird die Adresse als Parameter an die URL des Play-Links angefügt:

const addr = receipt.options.address;
document.getElementById("play-link").href = `play.html?addr=${addr}`;
document.getElementById("dply-succ").innerHTML = `Deployed contract at <code>${addr}</code>!`;
show("dply-succ");

Ausprobieren

Zum Ausprobieren klicken Sie auf „Connect!“, wenn Sie das noch nicht getan haben. Bei Erfolg erscheint die Meldung „Connected“. Kopieren Sie anschließend die Adresse von Spieler 2 in die Textbox. Wie erwähnt eignen sich zum Testen zwei Accounts in einem Testnetzwerk wie Ropsten gut. Sie können in so einem Fall die Adresse leicht kopieren, indem Sie in MetaMask auf den Spieler-2-Account wechseln, anschließend auf den Account-Namen klicken und zurück zum Account von Spieler 1 wechseln. Beim Klick auf den Account-Namen kopiert MetaMask die Adresse in die Zwischenablage, sodass Sie sie leicht ins Textfeld einfügen können.

Bereit zum Spiel: Die DApp zeigt an, unter welcher Adresse der Vertrag auf der Blockchain erreichbar ist.
Bereit zum Spiel: Die DApp zeigt an, unter welcher Adresse der Vertrag auf der Blockchain erreichbar ist.

Zuletzt klicken Sie in der DApp auf „Deploy!“, woraufhin die Warte-Meldung erscheint und MetaMask Sie bittet, die Transaktion abzusegnen. Wenn Sie das tun, passiert erstmal eine Weile nichts, aber nach spätestens einer Minute sollte die Transaktion von der Blockchain bestätigt werden. Die DApp zeigt dann die Erfolgsmeldung mit der Adresse des frisch gebackenen Vertrags an. Sie können die Adresse auch bei Diensten wie etherscan.io nachschlagen, um sich zu vergewissern, dass der Vertrag tatsächlich auf der Blockchain existiert. Achten Sie bei Etherscan darauf, das richtige (Test-)Netz auszuwählen, um den Vertrag zu finden.

Lasst die Spiele beginnen

Für eine Partie „Schere, Stein, Papier“ ist nun alles vorbereitet. Indem Sie auf den Play-Link unten in der DApp klicken, wechseln Sie zur Seite play.html. Die Adresse des Vertrages wird in der URL mit übertragen. Den Link, oder zumindest die Adresse, müssen Sie an den 2. Spieler weitergeben, damit er oder sie den Vertrag auf der Blockchain findet.

Über Etherscan kann man sich nicht nur vergewissern, dass der Vertrag tatsächlich auf der Blockchain liegt, sondern auch die Spiel-Transaktionen einsehen.
Über Etherscan kann man sich nicht nur vergewissern, dass der Vertrag tatsächlich auf der Blockchain liegt, sondern auch die Spiel-Transaktionen einsehen.

Auch play.html hat einen „Connect!“-Button, den man zuallererst drücken muss. Die Funktion ist dieselbe wie bei deploy.html. Als zweite Vorbedingung muss die Adresse des Vertrags angegeben werden, mit dem man spielen will. Die Seite bietet dafür ein Textfeld, das zwei kleine Codeschnipsel in common.js und play.js automatisch befüllen, falls eine Adresse in der URL steht.

Ein Klick auf „Select!“ wählt den Vertrag aus. Die zugehörige Implementierung erstellt ein Vertragsobjekt, ganz analog zur deployContract()-Funktion. Allerdings wird neben dem ABI diesmal die Vertragsadresse als zweiter Parameter übergeben. So weiß web3.js, wo der Vertrag auf der Blockchain liegt, und kann mit ihm interagieren:

window.contract = new window.web3.eth.Contract(JSON.parse(abi), address);

Damit das Vertragsobjekt in späteren Funktionen weiter benutzt werden kann, legt der Code es einfach im globalen window-Objekt ab. Dadurch hat sämtlicher JavaScript-Code innerhalb des gleichen Browser-Tabs Zugriff auf den Vertrag.

Bevor das eigentliche Spiel losgeht, muss der zweite Spieler noch seinen Teil des Wetteinsatzes überweisen, wofür ein Button bereitsteht. Die Implementierung der zugehörigen Funktion payStake() folgt dem oben erklärten try-catch-Schema:

try {
  show("pay-pend");
  const player = await getAccount();
  await contract.methods.pay().send({
    from: player,
    value: stake
  });
  show("pay-succ");
}
catch (err) {
  show("pay-fail");
  throw err;
}

Spätestens hier zeigen sich die Vorteile einer Bibliothek wie web3.js deutlich. Dank des ABIs stellt web3.js die Funktionen des Smart Contracts – hier pay() – 1:1 in JavaScript als Objektmethoden zur Verfügung. Man kann sie fast wie jede andere asynchrone Funktion aufrufen.

Spieler 2 muss übrigens den beim Deploy angegebenen Ethereum-Account nutzen, damit getAccount() auch diesen Account zurückgibt. Die DApp prüft das allerdings nicht und verhindert auch nicht, dass der erste Spieler versehentlich versucht, über „Pay!“ noch einmal seinen Einsatz zu überweisen. Allerdings akzeptiert der Smart Contract nur Geld von der angegebenen Spieler-2-Adresse. Transaktionen von anderen Adressen oder der Spieler-1-Adresse lehnt er ab. MetaMask kann das erkennen und warnt in so einem Fall, dass die Ausführung höchstwahrscheinlich fehlschlagen wird.

Die Implementierungen der weiteren Spielschritte folgen alle dem gleichen Muster. In der nächsten Phase („Commit“) müssen die Spieler eine Zahl aus 1, 2 oder 3 wählen, was für Schere, Stein oder Papier steht. play.html nutzt dafür ein Dropdown-Element, so können Spieler statt der Zahlen aus den bekannten Wörtern wählen. Außerdem müssen sie ein geheimes Kennwort eingeben. Aus dem Spielzug und dem Kennwort errechnet die DApp einen Hashwert und sendet ihn an den Vertrag. Details dieses Commitment-Verfahrens sind in Teil 3 dieser Serie beschrieben.

Die zugehörige JavaScript-Funktion commit() ähnelt der pay()-Funktion. Kern ist wieder der asynchrone Aufruf der passenden Vertragsmethode. Vorher wird über die externe Bibliothek jsSHA und die Hilfsfunktion sha256() in common.js der Hash errechnet:

const player = await getAccount();
const hash = sha256(choice + secret);
await contract.methods.commit(`0x${hash}`).send({
  from: player,
  value: 0
});

Der Einfachheit halber schert sich sha256() nicht um Text-Encoding. Sie sollten für das Kennwort deshalb nur ASCII-Zeichen verwenden. Der Hash ist ein hexadezimaler String, also stellt der Code ihm „0x“ voran, bevor er der commit()-Methode des Vertragsobjekts übergeben wird. Anschließend setzt send() die Transaktion direkt ab und await wartet die (erfolgreiche) Ausführung ab.

Anders als bei der Einzahlungsmethode pay() dient diese Transaktion nur der Kommunikation mit dem Vertrag und nicht dem Überweisen von Geld. Der Wert der Transaktion wird daher auf 0 gesetzt. Allerdings fallen trotzdem Transaktionsgebühren an, die MetaMask im Bestätigungsdialog kalkuliert und anzeigt.

Zu guter Letzt folgen noch die verbleibenden Schritte des Spiels, „Reveal“ und „Finish“, für die play.html ebenfalls Buttons bietet. Die Implementierungen dieser Funktionen sind recht simpel; es müssen keine weiteren Eingaben abgefragt und lediglich die zugehörigen Funktionen des Vertrags aufgerufen werden:

// In Funktion reveal():
await contract.methods.reveal(+choice, secret).send({
  from: player,
  value: 0
});

// In Funktion finish():
await contract.methods.finish().send({
  from: player,
  value: 0
});

Veröffentlichung

Jetzt hat die DApp alles Nötige, damit zwei Spieler im Browser Schere–Stein–Papier spielen können. Sie können das mit wechselnden Ethereum-Accounts in einem Browser testen. Allerdings verliert man so leicht den Überblick, wessen Spielzug mit welchen Eingaben als Nächstes kommt – obwohl der Smart Contract ungültige Züge blockiert und MetaMask dadurch vor solchen Zügen warnt.

Besser ist es, zum Ausprobieren zwei getrennte Browser-Instanzen zu benutzen – oder gleich einen echten Spielpartner. Die beiden Spieler müssen nämlich keineswegs am gleichen Rechner sitzen, oder auch nur mit derselben Instanz der DApp kommunizieren. Die „serverseitige“ Logik ist der Vertrag auf der Blockchain; nichts spricht dagegen, dass beide Spieler jeweils einen eigenen Webserver mit eigener Instanz der DApp betreiben. Sie müssen nur mit der gleichen Vertragsadresse kommunizieren. Die muss Spieler 1 irgendwie Spieler 2 zukommen lassen – etwa per Mail oder Chatnachricht – damit der sie bei „Select contract address“ angeben kann. Es ist einer der Grundpfeiler des Web 3.0, dass jeder seine eigenen Server betreiben kann und nur die verteilte Ethereum-Blockchain als Koordinator dient.

Mit zwei Browserinstanzen lässt sich die DApp auch bequem auf einem einzelnen Rechner testen.
Mit zwei Browserinstanzen lässt sich die DApp auch bequem auf einem einzelnen Rechner testen.

Für echte DApps ist es trotzdem praktischer, wenn sie über eine Webseite online erreichbar sind – zumindest wenn man die DApp allgemein verfügbar machen will. Besonders für große DApps ergeben sich oft noch weitere praktische Probleme: Zum Beispiel ist das Schreiben von temporären Daten oder die Verwaltung von Nutzern auf der Blockchain ineffizient und oft praktisch unmöglich. Viele DApps greifen deshalb für nicht-systemkritische Zusatzfunktionen auf traditionelle Server und Datenbanken zurück. Für das Schere-Stein-Papier-Spiel würde sich praktisch jeder Hoster eignen, schließlich muss er nur ein paar Dateien ausliefern.

Fazit

Mit einer Handvoll HTML und JavaScript lassen sich Webapplikationen auf Basis von Smart Contracts umsetzen. Um die Verwaltung der Blockchain kümmern sich web3.js und MetaMask, was Entwicklung und Betrieb solcher DApps stark vereinfacht.

Der Weg von unserem Beispiel zu einer wirklich guten DApp ist allerdings noch lang. Nutzereingaben werden zum Beispiel nicht überprüft und Fehlbedienungen nicht verhindert. Ungültige Aktionen scheitern erst am Smart Contract. Der muss sie blockieren, schließlich ist nur der Vertrag der manipulations- und ausfallsichere Teil der Applikation. Aber eine gute DApp verhindert trotzdem, dass verwirrte Nutzer Gebühren für Transaktionen verschwenden, die dann vom Vertrag abgelehnt werden.

Auch eine ordentliche Fehlerbehandlung fehlt der DApp noch genauso wie eine sinnvolle Nutzerführung. Für letzteres müsste die DApp allerdings wissen, in welchem Zustand sich der Vertrag gerade befindet – und dafür müssten Sie dem Vertrag eine neue Funktion spendieren, die diese Information liefert. Es gibt also viel zu experimentieren, die Dokumentationen von MetaMask und web3.js helfen weiter. Wer klein anfangen will, kann zuerst einen zusätzlichen Button für die abort()-Funktion des Vertrages in die Play-Seite einbauen. Damit können Nutzer den Vertrag abbrechen, wenn sich ihr Spielpartner weigert, weiterzuspielen.

TAGS