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?
| 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.
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.
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.
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
}
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:
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.
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.
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.
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).
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)
}
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.
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.
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);
}
}
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;
}
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.
| 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 |
| 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 |
Letzte Aktualisierung: 2026-06-13