====== 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.
→ [[lyx_-_programmiersprache:units:blockchain|std.blockchain (Unit-Referenz)]] · [[lyx_-_programmiersprache:units:blockchain:p2p|std.blockchain.p2p]] · [[lyx_-_programmiersprache:guides:welche-unit|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:**
* SHA-256(pubKey) == senderAddr (kein Key-Spoofing)
* ECDSA-Signatur gültig
* Nonce stimmt mit Ledger überein (kein Replay)
* Guthaben ≥ Betrag + Gebühr
* Kein Duplikat im Mempool
----
===== 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 =====
* [[lyx_-_programmiersprache:units:blockchain|std.blockchain — Unit-Referenz]] — vollständige API mit Struct-Layouts und Offset-Konstanten
* [[lyx_-_programmiersprache:units:blockchain:p2p|std.blockchain.p2p — Unit-Referenz]] — Protokoll-Details, Deserializer
* [[lyx_-_programmiersprache:units:crypto:ecc|std.crypto.ecc]] — ECDSA secp256k1 (Signierung)
* [[lyx_-_programmiersprache:units:crypto:sha256|std.crypto.sha256]] — SHA-256 (Hashing, Adressen)
* [[lyx_-_programmiersprache:units:net:epoll|std.net.epoll]] — für nicht-blockierende P2P-Event-Schleifen
Letzte Aktualisierung: 2026-06-13