NEU: Helius übernimmt Light Protocol
Eine Glühbirne und eine Leiterplatte
Blog/Grundlagen

So verwandelst du deine Idee in ein Solana-Programm (Smart Contract)

Developer EducationMike MacCana auf XMike MacCana auf LinkedIn
7 Min. Lesezeit

Entwerfen wir ein Solana-Programm. Das Solana-Ökosystem bietet zahlreiche Artikel zum Verständnis des Solana-Programmiermodells und zur Entwicklung kleiner Demoprogramme. Damit können Einsteiger Solana und Anchor hervorragend kennenlernen. Reale Programme stellen jedoch komplexere Anforderungen. 

In diesem Artikel betrachten wir ein reales Programm, das wir entwickeln möchten. Dabei behandeln wir folgende Themen: 

  1. Unsere Daten als Solana-Accounts definieren
  2. Beziehungen zwischen unseren Daten erstellen
  3. Token in unserem Programm speichern
  4. Lesevorgänge mit Indizes beschleunigen
  5. Schreibvorgänge mit Sharding beschleunigen

Dieses Beispiel soll dir helfen, deine eigenen Ideen als Solana-Programme und -Accounts zu modellieren. 

Hinweis: Dieser Artikel setzt voraus, dass du mit den Grundlagen von Anchor vertraut bist. 

Beispiel für ein Solana-Programm — Aufbau eines Prognosemarkts

Das Programm, das wir heute entwerfen, ist ein Prognosemarkt ähnlich wie Polymarket, Hedgehog oder Drift BET. Falls du Prognosemärkte noch nicht kennst: Dort können Menschen auf verschiedene Ergebnisse eines Ereignisses wetten. Das kann etwa die Frage sein, welches Team den Super Bowl gewinnt, wer den Oscar für die beste Regie erhält, ob die Regierung bis zu einem bestimmten Zeitpunkt eine Ankündigung macht oder wie ein anderes reales Ereignis ausgeht. Wer auf das richtige Ergebnis setzt, erhält einen Gewinn aus dem Wettpool.

Architektur des Prognosemarkts

Dies ist die grundlegende Architektur eines Prognosemarkts und so hängen die einzelnen Elemente zusammen:

  • Es gibt mehrere Ereignisse.
  • Jedes Ereignis hat mehrere Ergebnisse. Eines davon wird letztlich als Gewinnergebnis festgelegt.
  • Benutzer platzieren mehrere Wetten auf die einzelnen Ergebnisse. Wenn ein Benutzer eine Wette platziert, wird sein Einsatz dem Gewinnpool dieses Ereignisses hinzugefügt.

Wenn das Ereignis „aufgelöst“ wird, wir also das Gewinnergebnis kennen:

  • Benutzer, die auf das Gewinnergebnis gesetzt haben, können ihren Gewinn einfordern
  • Gewinner erhalten einen Anteil am Gewinnpool abzüglich des Anteils des Betreibers
  • Der Anteil jedes Gewinners am Gewinnpool richtet sich nach seinem Anteil an den Wetten auf das Gewinnergebnis.

1. Unsere App als Solana-Accounts entwerfen

Würden wir dieses Programm für eine relationale Datenbank entwerfen, müssten wir Folgendes berücksichtigen:

  • Ähnliche Datenelemente werden als Zeilen in einer Tabelle gespeichert 
  • Primärschlüssel identifizieren jedes Datenelement eindeutig
  • Die Spalten der Tabelle definieren die erwarteten Attribute jedes Datenelements und deren Typen

In Solana lassen sich diese Konzepte ungefähr wie folgt abbilden:

  • Ähnliche Datenelemente verwenden denselben Account-Typ 
  • Adressen identifizieren jedes Datenelement eindeutig
  • Das struct aus Schlüsseln und Datentypen definiert die Attribute jedes Elements

Hier siehst du dieselben Daten sowohl in einer herkömmlichen Datenbanktabelle als auch als Solana-Accounts:

