Diese Projektarbeit befasst sich mit der Erstellung einer dezentralen Anwendung (DApp) für das Spiel "Schere, Stein, Papier". Sie umfasst die Implementierung der App (Quellcode, Deployment, Frontend) sowie die Dokumentation der Erstellung (Anforderungen, Lösungsstrategien, Softwarearchitektur).
Die Aufgabenstellung bestand darin, das Spiel "Schere, Stein, Papier" mithilfe einer DApp zu implementieren, dessen Spiellogik und Nutzerverwaltung von einem in der Programmiersprache Solidity geschriebenen Smart Contract auf dem Ropsten-Testnet der Ethereum Blockchain übernommen wird. Zugriff auf das Spiel sollte über eine Website erfolgen, die wiederum den Smart Contract bedienen sollte.
Im Vergleich zu einer webbasierten Lösung, die durch einen Hersteller zentral betrieben wird, bietet die Implementierung des Spiels als DApp mithilfe der Blockchain-Technologie den Vorteil, dass die Spieler weltweit ohne einen Intermediär direkt gegeneinander spielen können.
Die grundlegende Spiellogik wird im Folgenden geschildert: Beim Aufrufen der Weboberfläche wird das Browserplugin Metamask geöffnet und der Spieler aufgefordert, sich mit einem Account zu verbinden. Erst nach erfolgreichem Verbinden eines Accounts besteht die Möglichkeit, einem Spiel beizutreten, und die Spielersuche zu starten. Für das Beitreten wird eine Servicegebühr von 2.000 Wei erhoben. Wurde ein Gegenspieler gefunden, dürfen beide Spieler ihren Einsatz von 10.000 Wei setzen.
Sowohl Servicegebühr als auch Einsatz sind durch den Ersteller des Smart Contracts beliebig anpassbar. Anschließend wählen die Spieler ihren Spielzug, hashen diesen und veröffentlichen den Hashwert im nächsten Schritt. Nachdem dies erfolgt ist, veröffentlichen beide Spieler den unverschlüsselten Spielzug, sodass der Smart Contract diesen mit dem zuvor veröffentlichten Hashwert verifizieren und den Gewinner ermitteln und auszahlen kann.
Inhaltsverzeichnis
Inhaltsverzeichnis
Abbildungsverzeichnis
1. Einführung
1.1 Aufgabenstellung
1.2 Projektbeschreibung
2. Anforderungen und Umsetzung
2.1 Anforderungen aus Aufgabenstellung
2.1.1 Transparenz der Blockchain berücksichtigen
2.1.2 Zentrale Rolle des Smart Contracts
2.1.3 Vorzeitigen Spielerausstieg berücksichtigen
2.1.4 Verwendung des Factory Patterns
2.1.5 Möglichkeit für Servicegebühr und Wetteinsatz
2.1.6 Nutzerschnittstelle via lokalem Webserver
2.2 Weiterführende Anforderungen
2.2.1 Verwaltung elementarer Spielfunktionalitäten
2.2.2 Intuitive Nutzerführung
2.2.3 Verbesserte Fehlersuche
3. Softwarearchitektur
4. Alternative Lösungsansätze
4.1 Vorkehrungen für Ausstieg beider Spieler
4.2 Wetteinsatz dynamisch von den Spielern bestimmen
4.3 Optimiertes Commit Reveal Schema
4.4 Alternative Blockchain
5. Fazit
Installationsanleitung
Literaturverzeichnis
Anlagen
Anmerkung der Redaktion: Die Installationsdateien sind nicht im Lieferumfang enthalten.
Abbildungsverzeichnis
Abbildung 1: play() Funktion des Smart Contracts
Abbildung 2: reveal() Funktion des Smart Contracts
Abbildung 3: startGame() Funktion des Smart Contracts
Abbildung 4: getWinner() Funktion des Smart Contracts
Abbildung 5: pay() Funktion des Smart Contracts
Abbildung 6: playTimeout() Modifier des Smart Contracts
Abbildung 7: revealPhaseEnded() Modifier des Smart Contracts
Abbildung 8: validFee() Modifier des Smart Contracts
Abbildung 9: validBet() Modifier des Smart Contracts
Abbildung 10: JavaScipt Funktion bei Ausführen eines Spielzugs
Abbildung 11: Hilfsfunktionen ausschließlich vom Smart Contract Owner auszuführen
Abbildung 12: onlyOwner() Modifier des Smart Contracts
Abbildung 13: JavaScript Funktion zum Abfangen des Game() Events
Abbildung 14: Klassendiagramm des Factory Patterns für das Spiel Schere, Stein, Papier
1. Einführung
Im Rahmen des Moduls „Blockchain 3“ des Masterstudiengangs „Blockchain und Distributed Ledger Technologies“ der Hochschule Mittweida, wurde den Studierenden als Prüfungsleistung auferlegt, eine Dapp zu programmieren und ihr Vorgehen in Form einer Belegarbeit zu dokumentieren. Das vorliegende Dokument stellt diese Belegarbeit dar.
1.1 Aufgabenstellung
Die Aufgabenstellung bestand darin das Spiel „Schere, Stein, Papier“ mithilfe einer DApp zu implementieren, dessen Spiellogik und Nutzerverwaltung von einem in der Programmiersprache Solidity geschriebenen Smart Contract auf dem Ropsten-Testnet übernommen wird. Zugriff auf das Spiel sollte über eine Website erfolgen, die wiederum den Smart Contract bedienen sollte. Die Anforderungen an die DApp werden in Kapitel 2 detaillierter Beschrieben.
1.2 Projektbeschreibung
Das Spiel „Schere, Stein, Papier“ wird klassischerweise von zwei Spielern mit den Händen gespielt. Auf ein gemeinsames Signal formen beide Spieler gleichzeitig ihre Hände zu entweder einer Schere, einem Stein oder einem Papier. Abhängig von den gewählten Formen beider Spieler kann der Gewinner ermittelt werden:
-Stein schlägt Schere
-Schere schlägt Papier
-Papier schlägt Stein
Sollten beide Spieler dieselbe Form gewählt haben, endet das Spiel unentschieden (vgl. World Rock Paper Scissors Association, n.d.).
Wie bei allen Spielen, bei denen ein Gewinner hervorgehen kann, besteht die Möglichkeit, Geld auf den eigenen Sieg zu wetten, wobei dem Gewinner der Einsatz des Gegenübers ausgezahlt wird. Die Implementierung des Spiels als DApp mithilfe der Blockchain Technologie würde es Spielern weltweit ermöglichen, gegeneinander zu spielen. Im Vergleich zu einer webbasierten Lösung, die durch einen Hersteller zentral betrieben wird, bietet die DApp den Vorteil, dass die Spieler ohne einen Intermediär direkt gegeneinander spielen könnten.
Beim Aufrufen der Weboberfläche wird das Browserplugin Metamask geöffnet und der Spieler aufgefordert sich mit einem Account zu verbinden. Erst nach erfolgreichem Verbinden eines Accounts, besteht die Möglichkeit einem Spiel beizutreten und die Spielersuche zu starten. Für das Beitreten wird eine Servicegebühr von 2.000 Wei erhoben. Wurde ein Gegenspieler gefunden, dürfen beide Spieler ihren Einsatz von 10.000 Wei setzen. Sowohl Servicegebühr als auch Einsatz sind durch den Ersteller des Smart Contracts beliebig anpassbar. Anschließend wählen die Spieler ihren Spielzug, hashen diesen und veröffentlichen den Hashwert im nächsten Schritt. Nachdem dies erfolgt ist, veröffentlichen beide Spieler den unverschlüsselten Spielzug, sodass der Smart Contract diesen mit dem zuvor veröffentlichten Hashwert verifizieren und den Gewinner ermitteln kann. Diese Vorgehensweise nennt sich Commit-Reveal Schema, auf das in Kapitel 2.1.1.1 detaillierter eingegangen wird.
2. Anforderungen und Umsetzung
Nachdem der Projektumfang einleitend erläutert wurde, werden im weiteren Verlauf die konkreten Anforderungen an das Spiel aufgenommen und Lösungsansätze vorgestellt, die diese Anforderungen erfüllen. Zu beachten ist, dass bei der Entwicklung der DApp lediglich die notwendigen Anforderungen aus der Aufgabenstellung sowie einige weitere Anforderungen, die als essenziell für eine erfolgreiche Umsetzung des Spiels gesehen wurden, umgesetzt wurden. Für die Entwicklung einer DApp, die auf dem Markt konkurrenzfähig ist und Akzeptanz bei einer kritischen Masse an Menschen erhält, müssten weitaus umfangreichere Anforderungen definiert und umgesetzt werden. Vor dem Hintergrund des vorgegebenen limitierten Umfangs dieser Belegarbeit, wird im Folgenden lediglich auf die wichtigsten Anforderungen eingegangen und anhand von Beispielen im Quellcode deren Umsetzung exemplarisch erläutert.
2.1 Anforderungen aus Aufgabenstellung
Dieses Kapitel beschäftigt sich zunächst mit den in der Aufgabenstellung aufgeführten Anforderungen. Die Vorgaben werden im Folgenden zunächst analysiert und in konkrete funktionale Anforderungen übersetzt, für die jeweils anschließend die gewählte Umsetzung beschrieben wird.
2.1.1 Transparenz der Blockchain berücksichtigen
Eine der zentralen Anforderungen aus der Aufgabenstellung ist, dass die DApp so programmiert werden soll, dass kein Spieler einen Vorteil aus der öffentlichen Zugänglichkeit der Blockchain ziehen kann. Es darf für keinen der Spieler möglich sein, herauszufinden, welchen Spielzug der Gegenspieler gespielt hat, bevor man selbst seinen Spielzug getätigt hat. Darüber hinaus sollte es für die Spieler nicht möglich sein, über eine Re-Entrancy Attack wiederholt Geld vom Smart Contract abzuheben.
2.1.1.1 Commit-Reveal Schema
Da die Daten auf der Blockchain zu jedem Zeitpunkt öffentlich zugänglich sind und eingesehen werden können, besteht eine Herausforderung darin zu verhindern, dass ein Spieler auf den Spielzug des Gegners wartet, diesen auf der Blockchain nachschaut und anschließend den eigenen Spielzug vorteilhaft wählt. Die Lösung für diese Herausforderung stellt das Commit-Reveal Schema dar. Hierbei wählen in der Commit Phase zunächst beide Spieler ihren Spielzug aus und hashen diesen zusammen mit einem gewählten Passwort. Der Hashwert ist eindeutig und kann auf Basis aktueller Verschlüsselungsstandards ausschließlich aus der Kombination von Spielzug und Passwort effizient generiert werden. Es ist nicht möglich aus dem Hashwert den gewählten Spielzug zurückzuverfolgen. Das bedeutet, dass beide Spieler ihren Spielzug tätigen und „einloggen“ können, ohne dass es für den Gegenspieler möglich ist mithilfe der Daten auf der Blockchain herauszufinden, was gespielt wurde. Die Smart Contract Funktion play() speichert die von den Spielern gespielten Spielzüge, die von JavaScript gehasht und verschlüsselt an den Smart Contract übergeben wurden (s. Abbildung 1).
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 1: play() Funktion des Smart Contracts
Anschließend startet die Reveal Phase, in der die Spieler ihren Spielzug sowie das gewählte Passwort veröffentlichen und zeigen, dass der damit erzeugte Hashwert genau dem Hashwert entspricht, der in der Commit Phase veröffentlicht wurde. Die Smart Contract Funktion r eveal() nimmt als Input Parameter die klaren Spielzüge der Spieler, hasht diese und prüft, ob die Werte mit denen der in der play() Funktion gespeicherten Hashwerten übereinstimmen (s. Abbildung 2). Ist dies der Fall, wird der Spielzug zurückgegeben. Solange die Hashwerte nicht übereinstimmen, wird Moves.None zurückgegeben, was bedeutet, dass ein Spieler noch nicht revealt hat. Die Modifier isRegistered() und commitPhaseEnded() prüfen, ob beide Spieler bereits einen Spielzug getätigt haben und auch diejenigen Spielteilnehmer sind, für die das Spiel kreiert wurde. Die Reveal Phase gilt als beendet, sobald beide Spieler den korrekten Spielzug veröffentlicht haben.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 2: reveal() Funktion des Smart Contracts
2.1.1.2 Re-entrancy Attack
Bei der Re-entrancy Attack macht sich ein Angreifer die fallback() Funktion in Solidity zunutze, die bei jedem Smart Contract als default vorhanden ist. Dafür wird diese Funktion als payable überschrieben, sodass sie Ether empfangen kann und innerhalb der fallback() Funktion eine weitere Funktion ausgeführt, die Ether aus dem auszunutzenden Smart Contract abhebt. Da die Überweisungsfunktion des auszunutzenden Contracts erst nach Terminierung der fallback() Funktion des ausnutzenden Smart Contracts terminiert, wird somit eine rekursive Schleife erzeugt, die immer wieder Ether abhebt.
Dieser Attacke wurde vorgebeugt, indem innerhalb der Funktion pay(), die die Auszahlung an den Gewinner ausführt und in Kapitel 2.1.2.2 im Detail beschrieben ist, vor der Auszahlung alle für die Auszahlung relevanten Spielvariablen zurückgesetzt werden. Das hat zur Folge, dass der Versuch einer Re-entrancy Attack scheitern würde, da die Adressen der Spieler, die am Spiel teilgenommen haben, der pay() Funktion bei erneutem Aufruf nicht mehr bekannt sind und damit kein erneuter Transfer möglich ist. Damit die initiale Ausschüttung des Gewinns dennoch gelingt, werden vor der Ausführung der reset() Funktion die Spieleradressen in einer separaten Variable gespeichert.
2.1.2 Zentrale Rolle des Smart Contracts
Eine weitere Anforderung an die DApp ist, dass der Smart Contract die Spiellogik und die Nutzerverwaltung enthalten, in der aktuellen Version von Solidity programmiert und auf dem Ropsten Testnet deployt werden soll. Hieraus lassen sich die funktionalen Anforderungen ableiten, dass sich die Spieler beim Smart Contract anmelden müssen und dieser eine interne Zuordnung von Spielern vornimmt. Darüber hinaus sollen alle Eingaben, die eine Relevanz für die Spiellogik haben, an den Smart Contract gesendet werden, der diese Daten verarbeitet, die Entscheidung über Sieg, Niederlage oder Unentschieden fällt und anschließend die Gewinnausschüttung durchführt.
2.1.2.1 Nutzerverwaltung
Die Nutzerverwaltung wird mithilfe einer queue Variable realisiert, die die Adresse des ersten Spielers speichert, der die Funktion startGame() aufruft und ein Spiel starten möchte. Sobald ein weiterer Spieler diese Funktion aufruft, wird mit der in der queue Variable gespeicherten und der aufrufenden Adresse ein neues Spiel gestartet und die queue Variable wieder geleert, um weitere Spieler aufnehmen zu können. Die Modifier validFee() und notAlreadyRegistered() prüfen, ob die entsprechende Servicegebühr für den Smart Contract Betreiber mitgesendet wurde und dass sich ein Spieler nicht bereits in der Warteschlange befindet, da es nicht möglich sein sollte gegen sich selbst zu spielen (s. Abbildung 3).
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 3: startGame() Funktion des Smart Contracts
2.1.2.2 Spiellogik
Die Spiellogik wird im Smart Contract mit den Funktionen play(), reveal(), getWinner() und pay() realisiert. Auf die Funktionen play() und r eveal() wurde bereits in Kapitel 2.1.1 genauer eingegangen. Die Funktion getWinner() ist eine view-Funktion, die ohne Gaskosten überweisen zu müssen, den Gewinner ermittelt (s. Abbildung 4). Dies hat den Vorteil, dass der Verlierer um seinen Verlust informiert wird und dies nicht erst bei Aufrufen der pay() Funktion erfährt und möglicherweise die Transaktionskosten für die Ausschüttung des Gewinns an den Gewinner bezahlen müsste. Mit dem Modifier revealPhaseEnded() wird garantiert, dass die Reveal Phase beendet ist und auch ein Gewinner ermittelt werden kann.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 4: getWinner() Funktion des Smart Contracts
Der Gewinner ruft anschließend die pay() Funktion auf, um sich auszahlen zu lassen. Die Funktion vergleicht in einem if-Statement die gespielten Spielzüge der Spieler und überweist dem Gewinner den Gewinn (s. Abbildung 5). Bevor die Auszahlung getätigt wird, werden wie in Kapitel 2.1.1.2 beschrieben, die Spieldaten mit reset() zurückgesetzt. Im Falle eines Unentschiedens, können sich beide Spieler ihren gesetzten Einsatz zurückzahlen lassen. Dabei wird die Adresse des Spielers, der die Funktion aufruft in der Variable paid gespeichert und anschließend der Einsatz ausgezahlt. Der Modifier UserNotAlreadyPaid() prüft bei Aufrufen der Funktion, ob sich der aufrufende Spieler nach einem Unentschieden bereits ausgezahlt hat, um damit auch hier eine erneute Auszahlung durch eine Re-entrancy Attack zu vermeiden. Der Weg des Modifiers wurde zusätzlich gewählt, da das Ausführen der reset() Funktion an dieser Stelle die Adressen der Spieler zurücksetzen würde und somit anschließend der zweite Spieler seinen Einsatz nicht mehr auszahlen lassen könnte. Auch falls die Auszahlung an den ersten Spieler fehlschlagen würde, besteht somit dennoch für den zweiten Spieler die Möglichkeit sich seinen Einsatz auszahlen zu lassen.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 5: pay() Funktion des Smart Contracts
Für die Auszahlung wurde die call() Funktion verwendet. Im Vergleich zu den beiden Alternativen transfer() und send(), wird bei call(), falls nicht anders definiert, das gesamte Gas an den Empfänger mitgesendet (Oettler, 2021). Dies ermöglicht zwar einerseits das Ausführen komplexen Codes auf Kosten des Senders, hat jedoch den Vorteil, dass nicht nur eine konstante Menge von 2.300 Gas mitgesendet wird, wie es bei send() und transfer() der Fall ist (vgl. ebenda). Sollten sich beispielsweise zukünftig die Gaskosten für das Ausführen von Code deutlich erhöhen, könnte es passieren, dass Auszahlungen, die mit send() bzw. transfer() umgesetzt wurden, fehlschlagen da 2.300 Gas nicht ausreichen könnten (vgl. ebenda).
2.1.3 Vorzeitigen Spielerausstieg berücksichtigen
Für den Fall, dass ein Spieler aus irgendeinem Grund das Spiel nicht beenden und vorzeitig aussteigen sollte, müssen Vorkehrungen getroffen werden. Diese Anforderung erfordert es, dass das Spiel beendet werden kann und ein Spieler nicht aufgrund von Fehlverhalten seines Gegenspielers auf seinem Einsatz sitzen bleibt, auch wenn sein Gegenspieler während des Spiels aussteigt. Die Servicegebühr wird bei dieser Betrachtung außen vorgelassen, da diese bereits mit Start der Spielersuche an den Smart Contract Betreiber übermittelt wird.
Sobald ein Spiel kreiert wurde und mindestens einer von beiden Spielern seinen Spielzug durchgeführt und seinen Einsatz gesetzt hat, muss eine Lösung gefunden werden, dass dieser Einsatz nicht verloren ist, sollte der Gegenspieler keinen Spielzug durchführen. Diese Anforderung wurde mit der Implementierung eines Timers umgesetzt. Sobald einer von beiden Spielern seinen Spielzug durchgeführt hat, wird die Blockzeit dieses Zeitpunkts gespeichert und ein Timer gestartet, der auf der Weboberfläche angezeigt wird und von fünf Minuten herunterzählt (s. Abbildung 1). Innerhalb dieser Zeit hat der Gegenspieler Zeit seinen Zug zu machen. Sollte der Gegenspieler keinen Spielzug machen, hat der erste Spieler die Möglichkeit sich seinen Einsatz mithilfe der payback() Funktion zurückzahlen zu lassen.
Der Modifier playTimeout() prüft anhand der Differenz der Blockzeiten den Status des Timers und ob nach dessen Ablauf einer von beiden Spielern noch keinen Spielzug durchgeführt hat (s. Abbildung 6). Es gilt zu beachten, dass die durchschnittliche Blockzeit auf der Ethereum Blockchain zwischen 12 und 14 Sekunden liegt (Wackerow, 2022). Aus diesem Grund wird sich der Wert der Blockzeit, auf deren Basis der Smart Contract die verstrichene Zeit misst, lediglich in diesem Zeitintervall ändern.
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 6: playTimeout() Modifier des Smart Contracts
Analog wurde für die Reveal Phase ein weiterer Timer implementiert, der startet, sobald ein Spieler seinen Spielzug veröffentlicht hat. Läuft die Zeit ab und es hat ein Spieler seinen Zug nicht veröffentlicht, wird der Gegenspieler als Gewinner festgelegt und kann sich seinen Gewinn auszahlen lassen. Die Abfrage, ob die Reveal Phase beendet ist, wurde mit dem Modifier revealPhaseEnded() gelöst (s. Abbildung 7).
Abbildung in dieser Leseprobe nicht enthalten
Abbildung 7: revealPhaseEnded() Modifier des Smart Contracts
[...]
- Quote paper
- Jannik Hehemann (Author), Dominik Bepple (Author), 2022, Das Spiel "Schere, Stein, Papier" als DApp. Entwicklung in der Programmiersprache Solidity, Munich, GRIN Verlag, https://www.grin.com/document/1304876
-
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X. -
Upload your own papers! Earn money and win an iPhone X.