Jacek Varky

Voraussichtliche Lesedauer: 10 Minuten

Techblog

Ethereum DApps mit Python statt Javascript

Sobald man sich mit der Entwicklung von DApps auf Ethereum auseinandersetzt, stellt man fest: Hier ist eindeutig JavaScript die bevorzugte Programmiersprache. JavaScript wird zum Beispiel in den meisten Tutorials verwendet, die erklären, wie ein Smart Contract geschrieben wird und wie mit diesem interagiert werden kann. Auch die Entwicklungsframeworks Truffle und Embark kommen in JavaScript daher.…

Techblog

Sobald man sich mit der Entwicklung von DApps auf Ethereum auseinandersetzt, stellt man fest: Hier ist eindeutig JavaScript die bevorzugte Programmiersprache. JavaScript wird zum Beispiel in den meisten Tutorials verwendet, die erklären, wie ein Smart Contract geschrieben wird und wie mit diesem interagiert werden kann. Auch die Entwicklungsframeworks Truffle und Embark kommen in JavaScript daher. Die Web3-Bibliothek für die Kommunikation mit einer Ethereum Node ist ebenfalls in JavaScript geschrieben. Ich schaue mir in diesem Blog Alternativen an, damit eine Backend-Komponente mit dem Ethereum-Netzwerk kommunizieren kann.

DApps auf Ethereum in Python

Eine Möglichkeit für das Programmieren von DApps auf Ethereum ist Python. Einige stellen sich bestimmt die Frage, warum man Python statt JavaScript verwenden sollte. Mein Grund ist recht simpel: Ich fühle mich in Python wohler. Deswegen wollte ich schauen, wie man eine DApp in Python entwickeln kann. Der Code kann dann zwar nicht im Browser ausgeführt werden, aber dass muss architekturbedingt auch gar nicht der Fall sein. Falls sich also jemand in Python wohler fühlt oder das Backend lieber in Python schreibt statt für Nodejs, dann bietet Python sehr gute Bibliotheken in diesen Zusammenhang mit an.

Python ist, im Gegensatz zu JavaScript, ist noch deutlich weniger verbreitet: Im Vergleich: Wir erhalten um die 100 Millionen Treffer einer Google-Suche mit den Suchworten „javascript ethereum“ bezeihungweise „ethereum javascript“; für Python kommen wir nur auf magere 16 Millionen.

Ist Python noch weniger gut geeignet? Oder nur zu Unrecht vernachlässigt? Wir wollen uns anschauen, welche Bibliotheken Python für das Deployen und Interagieren mit Ethereum Smart Contracts zur Verfügung stellt. Speziell werden wir uns anschauen, wie wir einen Smart Contract mit Python kompilieren, diesen in einem privaten Netzwerk deployen und damit interagieren können.

Um dieser Anleitung zu folgen, setzten wir voraus, dass der Zugriff zu einer Ethereum Node (private Node, Infura, etc.) zur Verfügung steht, und dass der Leser weiß, wie Bibliotheken in Python installiert werden.

pragma solidity >=0.4.24;
contract SimpleValue {
  uint256 value;
  constructor(uint256 _value) public {
    value = _value;
  }
 
  function set(uint256 _value) public {
    value = _value;
  }
 
  function get() public view returns (uint256) {
    return value;
  }
}

Abbildung 1: Einfacher Smart Contract zum Speichern eines Wertes

Wir werden den Smart Contract aus Abbildung 1 mit Python kompilieren, deployen, und dann damit interagieren:  Er speichert einen Wert, und er kann mit der set-Funktion verändert werden. Mit der get-Funktion können wir denn aktuellen Wert abfragen. Bevor wir den Smart Contract deployen können, müssen wir ihn einmal kompilieren. Dazu verwenden wir das py-solc-Paket (hier bei Github). Das py-solc-Paket ist ein Wrapper für den Solidity-Compiler und erlaubt uns, damit Python-Code zu schreiben, der Solidity-Code kompiliert.

Sobald das Paket installiert ist, können wir py-solc in unserem Python-Code verwenden (siehe unten bei Abbildung 2). In der ersten Zeile importieren wir die compile_files-Funktion. Diese Funktion nimmt eine Liste mit den Pfaden zu den Smart Contracts als Parameter auf und kompiliert diese, das sieht man in Zeile 4 der Abbildung 2. Als Ergebnis erhalten wir ein Dictionary mit allen wichtigen Contracts, die wir dem Compiler übergeben haben. Unter anderem enthält dieses Dictionary den Binary Code und das Application Binary Interface (ABI) für den Smart Contract. Da wir nur den SimpleValue Smart Contract kompiliert haben, entnehmen wir auch nur die jeweiligen Informationen aus dem Dictionary (siehe letzte Zeile Abbildung 2).

