Inhaltsverzeichnis

Blockchain mit Lyx

Dieser Guide zeigt den praktischen Einsatz von std.blockchain und std.blockchain.p2p: Schlüsselpaar-Generierung, Transaktionen erstellen und signieren, Mining, Kettenvalidierung und ein P2P-Netzwerk aufbauen.

std.blockchain (Unit-Referenz) · std.blockchain.p2p · Welche Unit?


Wann Blockchain einsetzen?

Szenario Blockchain? Alternative
Dezentrales, manipulationssicheres Transaktionslog ja
Proof-of-Work als Mechanismus gegen Spam / Flood ja
Lernprojekt: Krypto + Konsens verstehen ja
Einfaches Audit-Log (kein Konsens nötig) nein std.log + signierte Einträge (std.crypto.ecc)
Datenbank mit versionierter Geschichte nein PostgreSQL + Trigger / Eventstore
Verteilte Datenhaltung mit SQL nein std.db.postgres + Replikation
Mehr als 1 024 Adressen gleichzeitig nein¹ Ledger-Kapazität anpassen (BL_LEDGER_CAP)

¹ BL_LEDGER_CAP = 1024 ist in der Quelle fest. Bei größeren Netzwerken muss die Konstante vor dem Kompilieren erhöht werden.


Konzeptüberblick

std.crypto.ecc          std.crypto.sha256
       │                         │
       ▼                         ▼
  Schlüsselpaar ──────▶ Adresse (32 Bytes)
  (priv 32 B / pub 64 B)   SHA-256(pubKey)
       │
       ▼
  BLNewTransaction ──▶ BLSignTransaction ──▶ BLAddTransaction (Mempool)
                                                    │
                                                    ▼
                                        BLMinePendingTransactions
                                          (PoW-Schleife, blockiert)
                                                    │
                                              Block + Ledger
                                                    │
                                         BLIsValidChain (optional)

Der Ledger ist eine immutable Hash-Tabelle. Jede BLApplyTransaction- und BLApplyBlock-Operation gibt einen neuen Ledger-Pointer zurück — der alte muss danach freigegeben werden.


Schlüssel und Adressen

Lyx-Blockchain verwendet secp256k1 (gleicher Algorithmus wie Bitcoin). Die Adresse ist der SHA-256 des unkomprimierten Public Keys — kein Base58, keine Checksums.

import std.crypto.ecc;
import std.crypto.sha256;
import std.blockchain;
import std.alloc;
import std.io;

fn SchluesselUndAdresse(): void {
    // Schlüsselpaar generieren
    var priv: int64 := alloc(BL_PRIVKEY_LEN);   // 32 Bytes
    var pub:  int64 := alloc(BL_PUBKEY_LEN);    // 64 Bytes X||Y

    ECCGenerateKeyPair(priv, pub);

    // Adresse = SHA-256(pubKey), 32 Bytes
    var addr: int64 := alloc(BL_ADDR_LEN);
    SHA256(pub, BL_PUBKEY_LEN, addr);

    // Adresse als Hex ausgeben
    var i: int64 := 0;
    while i < BL_ADDR_LEN do {
        Print(IntToStr(peek8(addr + i) & 0xFF)c);
        i := i + 1;
    }
    PrintLn(""c);

    free(priv, BL_PRIVKEY_LEN);
    free(pub,  BL_PUBKEY_LEN);
    free(addr, BL_ADDR_LEN);
}

Wichtig: Private Keys sicher verwalten. Es gibt keine Wallet-Funktion in std.blockchain — Persistenz und Key-Management liegen beim Aufrufer.


Blockchain erstellen

BLBlockchainNew erstellt eine neue Kette mit einem Genesis-Block. Der Genesis-Block hat Timestamp 0 und einen berechneten Hash.

import std.blockchain;

fn BlockchainErstellen(): int64 {
    // difficulty=1: Hash braucht 1 führendes Null-Byte
    // reward=50_000_000: 50 Coins Mining-Belohnung (1 Coin = 1_000_000 Basiseinheiten)
    // maxTxPerBlock=100: max 100 TXs pro Block
    var bc: int64 := BLBlockchainNew(1, 50 * BL_BASE_UNIT, 100);

    Print("Kette erstellt. Länge: "c);
    PrintLn(IntToStr(BLChainLength(bc))c);   // → 1 (Genesis)

    return bc;   // Aufrufer muss BLBlockchainFree(bc) aufrufen
}