2. Beziehungen zwischen Daten abbilden

Beziehungen zwischen Datenelementen funktionieren in Solana ganz anders. Herkömmliche Datenbanken verwenden Beziehungen, also logische Verknüpfungen zwischen Tabellen. Solana bildet Eins-zu-viele-Beziehungen als Vektor aus Adressen ab. Jede Adresse verweist dabei auf einen Account mit dem jeweiligen Element. 

Ereignisse haben beispielsweise „Ergebnisse“, die als Vektor aus Ergebnisadressen gespeichert werden. Anchor stellt dies als Vec<Pubkey> dar, obwohl PDA-Adressen wie die unseres Ergebnisses nicht wirklich öffentliche Schlüssel sind.

3. Token speichern

Anders als herkömmliche Datenbanken können Solana-Programme auch echte Guthaben in einem Account speichern, nicht nur Zahlenwerte für Guthaben.

In unserem Beispiel benötigt das Ereignis einen Token-Account für seinen Gewinnpool. Die PDA des Ereignisses besitzt den Account des Gewinnpools. Wenn Benutzer Wetten platzieren, senden sie Token an diesen Account. Noch wichtiger ist jedoch: Wenn sie ihre Gewinne einfordern, signiert unser Programm die Transaktion als Ereignis-Account, um Token aus dem Gewinnpool zu übertragen.

4. Aufwand für Lesevorgänge reduzieren

Unser Programm muss sich für Benutzer möglichst reaktionsschnell anfühlen. Gleichzeitig möchten wir als Entwickler vermeiden, unnötig für überflüssige Account-Lesevorgänge zu zahlen. Mit laufenden Summen und Indizes machen wir unser Programm reaktionsschneller und effizienter.

Laufende Summen berechnen

Wenn Benutzer ihre Gewinne einfordern, müssen wir genau wissen, wie viel auf jedes Ergebnis gesetzt wurde. Prognosemärkte berechnen die Auszahlung eines Gewinners mit der Formel Gewinnpool × Wettbetrag ÷ Gesamteinsätze auf das Gewinnergebnis.

Aktuell speichern wir nur den Betrag jeder Wette im Account der Wette. Um den Gesamteinsatz für ein bestimmtes Ergebnis zu ermitteln, müssten wir jeden Wett-Account lesen und alle Beträge addieren.

Fügen wir stattdessen jedem Ergebnis das Feld total_amount hinzu und erhöhen es, wenn Benutzer Wetten platzieren. So können wir bei einem Gewinn die Auszahlung einfach bestimmen, ohne jede Wette auf dieses Ergebnis lesen zu müssen.

Indizes verwenden

Außerdem müssen wir alle Wetten eines bestimmten Benutzer-Accounts finden. Wir könnten jeden einzelnen Wett-Account mit getProgramAccounts() abrufen und nach Accounts filtern, deren Wettender auf die Adresse dieses Benutzers gesetzt ist. Helius’ schnelles getprogramAccounts() ist zwar deutlich schneller als bei anderen RPC-Anbietern, doch Indizes sind eine gängige Alternative. 

Erstellen wir daher einen Index, der die Wetten jedes Benutzers speichert. Wenn ein Benutzer eine neue Wette platziert, erstellen wir dieses Element, falls es noch nicht existiert, und fügen die Wette seiner Wettliste hinzu:

Außerdem versehen wir jedes Ereignis mit Tags. So können wir alle Ereignisse mit Tags wie „sports“, „politics“, „europe“, „usa“ oder „politics“ leicht finden. Dafür erstellen wir einen weiteren Index:

Jetzt können wir alle relevanten Ereignisse einfach über den Account des Ereignis-Tags abrufen.

5. Konflikte bei Schreibzugriffen reduzieren

Erinnerst du dich daran, dass jedes Ereignis einen einzelnen Token-Account besitzt, in dem die Wetteinsätze für dieses Ereignis gespeichert werden? Jedes Mal, wenn ein Benutzer eine neue Wette platziert, werden Token in diesen Account übertragen. Der Account wird also beschrieben.