from solc import compile_files
 
#compile smart contract code
compiled = compile_files(['simpleContract.sol'])
 
#get all data about the contract
simplevalue_contract = compiled['simpleContract.sol:SimpleValue']

Abbildung 2: Der SimpleValue Smart Contract wird mit py-solc compiliert

Wir haben jetzt den Bytecode und das ABI des Smart Contracts. Da py-solc nur ein Wrapper zum Kompilieren von Smart Codes ist, kann es den Smart Contract nicht deployen. Dafür brauchen wir eine Verbindung zu einer Ethereum-Node (Geth oder Parity). Die Verbindung stellen wir in JavaScript mit der Web3js-Bibliothek her. In Python nutzen wir die Web3py-Bibliothek (siehe hier), welche dieselben Funktionen für Python zur Verfügung stellt wie Web3js für JavaScript.

Deployment

Nun erstellen wir mit Web3py ein Contract-Objekt, das die zuvor generierten ABI und Bytecode aufnimmt (Zeile 9 in Abbildung 3). Mit diesem Objekt können wir den Smart Contract in unserem Netzwerk deployen, egal, ob es ein privates Netzwerk ist, ein Ethereum-Testnet oder sogar das Mainnet.

Für das Deployment rufen wir zuerst die construct-Function auf und übergeben dieser die Parameter, die der Konstruktor des Smart Contracts erwartet. In unserem Fall einen uint256, weswegen wir den Wert 1337 übergeben. Damit auch eine Transaktion mit dem Bytecode und dem Parameter für den Konstruktor an die Node verschickt wird, müssen wir die transact-Funktion aufrufen (Zeile 15 in Abbildung 3). Diese erstellt eine Transaktion, worin alle wichtigen Felder gefüllt sind. Sobald die Transaktion von der Node angenommen wird, erhalten wir einen Hash. Er wird zum Identifizieren unserer soeben versendeten Transaktion verwendet.

Wir haben die Transaktion mit dem Bytecode an die Ethereum-Node verschickt. Sie nimmt die Transaktion auf, fügt diese zu einem Block hinzu und schürft diesen Block. Wenn der Block mit der Transaktion geschürft ist, ist der Smart Contract deployed und ist unter einer Adresse erreichbar. Mit der Adresse wissen wir, wo der Smart Contract sich befindet und sind damit in der Lage dessen Funktionen auszuführen. Je nach Netzwerk kann das Hinzufügen der Transaktion und das Minen des Blocks einige Zeit in Anspruch nehmen.

Um zu überprüfen, ob eine Transaktion geschürft wurde, bietet Web3py die getTransactionReceipt-Funktion an. Sie nimmt einen Hash-Wert entgegen und fragt bei der Ethereum-Node nach, ob die Transaktion mit dem Hash geschürft wurde. Ist die Transaktion noch nicht geschürft, erhalten wir None als Rückgabe. Sollte die Transaktion hingegen bereits in einem Block enthalten sein, dann bekommen wir ein AttributDict-Object als Rückgabe der Funktion. Mit diesem Wissen erstellen wir eine while-Schleife, die jede Sekunde erfragt, ob die Transaktion bereits geschürft wurde (Zeile 18–21 in Abbildung 3).

from web3.auto import w3
from time import sleep
from solc import compile_files
 
compiled = compile_files(['simpleContract.sol'])
simplevalue_contract = compiled['simpleContract.sol:SimpleValue']
 
# Instantiate
SimpleValue = w3.eth.contract(abi = simplevalue_contract['abi'],
   bytecode = simplevalue_contract['bin']
)
 
#and deploy the contract
print('Going to deploy the contract')
hash = SimpleValue.constructor(1337).transact()
 
mined = w3.eth.getTransactionReceipt(hash)
   while mined is None:
   sleep(1)
mined = w3.eth.getTransactionReceipt(hash)
print('Mined..., Address: {}'.format(mined['contractAddress']))

Abbildung 3 Python Code um Web3py Code erweitert