Transaktionen erstellen und signieren

Eine Transaktion besteht aus drei Schritten: erstellen, signieren, dem Mempool hinzufügen.

import std.blockchain;
import std.crypto.ecc;
import std.crypto.sha256;
import std.alloc;
import std.io;

fn TransaktionSenden(bc: int64,
                     senderPriv: int64, senderPub: int64,
                     empfaengerAddr: int64,
                     betragCoins: int64, gebuehrCoins: int64): void {
    // Aktuelle Nonce des Senders aus dem Ledger lesen
    var senderAddr: int64 := alloc(BL_ADDR_LEN);
    SHA256(senderPub, BL_PUBKEY_LEN, senderAddr);

    var nonce: int64 := BLBlockchainGetNonce(bc, senderAddr);

    // Transaktion erstellen (Signature-Feld noch Null)
    var tx: int64 := BLNewTransaction(senderPub, empfaengerAddr,
                                      betragCoins * BL_BASE_UNIT,
                                      gebuehrCoins * BL_BASE_UNIT,
                                      nonce);

    // Signieren mit Private Key
    if BLSignTransaction(tx, senderPriv) == 0 then {
        PrintLn("Fehler: Signierung fehlgeschlagen"c);
        BLTxFree(tx);
        free(senderAddr, BL_ADDR_LEN);
        return;
    }

    // Dem Mempool hinzufügen — BLAddTransaction übernimmt bei Erfolg die Ownership
    var ok: int64 := BLAddTransaction(bc, tx);
    if ok == 0 then {
        PrintLn("TX abgelehnt (Nonce, Guthaben oder Signatur ungültig)"c);
        BLTxFree(tx);   // Aufrufer gibt frei wenn abgelehnt
    } else {
        PrintLn("TX akzeptiert"c);
        // tx NICHT mehr free'n — gehört jetzt dem Mempool
    }

    free(senderAddr, BL_ADDR_LEN);
}

BLAddTransaction prüft automatisch:


Mining

BLMinePendingTransactions nimmt bis zu maxTxPerBlock Transaktionen aus dem Mempool (FIFO), fügt eine Coinbase-TX hinzu und löst den PoW. Die Funktion blockiert bis ein gültiger Hash gefunden wird.

import std.blockchain;
import std.io;

fn NaechstenBlockMinen(bc: int64, minerAddr: int64): void {
    Print("Mining Block "c);
    Print(IntToStr(BLChainLength(bc))c);
    PrintLn("..."c);

    var block: int64 := BLMinePendingTransactions(bc, minerAddr);

    if block == 0 then {
        PrintLn("Mining fehlgeschlagen (Ledger-Fehler)"c);
        return;
    }

    // Block gehört jetzt der Kette — NICHT freigeben
    Print("Block gemined. Nonce: "c);
    PrintLn(IntToStr(peek64(block + BL_BLK_NONCE))c);
    Print("Kettenlaenge: "c);
    PrintLn(IntToStr(BLChainLength(bc))c);
}

Coinbase-TX: BLMinePendingTransactions erstellt automatisch eine Coinbase-Transaktion mit reward + Summe aller Gebühren. Die Coinbase landet immer als erste TX im Block.

Schwierigkeitsanpassung

Alle 10 Blöcke wird die Schwierigkeit angepasst, sodass die durchschnittliche Blockzeit bei 10 Sekunden liegt. Der Änderungsfaktor ist auf ±4 begrenzt.

Schwierigkeit Bedeutung Typische Dauer (CPU-abhängig)
1 1 führendes Null-Byte Millisekunden
2 2 führende Null-Bytes Sekunden
3 3 führende Null-Bytes Minuten
4+ exponentiell steigend

Für Tests und Entwicklung empfiehlt sich difficulty=1.


Guthaben und Nonce abfragen

import std.blockchain;
import std.io;

fn KontostandAusgeben(bc: int64, addr: int64): void {
    var balance: int64 := BLBlockchainGetBalance(bc, addr);
    var nonce:   int64 := BLBlockchainGetNonce(bc, addr);

    Print("Guthaben: "c);
    Print(IntToStr(balance / BL_BASE_UNIT)c);
    PrintLn(" Coins"c);

    Print("Nonce: "c);
    PrintLn(IntToStr(nonce)c);
}

