Solidity: Wie programmiert man Smart Contracts? (Teil 3)

Seit Vitalik Buterin Ethereum entwickelt hat, sind Smart Contracts aus dem Blockchain-Ökosystem nicht mehr wegzudenken. Doch was steckt hinter den „schlauen Verträgen“? In fünf Teilen erkundet Ingo Rammer für uns die Implikationen der Technologie. Heute: Wie programmiert man Smart Contracts?

Ingo Rammer
Teilen

ong>„schlauen Verträgen“? In fünf Teilen erkundet Ingo Rammer für uns die Implikationen der Technologie. Heute: Wie programmiert man Smart Contracts?

Wie programmiert man Smart Contracts?

Solidity: Schritt für Schritt

Hier geht es zu Teil 1: Smart Contracts – Heilsbringer oder bessere Datenbank-Trigger?

Hier geht es zu Teil 2: Mehr als nur Kryptowährungen: Transaktionen und Smart Contracts

In den Beispielen in diesem Artikel werde ich Solidity verwenden – die Contracts sind daher auf jedem Ethereum-Derivat, sowohl für öffentliche als auch private Blockchains lauffähig. Die vorgestellten Konzepte lassen sich jedoch auch für die meisten anderen Blockchains anwenden. Der Grund für die Wahl von Solidity ist die Verfügbarkeit von einfach nutzbaren Entwicklungswerkzeugen: So gibt es mit der Open-Source-Entwicklungsumgebung Remix eine Möglichkeit, Solidity-Code lokal zu entwickeln, zu testen und zu debuggen, ohne die Notwendigkeit, Verbindung mit einem echten Blockchain-Netzwerk aufzunehmen.

Als ersten Schritt müssen wir ein Datenmodell definieren, das den Ist-Zustand der Daten des Smart Contract festlegt. Stark vereinfacht dargestellt, umfasst dieser Grundzustand in unserem Fallbeispiel die folgenden drei Aspekte:

  • Festlegung der Identität des Regulators – hier die der Bundesnetzagentur. Transaktionen, die von dort gesendet werden, haben zusätzlich die Berechtigung, eine Telefonnummer erstmalig mit einem Betreiber zu verknüpfen.
  • Definition einer Schlüssel-/Wert-Zuordnung, vergleichbar mit einem Dictionary. Diese Zuordnung erlaubt die Feststellung der Identität des aktuellen Betreibers für jede eingetragene Telefonnummer.
  • Definition einer weiteren Schlüssel-/Wert-Zuordnung, die für jede Telefonnummer, die sich gerade in einer Portierung befindet, die Identität des Portierungsziels (des neuen Mobilfunkbetreibers) festhält.