Wenn die Adresse des Smart Contracts ausgegeben wird, bedeutet das, dass der Block mit der Transaktion geschürft wurde. Das heißt auch: der Smart Contract wurde initialisiert, wir können nun damit interagieren.

Interaktion mit dem Smart Contract

Für die Interaktion mit dem Smart Contract erstellen wir ein neues Contract-Objekt – dieses Mal mit ABI und Adresse des neu angelegten Smart Contracts als Parameter (Zeile 1–4 in Abbildung 4). Mit diesem neuen Objekt rufen wir nun die Funktionen des Smart Contracts auf.

Um eine Smart Contract Funktion aufzurufen, gibt es zwei Möglichkeiten: call() oder transact().

call() sorgt dafür, dass der Funktionsaufruf auf einer Node lokal ausgeführt wird. In dem Fall wird die Funktion get() auf der mit uns verbundenen Node ausgeführt und das Ergebnis zurückgegeben. Genau das machen wir in Zeile 6 in Abbildung 4. Wir rufen die get()-Funktion zusammen mit der call()-Funktion auf. Damit erhalten wir das Ergebnis, das der SimpleValue Smart Contract aktuell speichert. In unserem Fall sollte dies die vorab definierte 1337 sein.

Mit der zweiten Möglichkeit, der transact()-Funktion, wird eine Transaktion erstellt, welche an eine Ethereum-Node geschickt wird und die dann vom gesamten Netzwerk verarbeitet wird. Durch das Aufrufen der Funktion set() durch eine Transaktion wird der neue Wert im Netzwerk gespeichert, so dass jeder, der die get()-Funktion aufruft, den neuen Wert sehen kann. Wird die set()-Funktion hingegen mit einem call() aufgerufen, dann wird die Funktion nur lokal ausgeführt und der Wert wird nicht im Netzwerk gespeichert.

Wie bereits beim Deployen des Smart Contracts gibt die transact()-Funktion einen Hash als Rückgabewert zurück. Diesen übergeben wir der getTransactionReceipt-Funktion, um zu überprüfen, ob die Transkation mit dem neuen Wert geschürft wurde oder nicht.

Genau das wenden wir in den Zeilen 9 bis 16 in Abbildung 4 an. Wir rufen set() zusammen transact() in Zeile 7 in Abbildung 4 auf. Dadurch wird eine Transaktion erstellt und an die Ethereum-Node geschickt. Als Rückgabe-Wert erhalten wir einen Hash. Solange die Transaktion nicht geschürft ist, wird das Aufrufen der get()-Funktion (in Zeile 9 Abbildung 4) den vorab definierten Wert 1337 liefern. Wir verwenden den Hash beim Aufrufen der getTransactionReceipt-Funktion, um zu überprüfen, ob die Transaktion geschürft wurde. Das sieht man in Zeilen 11 und 14 der Abbildung 4. Sobald die Transaktion in einem Block enthalten ist, rufen wir erneute die get()-Funktion in Zeile 16 Abbildung 4 auf und erhalten den neuen Wert 7331.

simpleValue = w3.eth.contract(
   address=mined.contractAddress,
   abi=simplevalue_contract['abi'],
)
 
print('Current value: ', simpleValue.functions.get().call())
print('Setting value in contract to 7331...')
 
hash = simpleValue.functions.set(7331).transact()
print('Current value: ', simpleValue.functions.get().call())
mined = w3.eth.getTransactionReceipt(hash)
while mined is None:
   sleep(1)
   mined = w3.eth.getTransactionReceipt(hash)
print('Tx mined')
print('Current value: ', simpleValue.functions.get().call())

Abbildung 4 Fortsetzung des Python Scripts nach Abbildung 3

Check fürs Backend

Soll eine DApp entwickelt werden, die komplett im Browser ausgeführt wird, braucht man JavaScript-Code statt Python-Code. Für Backend-Anwendungen sieht das anders aus: Wie gezeigt gibt es mittlerweile gute Python-Bibliotheken, die das Deployen und die Interaktion mit Smart Contracts im Ethereum-Netzwerk erlauben. Wir haben gesehen, dass Python-Bibliotheken zur Verfügung stehen, mit denen es möglich ist, Smart Contracts zu kompilieren, diese zu deployen und mit ihnen zu interagieren. Damit bietet sich Python als Alternative für die DApp-Entwicklung an, wenn der Code im Backend ausgeführt wird.


Über den Autor

Jacek Varky