Die Nonce beginnt bei 0 und wird bei jeder akzeptierten TX um 1 erhöht. Die nächste TX muss genau die aktuelle Nonce aus dem Ledger verwenden — sonst wird sie von BLAddTransaction abgelehnt.


Kette validieren

BLIsValidChain prüft die gesamte Kette von Block 1 bis zum aktuellen Kopf: Hash-Integrität, Verkettung, Merkle-Root, PoW und vollständiger Ledger-Replay.

import std.blockchain;
import std.io;

fn KetteValidieren(bc: int64): void {
    if BLIsValidChain(bc) == 1 then {
        PrintLn("Kette gueltig"c);
    } else {
        PrintLn("Kette ungueltig — Manipulation erkannt!"c);
    }
}

Achtung: Bei langen Ketten ist BLIsValidChain aufwendig — es werden alle Blöcke und alle Transaktionen erneut verarbeitet. Im Produktionseinsatz nur gezielt aufrufen (z.B. beim Sync mit einem neuen Peer).


P2P-Netzwerk

Knoten starten

import std.blockchain;
import std.blockchain.p2p;
import std.io;

fn P2PKnotenStarten(bc: int64, port: int64): int64 {
    var node: int64 := BLP2PNodeNew(bc, port);

    if BLP2PNodeStart(node) == 0 then {
        PrintLn("Fehler: Port bereits belegt oder Bind-Fehler"c);
        BLP2PNodeFree(node);
        return 0;
    }

    Print("P2P-Knoten lauscht auf Port "c);
    PrintLn(IntToStr(port)c);
    return node;   // Aufrufer ruft BLP2PNodeFree(node)
}

Zu einem Peer verbinden

BLP2PConnectPeer erwartet die IPv4-Adresse als Integer (4 Bytes, big-endian) und sendet sofort eine QUERY_LATEST-Nachricht, um den aktuellen Stand vom Peer abzuholen.

import std.blockchain.p2p;
import std.io;

fn PeerVerbinden(node: int64): void {
    // 192.168.1.50 → (192 << 24) | (168 << 16) | (1 << 8) | 50
    var ip: int64   := (192 << 24) | (168 << 16) | (1 << 8) | 50;
    var port: int64 := 9000;

    if BLP2PConnectPeer(node, ip, port) == 1 then {
        PrintLn("Verbunden"c);
    } else {
        PrintLn("Verbindung fehlgeschlagen (kein Slot oder TCP-Fehler)"c);
    }
}

Max. 32 Peers gleichzeitig. Wenn alle Slots belegt sind, gibt BLP2PConnectPeer 0 zurück ohne zu verbinden.

Nachrichten empfangen

BLP2PHandleIncoming liest eine Nachricht von einem bestimmten FD und verarbeitet sie. In einer echten Anwendung gehört das in eine Event-Schleife (epoll oder select).

import std.blockchain.p2p;
import std.io;

fn EinfacheEventSchleife(node: int64): void {
    var fds: int64 := peek64(node + BLP2P_NODE_PEER_FDS);

    while 1 == 1 do {
        // Neue eingehende Verbindungen akzeptieren
        // Achtung: BLP2PPoll blockiert bis eine Verbindung anliegt
        BLP2PPoll(node);

        // Bestehende Verbindungen abfragen
        var i: int64 := 0;
        while i < BLP2P_MAX_PEERS do {
            var fd: int64 := peek64(fds + i * 8);
            if fd != (0 - 1) then {
                if BLP2PHandleIncoming(node, fd) == 0 then {
                    // Peer hat Verbindung getrennt — Slot freigeben
                    poke64(fds + i * 8, 0 - 1);
                    poke64(node + BLP2P_NODE_PEER_COUNT,
                           peek64(node + BLP2P_NODE_PEER_COUNT) - 1);
                    Print("Peer getrennt (Slot "c);
                    Print(IntToStr(i)c);
                    PrintLn(")"c);
                }
            }
            i := i + 1;
        }
    }
}