Solana ist schnell, weil es Vorgänge parallel ausführt. Der Saldo eines einzelnen Accounts kann jedoch nicht parallel aktualisiert werden. Die Aktualisierungen müssen nacheinander erfolgen, da jeder Account zu jedem Zeitpunkt genau einen Saldo haben muss. 

Wenn ein neues Ereignis angekündigt wird und viele Wetten eingehen, erfolgen möglicherweise zahlreiche Schreibzugriffe gleichzeitig auf den Token-Account des Ereignisses. Dadurch können die Transaktionen unseres Programms langsam wirken. Dies bezeichnet man als Konflikt bei Schreibzugriffen: Mehrere Transaktionen konkurrieren um den Zugriff auf denselben Account.

Eine Möglichkeit, in solchen Fällen Parallelität zu ermöglichen, ist Sharding. Dabei wird eine Ressource in mehrere Teile, sogenannte Shards, aufgeteilt, auf die parallel zugegriffen werden kann.

So funktioniert Sharding

Eingehende Zahlungen werden anhand des Werts des letzten Bytes im öffentlichen Schlüssel des Wettenden an separate win_pool-Shards gesendet. Dafür verwenden wir das Makro shard_num(). So lassen sich eingehende Zahlungen schnell verarbeiten.

Später kann ein Admin-Instruction-Handler diese Shards in einem einzigen Gewinnpool-Account zusammenführen. Dadurch steht die Liquidität zur Auszahlung der Gewinner im selben Account bereit.

Wir müssen auch Konflikte bei Schreibzugriffen berücksichtigen, wenn ein beliebtes Ereignis abgeschlossen wird und alle Gewinner gleichzeitig ihre Gewinne einfordern. Auch dann müssen wir die Mittel möglichst schnell aus dem Gewinnpool übertragen.

Am besten vermeiden wir hier einen Einforderungsprozess. Wenn wir die Mittel stattdessen direkt nach Abschluss des Ereignisses nacheinander senden, müssen sich Benutzer überhaupt nicht mit langsamen Einforderungen befassen. Ihre Gewinne liegen dann bereits in ihren Accounts.

Bevor du dir jedoch Gedanken über große Mengen von Benutzern machst, die Wetten platzieren oder Gewinne einfordern, brauchst du erst einmal Benutzer. Wenn du dein Solana-Programm noch planst und nicht veröffentlicht hast, sind Optimierungen für große Mengen an nicht vorhandenem Benutzer-Traffic unnötig. Prüfe, ob du die zusätzliche Komplexität umfangreicher Optimierungen wie dieser wirklich für den Launch benötigst. Sei dir aber bewusst, dass du sie möglicherweise implementieren musst, sobald dein Programm beliebter wird.

Fazit

In diesem Artikel haben wir die Entwicklung einer realen Anwendung auf Solana ausführlich betrachtet. Du weißt jetzt, wie du Solana-Accounts definierst, Datenbeziehungen herstellst, Token speicherst und die Performance mit Indizes und Sharding optimierst. 

Wenn du weitere Fragen hast, kontaktiere gern @helius auf X oder tritt dem Helius Discord bei. Wir werden dieses Beispiel für einen Prognosemarkt künftig weiter ausbauen. Folge daher diesen Accounts, um keine Neuigkeiten zu verpassen.

Mit diesen neuen Fähigkeiten kannst du deine Ideen jetzt in Solana-Programme verwandeln und sie im Solana-Ökosystem zum Leben erwecken. Viel Spaß beim Programmieren! Danke an Ichigo für die Prüfung dieses Artikels und an r0bre für den Hinweis auf die verwendete Account-Sharding-Technik.

Weitere Ressourcen

Helius abonnieren

Bleib bei der Solana-Entwicklung auf dem Laufenden und erhalte Updates, wenn wir neue Beiträge veröffentlichen

Vergrößertes Bild