(Bei der oben beschriebenen Datenstruktur handelt es sich selbstverständlich um eine starke Vereinfachung. Zum Zweck der Lesbarkeit habe ich mir erlaubt, einige Aspekte für diesen Artikel außer Acht zu lassen, die in der Praxis unabdingbar ebenfalls zu definieren wären. So ist beispielsweise weder eine Liste der zugelassenen Identitäten festgelegt (jeder Teilnehmer an diesem Netz könnte also beliebig viele Mobilfunkbetreiber erfinden), noch sind Eskalationsprozesse oder Zeitlimits bei der Portierung berücksichtigt. Die zugrunde liegenden Überlegungen und Technologien erlauben jedoch die problemlose Erweiterung um diese Parameter.

Absenderadressen

Die angesprochenen „Identitäten“ sind jeweils technologiespezifische Absenderadressen. In der Praxis handelt es sich durchweg um Werte, die dem öffentlichen Schlüssel aus einem asymmetrischen Schlüsselpaar entsprechen. Das bedeutet, dass sich durch die Prüfung der kryptografischen Signatur einer Transaktion ermitteln lässt, von welcher Identität diese Transaktion signiert wurde. Dies erlaubt es uns sicherzustellen, dass beispielsweise nur der aktuell für eine Telefonnummer zuständige Mobilfunkbetreiber den Datensatz ändern darf.

Sehen wir uns also zuerst in Listing 1 den Basiscode unseres Smart Contract an, der das oben skizzierte Datenmodell implementiert.

Listing 1

contract PhoneNumberTransfer {

mapping(string => address) allPhoneNumbers;

mapping(string => address) requestedTransfers;

address regulator;

 

constructor() public {regulator = msg.sender;}

}

Das konzeptionelle Persistenzmodell von Solidity

Das konzeptionelle Persistenzmodell von Solidity ist relativ einfach: Alle Member-Felder eines Smart Contract werden automatisch in ihrem persistenten World State gespeichert. In diesem Fall betrifft das die Felder allPhoneNumbers, requestedTransfer und regulator.

Der Konstruktor in diesem Codefragment nutzt eine weitere Besonderheit von Ethereum-Derivaten: Ein Smart Contract wird im Netzwerk installiert, indem sein kompilierter Bytecode mit einer speziellen Blockchain-Transaktion dem Netzwerk bekannt gemacht wird. Diese Blockchain-Transaktion hat (so wie jede andere Transaktion) einen Absender und dieser kann mit msg.sender ausgewertet und gespeichert werden. Bevor ein Teilnehmer unseres Nummerportierungsnetzwerks den Smart Contract nutzen kann, müsste also die Netzagentur einmalig diese Installationstransaktion an das Netzwerk senden. Als Antwort bekommt der Absender danach eine Adresse, die die gerade erzeugte Instanz des Smart Contract referenziert. Alle weiteren Transaktionen (von allen Teilnehmern) werden daraufhin an diese konkrete Instanzadresse gesendet.

Wie oben angesprochen, handelt es sich dabei um eine technische Besonderheit von Blockchain-Netzwerken, die auf Ethereum basieren. In Netzwerken, die auf anderen Basistechnologien fußen, kann die Installation von Smart Contracts anders erfolgen. Je nach gewählter Plattform – zum Beispiel bei Hyperledger Fabric – muss die Identität des Regulators dem Contract eventuell auch explizit, mit einem einmaligen Funktionsaufruf nach Installation, bekannt gemacht werden. (Eine Ethereum-Implementation wie Parity verhält sich zu Hyperledger Fabric in etwa so wie Microsoft SQL Server zu MongoDB: Beide sind Ausprägungen eines Technologiekonzepts –„Datenbank“ oder „Blockchain“–, werden aber gänzlich unterschiedlich interpretiert und implementiert).

Die Funktion der initialen Zuordnung einer Telefonnummer zu einem Anbieter könnte, wie in Listing 2 gezeigt, implementiert werden.

Listing 2

contract PhoneNumberTransfer {

/* … wie oben … */

function createAndAssignNumber(uint phoneNumber, address to) public {

require(regulator == msg.sender);

require(allPhoneNumbers[phoneNumber] == 0);

allPhoneNumbers[phoneNumber] = to;

}

}

Die in diesem Fragment angegebene Funktion createAndAssignNumbers erhält zwei Parameter: die Telefonnummer und die Identität eines Mobilfunkanbieters (in Form einer Ethereum-Adresse, wie oben beschrieben), dem die Telefonnummer zugewiesen werden soll. Der Code prüft zuerst, ob der Absender der Transaktion gleich dem Regulator ist, denn nur dieser hat in unserem Beispiel die Berechtigung der initialen Erstellung und Zuordnung einer Telefonnummer. Die Funktion require führt in Solidity zu dem Äquivalent einer Exception, sodass das System bei Nichterfüllung der Bedingung die Bearbeitung des restlichen Codes der Transaktion sofort abbricht und sie als fehlerhaft markiert. Danach prüfen wir durch den Zugriff auf allPhoneNumbers[phoneNumber], ob die gewünschte Telefonnummer bereits zugewiesen ist. Dieser Zugriff auf ein Mapping ist in Solidity so definiert, dass nicht gefüllte Einträge als 0 zurückgegeben werden. In der letzten Zeile weisen wir ansonsten einfach der übergebenen Telefonnummer die als Parameter übergebene Adresse zu. Da bei der Transaktionsabarbeitung in einer Blockchain eine absolute zeitliche Abfolge der einzelnen Transaktionen durch deren Position in einem Block fest vorgegeben ist, müssen wir hier auch nicht auf gleichzeitigen schreibenden Zugriff durch andere Transaktionen achten.

Für den zweiten Transaktionstyp, dem Wunsch nach Portierung einer Nummer, gehen wir analog vor. Ein solcher Aufruf würde vom neuen Mobilfunkbetreiber an das Netzwerk gesendet werden und könnte wie in Listing 3 gezeigt ausgeführt werden.

Listing 3

function requestTransfer(uint phoneNumber) public {

// Nummer muss existieren und zugeordnet sein

require(allPhoneNumbers[phoneNumber] != 0);

// Nummer darf nicht bereits dem Anfragenden zugeordnet sein

require(allPhoneNumbers[phoneNumber] != msg.sender);

// Nummer darf nicht bereits in einem Portierungsvorgang sein

require(requestedTransfers[phoneNumber] == 0);

 

requestedTransfers[phoneNumber] = msg.sender;

}

Auch hier prüft der Code des Smart Contracts zuerst die beiden Eingangsbedingungen und ändert nach positiver Prüfung die Daten im World State so, dass festgehalten wird, wer die Portierung der gegebenen Nummer angefordert hat. Typischerweise würde als letzte Zeile des Smart Contracts jetzt noch ein Benachrichtigungsereignis (zum Beispiel ein Event in Solidity) ausgelöst werden, auf das jeder Mobilfunkbetreiber durch eigenen Programmcode an seinem Blockchain-Node subskribiert ist. Damit startet der Smart Contract die lokale (blockchainunabhängige) Bearbeitung der Portierung in den existierenden Backend-Systemen eines Teilnehmers, sobald ein anderer Betreiber die Portierung einer Nummer aus seinem Einflussgebiet anfordert.

Nachdem der Mobilfunkanbieter, dem diese Nummer aktuell zugeordnet ist, seine interne Prüfung der Portierung abgeschlossen und die Korrektheit der Portierung selbst validiert hat (z. B. durch Einverständniserklärung des Kunden per SMS), kann er die Anfrage mit einer weiteren Transaktion beantworten. Der Smart-Contract-Code dieser Transaktion könnte vereinfacht wie in Listing 4 gezeigt aussehen.

Listing 4

function confirmTransfer(uint phoneNumber) public  {

// Sicherstellen, dass die Nummer überhaupt portiert werden soll

address destination = requestedTransfers[phoneNumber];

require (destination != 0);

 

// Nummer muss aktuell dem Sender der Transaktion zugeordnet sein

require (allPhoneNumbers[phoneNumber] == msg.sender);

 

// Aktualisierung der Zuordnung für die Nummer

allPhoneNumbers[phoneNumber] = destination;

// Löschen des offenen Transfers

delete requestedTransfers[phoneNumber];

}

Analog kann man die Ablehnung eines Transfers gestalten. Hier liegt der Unterschied darin, dass die Zuordnung der Telefonnummer nicht aktualisiert, sondern lediglich die Portierungsanforderung in requestedTransfers entfernt wird. In beiden Fällen würde man am Ende der Funktion wieder ein Event auslösen, sodass die betroffene Gegenseite informiert wird, ohne jede einzelne Transaktion der Blockchain ständig überwachen zu müssen.

Ingo Rammer ist Speaker bei der Blockchain Technology Conference vom 19-21. November in Berlin. Dort kann man praktische Erfahrungen mit internationalen Experten sammeln. Außerdem gibt es Live-Demos und Fallstudien von echten Implementierungen, individuelle Interaktion mit Experten und Networking-Möglichkeiten mit Menschen aus den verschiedensten Branchen. Es erwartet euch außerdem eine Vielzahl von Sessions, Workshops und Kurzvorträgen von internationalen Referenten.

Exklusiver Rabatt-Code für unsere Leser: Einfach blockchain-btc-15 eingeben und 15 Prozent Rabatt sichern. Tickets gibt es hier.

Do Your Own Research!
Glassnode bietet dir umfangreiche On-Chain-Daten, um bessere Investitions- und Handelsentscheidungen für Bitcoin, Ethereum und Co. treffen zu können.
Jetzt anmelden und 10% sparen