Hinweis: Die obige Schleife ist vereinfacht. In einer echten Anwendung sollte std.net.epoll verwendet werden, damit BLP2PPoll und BLP2PHandleIncoming nicht blockieren.

Transaktionen und Blöcke broadcasten

Nach dem Mining einen Block an alle verbundenen Peers verteilen:

import std.blockchain;
import std.blockchain.p2p;

fn MineUndBroadcast(bc: int64, node: int64, minerAddr: int64): void {
    var block: int64 := BLMinePendingTransactions(bc, minerAddr);
    if block == 0 then { return; }

    // Block an alle Peers senden
    BLP2PBroadcastBlock(node, block);
}

Eine neue TX vor dem Mining an alle Peers verteilen:

import std.blockchain;
import std.blockchain.p2p;
import std.io;

fn TxEinreichenUndBroadcasten(bc: int64, node: int64, tx: int64): void {
    var ok: int64 := BLAddTransaction(bc, tx);
    if ok == 1 then {
        BLP2PBroadcastTx(node, tx);
        // tx gehört jetzt dem Mempool
    } else {
        PrintLn("TX abgelehnt"c);
        BLTxFree(tx);
    }
}


Vollständiges Beispiel: Zwei-Knoten-Netzwerk

import std.blockchain;
import std.blockchain.p2p;
import std.crypto.ecc;
import std.crypto.sha256;
import std.alloc;
import std.io;

fn main(): int64 {
    // Knoten A: Blockchain + P2P
    var bcA: int64   := BLBlockchainNew(1, 50 * BL_BASE_UNIT, 100);
    var nodeA: int64 := BLP2PNodeNew(bcA, 9001);
    BLP2PNodeStart(nodeA);

    // Schlüssel für Miner A
    var privA: int64 := alloc(BL_PRIVKEY_LEN);
    var pubA:  int64 := alloc(BL_PUBKEY_LEN);
    ECCGenerateKeyPair(privA, pubA);
    var addrA: int64 := alloc(BL_ADDR_LEN);
    SHA256(pubA, BL_PUBKEY_LEN, addrA);

    // Schlüssel für Teilnehmer B
    var privB: int64 := alloc(BL_PRIVKEY_LEN);
    var pubB:  int64 := alloc(BL_PUBKEY_LEN);
    ECCGenerateKeyPair(privB, pubB);
    var addrB: int64 := alloc(BL_ADDR_LEN);
    SHA256(pubB, BL_PUBKEY_LEN, addrB);

    // Block 1 minen: A erhält 50 Coins Mining-Belohnung
    BLMinePendingTransactions(bcA, addrA);
    Print("A nach Block 1: "c);
    PrintLn(IntToStr(BLBlockchainGetBalance(bcA, addrA) / BL_BASE_UNIT)c);

    // TX: A schickt 10 Coins an B (1 Coin Gebühr)
    var nonce: int64 := BLBlockchainGetNonce(bcA, addrA);
    var tx: int64    := BLNewTransaction(pubA, addrB,
                                         10 * BL_BASE_UNIT,
                                         1  * BL_BASE_UNIT, nonce);
    BLSignTransaction(tx, privA);

    var ok: int64 := BLAddTransaction(bcA, tx);
    if ok == 0 then {
        PrintLn("TX abgelehnt"c);
        BLTxFree(tx);
    }

    // Block 2 minen: enthält TX + Coinbase (50 + 1 = 51 Coins für A)
    var block2: int64 := BLMinePendingTransactions(bcA, addrA);
    if block2 != 0 then {
        BLP2PBroadcastBlock(nodeA, block2);
    }

    Print("A nach Block 2: "c);
    PrintLn(IntToStr(BLBlockchainGetBalance(bcA, addrA) / BL_BASE_UNIT)c);
    Print("B nach Block 2: "c);
    PrintLn(IntToStr(BLBlockchainGetBalance(bcA, addrB) / BL_BASE_UNIT)c);

    // Kettenvalidierung
    if BLIsValidChain(bcA) == 1 then {
        PrintLn("Kette A gueltig"c);
    }

    // Aufräumen
    BLP2PNodeStop(nodeA);
    BLP2PNodeFree(nodeA);
    BLBlockchainFree(bcA);   // gibt Ledger, alle Blöcke, Mempool frei
    free(privA, BL_PRIVKEY_LEN); free(pubA, BL_PUBKEY_LEN); free(addrA, BL_ADDR_LEN);
    free(privB, BL_PRIVKEY_LEN); free(pubB, BL_PUBKEY_LEN); free(addrB, BL_ADDR_LEN);
    return 0;
}


Speicherverwaltung

Die Ownership-Regeln sind nicht intuitiv — dieser Überblick hilft Fehler zu vermeiden:

Objekt Erstellt von Freigabe Besonderheit
TX BLNewTransaction / BLNewCoinbaseTx BLTxFree(tx) Nicht freigeben wenn BLAddTransaction = 1
Block BLNewBlock BLBlockFree(block) Befreit txPtrs-Array, nicht die TX-Pointer darin
Ledger BLLedgerNew BLLedgerFree(l) BLApplyTransaction/Block gibt neuen Ptr zurück — alten danach freigeben
Blockchain BLBlockchainNew BLBlockchainFree(bc) Gibt Ledger + alle Blöcke + Mempool-TXs frei
P2P-Node BLP2PNodeNew BLP2PNodeFree(node) Schließt alle Peer-Verbindungen; bc wird nicht freigegeben
Deserialisierter TX BLDeserializeTx BLTxFree(tx) Wenn nicht via BLAddTransaction übergeben

Immutable Ledger — typisches Muster:

// RICHTIG: alten Ledger nach Apply freigeben
var oldLedger: int64 := peek64(bc + BL_BC_LEDGER);
var newLedger: int64 := BLApplyTransaction(oldLedger, tx);
if newLedger != 0 then {
    BLLedgerFree(oldLedger);
    poke64(bc + BL_BC_LEDGER, newLedger);
}

// FALSCH: alten Ledger nicht freigeben → Memory Leak
// var newLedger: int64 := BLApplyTransaction(peek64(bc + BL_BC_LEDGER), tx);
// poke64(bc + BL_BC_LEDGER, newLedger);

Im normalen Anwendungsfall verwenden Senior-Entwickler BLBlockchainNew und BLAddTransaction/BLMinePendingTransactions — dann verwaltet bc den Ledger intern und das Muster ist nicht nötig.


Fehlerbehandlung

Funktion Rückgabe 0 bedeutet … Ursache (typisch)
BLAddTransaction TX abgelehnt Nonce falsch, Guthaben zu gering, Signatur ungültig, Duplikat
BLMinePendingTransactions Mining-Fehler Ledger-Anwendung fehlgeschlagen (interner Fehler)
BLSignTransaction Signierung fehlgeschlagen Ungültiger Private Key
BLVerifyTransaction Signatur ungültig Key/TX-Manipulation
BLIsValidChain Kette ungültig Hash-Abweichung, falscher Ledger-Zustand, PoW-Lücke
BLP2PNodeStart Port-Fehler Port belegt, fehlende Rechte
BLP2PConnectPeer Verbindung fehlgeschlagen Host nicht erreichbar, kein freier Peer-Slot
BLP2PHandleIncoming Verbindung getrennt Peer hat Socket geschlossen oder Lesefehler

Bekannte Einschränkungen

Einschränkung Details
Ledger-Kapazität Max. 1 024 Adressen (BL_LEDGER_CAP). Beim Überschreiten gibt BLApplyTransaction 0 zurück
PoW blockiert BLMinePendingTransactions kehrt erst zurück wenn ein Hash gefunden wurde
Chain-Sync unvollständig BLP2PHandleIncoming sendet QUERY_CHAIN wenn die eigene Kette kürzer ist, verarbeitet RESP_CHAIN aber nicht — kein vollständiger Catch-up möglich
BLP2PPoll blockiert Trotz des Namens verwendet BLP2PPoll blockierendes accept
Keine Persistenz Blockchain liegt vollständig im RAM — kein eingebautes Speichern
Kein Fee-Markt FIFO-Auswahl aus dem Mempool, keine Gebühren-Priorisierung
Kein UTXO Kontobasiertes Modell (wie Ethereum), kein UTXO (wie Bitcoin)
Max. 32 Peers Feste Obergrenze in BLP2P_MAX_PEERS

Weiterführend

Letzte Aktualisierung: 2026-06-13