From 862ccf1d03d848ed62f9378bb729a5e1c342a05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B6hml=C3=A4nder?= Date: Sun, 25 Oct 2026 11:45:32 +0000 Subject: [PATCH] docs: update Potatium v0.6 architecture and PoP research --- AGENTS.md | 18 +- README.md | 42 +- docs/ARCHITEKTUR.md | 24 +- docs/COCKPITS_UND_MONITORING.md | 74 +++ docs/GESAMTHANDBUCH.md | 18 +- docs/HUB_WORKER_CLUSTER.md | 90 +++ docs/INFRASTRUKTUR_UND_DEPLOYMENT.md | 127 ++++ docs/INSTALLATIONSPLAN_HOMELAB.md | 150 +++++ docs/NETWORK_SERVICES_UND_ONBOARDING.md | 9 + docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md | 27 + docs/POP_CLUSTER_SIMULATION.md | 127 ++++ docs/POP_DYNAMIC_FUND_SIMULATION.md | 57 ++ ...POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md | 55 ++ docs/POP_GEBUEHREN_VERTEILUNG_FORSCHUNG.md | 68 +++ docs/PROJEKTPLAN.md | 24 +- docs/PROTOKOLL_ENTWURF.md | 18 +- docs/ROADMAP.md | 39 +- docs/WEB_WALLET.md | 16 +- ...or-infrastruktur-und-cockpits-ab-devnet.md | 42 ++ ...-services-onboarding-und-gebuehrenfonds.md | 41 ++ ...anker-netzzugang-admin-und-identitaeten.md | 42 ++ .../po_p_dynamic_20261025_comparison.csv | 13 + handoff/CHATGPT_PROJEKTANWEISUNGEN.md | 7 +- handoff/REPO_UPDATE_V05.md | 13 + handoff/REPO_UPDATE_V06.md | 9 + handoff/STARTPROMPT_VSCODE_CODEX.md | 11 +- scripts/analyze_pop_fee_flows.py | 301 ++++++++++ scripts/simulate_pop_cluster.py | 556 ++++++++++++++++++ scripts/simulate_pop_dynamic.py | 532 +++++++++++++++++ scripts/simulate_pop_longterm.py | 305 ++++++++++ tests/test_pop_cluster.py | 120 ++++ tests/test_pop_dynamic.py | 155 +++++ tests/test_pop_fee_flows.py | 93 +++ tests/test_pop_longterm.py | 52 ++ 34 files changed, 3257 insertions(+), 18 deletions(-) create mode 100644 docs/COCKPITS_UND_MONITORING.md create mode 100644 docs/HUB_WORKER_CLUSTER.md create mode 100644 docs/INFRASTRUKTUR_UND_DEPLOYMENT.md create mode 100644 docs/INSTALLATIONSPLAN_HOMELAB.md create mode 100644 docs/NETWORK_SERVICES_UND_ONBOARDING.md create mode 100644 docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md create mode 100644 docs/POP_CLUSTER_SIMULATION.md create mode 100644 docs/POP_DYNAMIC_FUND_SIMULATION.md create mode 100644 docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md create mode 100644 docs/POP_GEBUEHREN_VERTEILUNG_FORSCHUNG.md create mode 100644 docs/decisions/0006-validator-infrastruktur-und-cockpits-ab-devnet.md create mode 100644 docs/decisions/0007-network-services-onboarding-und-gebuehrenfonds.md create mode 100644 docs/decisions/0008-schlanker-netzzugang-admin-und-identitaeten.md create mode 100644 docs/research/po_p_dynamic_20261025_comparison.csv create mode 100644 handoff/REPO_UPDATE_V05.md create mode 100644 handoff/REPO_UPDATE_V06.md create mode 100644 scripts/analyze_pop_fee_flows.py create mode 100644 scripts/simulate_pop_cluster.py create mode 100644 scripts/simulate_pop_dynamic.py create mode 100644 scripts/simulate_pop_longterm.py create mode 100644 tests/test_pop_cluster.py create mode 100644 tests/test_pop_dynamic.py create mode 100644 tests/test_pop_fee_flows.py create mode 100644 tests/test_pop_longterm.py diff --git a/AGENTS.md b/AGENTS.md index 427aed7..0097033 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,14 +3,28 @@ 1. **Zuerst lesen:** `README.md`, `docs/PROJEKTPLAN.md`, `docs/PROTOKOLL_ENTWURF.md`, `docs/POP_FAIRNESS_UND_GEBUEHREN.md`, `docs/ROADMAP.md`, `docs/WEB_WALLET.md`, `docs/decisions/*.md`. Die letzte akzeptierte ADR entscheidet bei historischen Widersprüchen. Der *tatsächliche Git-Stand* ist maßgeblich. 2. **Bestandsaufnahme:** Umgebung Ubuntu 26.04 LTS im unprivilegierten LXC, Workspace `/home/potatium/src/potatium`, Go, private Gitea-Git-Remote. Bestehende Änderungen erhalten; nicht ungefragt branch/rebase/push oder `git clean/reset` verwenden. 3. **Architektur:** eigenständige Hobby-L1, Go, CometBFT/ABCI++ mit eigener deterministischer App als vorläufige Wahl, kein Cosmos SDK für den Start. Validatoren bestätigen Blöcke, Miner lösen nur PoP-Challenges. `linux/amd64` und `linux/arm64` als Ziel; Pi 4 als Benchmarkminimum. -4. **PoP v0.2 (akzeptierte Produktidee):** Jede Mining-Runde hat **3 zeitlich getrennte Fenster**. Fenster 1 gratis; Fenster 2/3 optional solange noch kein gültiges Ticket angenommen wurde und nach bestätigter **fixer + progressiver** Gebühr. Progressionsbasis: finalisierte PoP-Mining-Gewinne der zurückliegenden **ca. 90 Tage je registrierter Mining-Identität/Betreibergruppe**, **nicht Wallet-Guthaben**. Keine lebenslange Benachteiligung. Zusatzgebühren burnen; normale Transaktionsgebühren sollen Validatoren finanzieren, Aufteilung noch offen. Pro identifizierter Mining-Node **maximal ein Ticket je Gesamtrunde**. Nach Erfolg stoppen. Identität/Hardware und Betreiberzuordnung können Sybil nicht kryptografisch beweisen. +4. **PoP v0.2 (akzeptierte Produktidee):** Jede Mining-Runde hat **3 zeitlich getrennte Fenster**. Fenster 1 gratis; Fenster 2/3 optional solange noch kein gültiges Ticket angenommen wurde und nach bestätigter **fixer + progressiver** Gebühr. Progressionsbasis: finalisierte PoP-Mining-Gewinne der zurückliegenden **ca. 90 Tage je registrierter Mining-Identität/Betreibergruppe**, **nicht Wallet-Guthaben**. Keine lebenslange Benachteiligung. **ADR 0007 hat die Gebührenverwendung geändert:** 100 % der PoP-Zusatzfenster- und Worker-Aktivierungsgebühren in den On-chain-Community-Fonds, 0 % Burn dieser Gebühren. Normale Transaktionsgebühren, Fondsausgaben und Tarifhöhen offen. Pro identifizierter Mining-Node **maximal ein Ticket je Gesamtrunde**. Nach Erfolg stoppen. Identität/Hardware und Betreiberzuordnung können Sybil nicht kryptografisch beweisen. 5. **Noch nicht entschieden:** BTC-artige Coin-Grenze, Mint je Runde, Gebührenbeträge/Schwellen, Nonce-Raum, exakte Fensteranzahl an Blöcken, p=70 %, feste Blockzeit, Wallet-Kryptografie, Beaconauswahl. Beispiele niemals als gültige Konsensregel kodieren. Vor PoP-Implementierung Simulationen und Pi-Messung samt ADR. 6. **Ziehung:** Ticketbestand vor künftiger überprüfbarer Zufallsquelle finalisieren. Kein `time.Now()`, externe Zufallsabfrage oder Float in deterministischen State-Transitions. Reorgs, Zeit-/Höhenfristen, Beacon-Ausfall, Zensur, Replay, Rundungsrest und Multi-Node-Gleichheit behandeln. 7. **Gebühren/Wallet:** Browser-Web-Wallet **ab erster funktionaler MVP-Version**; Keys erzeugen, importieren, verschlüsselt sichern/entsperren, Adresse, Balance, senden/empfangen, Verlauf/Netzstatus. Miner/Node erhalten nie Private Keys/Seeds. Zusatzfenster nur mit explizit begrenzter signierter Wallet-Freigabe, auf Runde/Identität/Fenster, Betrag, Nonce und Ablauf gebunden; noch nicht implementiert. UI-Vorschau niemals als funktionsfähig ausgeben. -8. **Tests:** Go `gofmt`, `go test ./...`, `go vet ./...`; passende Integrationstests, fuzzer/race tests. Für PoP Simulation und Pi 4 / Office-PC Benchmark, Fairness, Gebühren-Erwartungswerte, abweichenden Codepfad, Replay, Burn und Restart. Tests und Resultate **nie erfinden**. +8. **Tests:** Go `gofmt`, `go test ./...`, `go vet ./...`; passende Integrationstests, fuzzer/race tests. Für PoP Simulation und Pi 4 / Office-PC Benchmark, Fairness, Gebühren-Erwartungswerte, abweichenden Codepfad, Replay, Fondsgutschrift, echte Burns (falls vorgesehen) und Restart. Tests und Resultate **nie erfinden**. 9. **Sicherheit:** keine öffentlich gebundenen Test-APIs (nur `127.0.0.1`/SSH-Forward), keine echten Schlüssel/Tokens/Datenbank-/Wallet-Dateien in Git. Keine unverhältnismäßigen Dependencies, keine destruktiven Befehle ohne Backup. Entgeltliche Chance auf Lotteriegewinn birgt Rechtsrisiko; bis auf Weiteres **wertlose private Test-PTM**. 10. **Lieferstil:** Vor jeder größeren Änderung kurze Planung + Akzeptanzkriterien; danach Dateien/Änderungen, was real läuft, Testbefehle mit Ergebnissen, Grenzen und nächsten kleinen Schritt angeben. ADR für wesentliche Entscheidungen. ## Zukunftsvision F1–F4 (25.10.2026) Geplante **spätere** PTM-Nutzungen sind digitale Sammlerstücke, ein interner PTM-Marktplatz und eine Handelsplattform für fiktive interne Assets mit Orderbuch, Kursen und Portfolio, optional mit **strikt getrenntem** Paper-Trading. Sie sind **noch nicht implementiert** und erfordern nach M6 eigene ADRs, sichere Eigentums-/Handelsabwicklung und klare Kennzeichnung interner vs. simulierter Kurse. **Nicht** auf Initiative von Codex vorziehen, keine Cosmos-/NFT-/DEX-Frameworks ungefragt integrieren, keine echten Preise oder Volumina erfinden. Sammlerstücke/Assetbesitz dürfen PoP-Chancen nicht erhöhen. Maßgeblich: `docs/ZUKUNFT_OEKOSYSTEM.md`, `docs/decisions/0004-zukunftsvision-internes-oekosystem.md`. + +## Produktentscheidung ADR 0005: Hub/Worker/Operator (25.10.2026) + +Ab M1/M2 nach `docs/HUB_WORKER_CLUSTER.md`, `docs/WORKER_GEBUEHREN_FORSCHUNG.md` und `docs/decisions/0005-hub-worker-cluster-und-betreiber.md` planen. **Hub** ist eine selbstständige Node mit optionalem Mining, Browser-Wallet und lokalem Node-Cockpit; **Worker** ist Hub-gebundener schlanker Miner mit Geräte-ID ohne Coin-Wallet/Full-Chain. Betreiberzuordnung `operator_id` aggregiert 90-Tage-Gewinne, Gebühren, Worker und Auszahlungen. **Pro Betreiber maximal ein kostenloser Hub-Mining-Platz**; zusätzliche Hub-Miner gelten wirtschaftlich als Worker. Jeder zugelassene Miner (auch Hub) maximal 1 Ticket/Runde. Das Hub-/Cluster-Cockpit hat Vorrang vor Admin-Web; Admin-CLI bei Bedarf. Private Wallet-Keys nie an Hub-Backend oder Worker weiterreichen. Fingerprint allein reicht nicht für Authentifizierung. Gebühren für Worker sind weiterhin **FORSCHUNG**, die erste 10-Worker-Grenze und Tarifzahlen sind **nicht beschlossen**; keine Konsenscodierung dieser Zahlen. Gebühren vor Kenntnis der jeweiligen Challenge. Keine offenen Admin/RPC Ports oder Node-seitige Wallet-Seeds. + +## ADR 0006: Infrastruktur und frühe read-only Cockpits (25.10.2026) + +Zusätzlich `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md`, `docs/COCKPITS_UND_MONITORING.md` und ADR `0006-validator-infrastruktur-und-cockpits-ab-devnet.md` **zuerst lesen**. Konsens besteht nur über verfügbare Validatoren mit lokalem Go-App-State; kein zentraler Blockchain-DB-/Admin-Server. Einen 1-Validator-Devnet-Kern und später 4 getrennte Validatoren planen; normale Hubs sind Full Nodes **ohne zwingendes Validatorrecht**, Worker nur Miner. Read-only Network Admin und Hub-Statuscockpit **ab dem ersten lauffähigen Devnet** aus echten APIs speisen (bei noch nicht laufender Chain ehrlich `nicht verbunden`). Sensible schreibende Admin-Webfunktionen bleiben zurückgestellt; CLI nur mit gesondert autorisierten signierten Aktionen. Network Admin darf weder wallet-private Daten noch Coin-State verändern. Auf 127.0.0.1/SSH-Forward begrenzen. **ADR 0007:** PoP-Fenster-/Worker-Gebühren gehen zu 100 % in einen On-chain-Fonds (nicht burnen); andere Gebühren/Fondsausgaben offen. Bestehende Forschungsergebnisse nicht als beschlossene Tarife deuten. Kein Versprechen einer implementierten GUI oder Chain. + +## Aktueller Architekturstand: ADR 0008 (ersetzt zentrale Network Services) + +**VERBINDLICH:** Erst `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md`, `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md`, `docs/COCKPITS_UND_MONITORING.md` und ADR 0008 lesen. **KEIN verpflichtendes `potatium-network`, kein zentrales Benutzerportal, kein zentraler Coin-Server** implementieren. Hub-P2P-Bootstrap anhand vertrauenswürdiger Chain-ID/Genesis und Peer-Liste lokal konfigurieren; Mining-Zulassung ausschließlich on-chain mit signierten Admin-Freigaben/Sperren. Frühestes Devnet muss lesendes Network Admin und Hub-Status-Cockpit mit echten Werten/nicht-verfügbar-Zuständen haben; Node-Signaturen, Wallet-Spend-Keys und Admin-Signatur streng getrennt. Admin-Web darf als frühe Ausnahme nur Identitätsfreigabe/Sperrung mit protokollseitig geprüftem signiertem Workflow auslösen; weder Guthaben noch Tarife noch Fonds eigenmächtig verändern. Optionaler Status-/Heartbeat-Sammler (und SQLite) ist *nicht* Blockchain-/P2P-/Registrierungspflicht. Keine vollständigen Live-Zahlen ohne Datenabdeckung behaupten. + +**Forschung:** `docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md` liefert 36 Monte-Carlo-Läufe als **nicht bindende** Gebühren-/Fonds-Erkenntnisse; 1×/3×/5×, 720 Modellrunden/Tag, 1 PTM/Runde, höchstens 10 Worker und hypothetische Rückverteilung sind *keine* Protokollregeln. 100 % PoP-/Worker-Gebühren an Fonds, 0 % Burn sind dagegen beschlossen; Tx-Gebühren und Fonds-Ausgabeordnung bleiben offen. diff --git a/README.md b/README.md index 9e1366c..9b04ab8 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,25 @@ Potatium soll eine **eigene Layer-1-Blockchain in Go** werden, kein ERC-20-Token - **Web-Wallet ab funktionalem MVP:** Schlüssel und Signaturen bleiben im Browser; die API bekommt niemals Seed oder Private Key. - **Keine reale Wertzusage:** PTM ist ein Hobby-/Testnet-Coin ohne garantierte Einlösung in Geld oder andere Kryptowährungen. +## Infrastruktur: Wer betreibt was? + +**Die Blockchain braucht Validatoren, aber keinen zentralen Coin-Server.** Anfangs betreibt der Projektbetreiber einen Validator im Devnet, anschließend vier getrennte Validatoren im Testnet. Jeder Validator speichert Blockchain-/App-State **lokal** und stimmt über gültige Blöcke ab. **Normale Hub-Full-Nodes prüfen die Chain ohne Validator-Stimmrecht**; Worker sind schlanke PoP-Miner am Hub. Eine browserseitige Wallet gehört logisch zum Betreiber und bleibt ohne Export ihrer privaten Schlüssel aus Hub/Worker heraus. + +| Bestandteil | Betrieb | Zweck | +|---|---|---| +| `potatium-dev` | Projektbetreiber (Ubuntu-LXC) | Git/Go/Tests; zunächst isoliertes Devnet + lokale Früh-Cockpits | +| 1 → 4 Validatoren | Zunächst Projektbetreiber; später ggf. zugelassene weitere Betreiber | CometBFT-Blockkonsens, je eigener State und Schlüssel | +| **Potatium Hub** | Teilnehmer auf VM/Pi/PC, nativ zuerst | Full Node, echte Browser-Wallet, lokale GUI, eigenes Mining, eigene Worker und Clusteransicht | +| **Potatium Worker** | Teilnehmer auf Pi/Alt-PC/VM | Signierter Mining-Agent, keine Full-Chain, keine Geld-Wallet | +| **Potatium Network Admin (read-only)** | Projektbetreiber, anfangs im LXC, später separate Management-VM möglich | Chain-/Validator-/Netzwerküberwachung; keine Coins kontrollieren | + +**Benutzer-Hub und lesender Network Admin gehören schon zum ersten funktionalen Devnet**, mit verifizierten Daten und ehrlichen Offline-Zuständen. Die vollständige Operator-/Validator-Administration per Web kommt später; zunächst geschützte signierte Admin-CLI, sobald benötigt. Die bestehenden UI-Dateien sind noch Entwürfe, keine echte Chain/Wallet. Der Network Admin ist **keine zentrale Instanz, die für die Chain laufen muss**. + +Details: [Gesamtinfrastruktur & Deployment](docs/INFRASTRUKTUR_UND_DEPLOYMENT.md) · [Cockpits & Monitoring](docs/COCKPITS_UND_MONITORING.md) · [ADR 0006](docs/decisions/0006-validator-infrastruktur-und-cockpits-ab-devnet.md). + ## Proof of Potato: aktueller Produktentwurf -Eine Mining-Runde enthält **drei aufeinanderfolgende Aufgabenfenster**. Fenster 1 ist für zugelassene Miner kostenlos. Wer noch kein gültiges Ticket hat, kann Fenster 2 und 3 gegen **fixe und progressive Gebühren** freischalten. Maßstab für die Progression sind zurechenbare **PoP-Gewinne eines gleitenden Zeitraums von ungefähr 90 Tagen**, nicht die aktuelle Wallet-Balance. Gebühren der Zusatzfenster werden verbrannt. Pro freigegebener Mining-Identität wird **höchstens ein Ticket je Gesamtrunde** akzeptiert. Nach Abschluss bestimmt eine später überprüfbare Zufallsquelle den Gewinner. +Eine Mining-Runde enthält **drei aufeinanderfolgende Aufgabenfenster**. Fenster 1 ist für zugelassene Miner kostenlos. Wer noch kein gültiges Ticket hat, kann Fenster 2 und 3 gegen **fixe und progressive Gebühren** freischalten. Maßstab für die Progression sind zurechenbare **PoP-Gewinne eines gleitenden Zeitraums von ungefähr 90 Tagen**, nicht die aktuelle Wallet-Balance. Gebühren aus Zusatzfenstern und zusätzlichen Workern fließen nach ADR 0007 **zu 100 % in einen On-chain-Community-Fonds statt in den Burn**; die konkreten Gebührenhöhen sind offen. Pro freigegebener Mining-Identität wird **höchstens ein Ticket je Gesamtrunde** akzeptiert. Nach Abschluss bestimmt eine später überprüfbare Zufallsquelle den Gewinner. Die exakten Gebühren, Erfolgswahrscheinlichkeiten, Fensterlängen, Emissionsregeln und Coinparameter sind **noch nicht beschlossen**. Simulationen wie „Balanced + Low“ sind ausschließlich Forschungsmodelle. Details: [PoP-Fairness und Gebühren](docs/POP_FAIRNESS_UND_GEBUEHREN.md), [Protokollentwurf](docs/PROTOKOLL_ENTWURF.md) und [ADR 0003](docs/decisions/0003-pop-fenster-und-gebuehren.md). @@ -34,7 +50,7 @@ Die Statusangaben trennen **bereitgestellte Gerüste**, **offene Entscheidungen* | **M1 · Protokoll und Ökonomie** | Coin-/Transaktionsregeln, PoP-Gebühren, Simulationen, Pi-4-Benchmark, ADRs | 🟡 In Planung und Forschung | | **M2 · Chain-Kern + echte Web-Wallet** | Konten, Signaturen, Transfers, persistenter State; Wallet lokal erstellen/importieren, senden/empfangen | ⬜ Offen | | **M3 · Konsensnetz** | CometBFT, zunächst 1, dann 4 Validatoren, Synchronisation und Ausfalltests | ⬜ Offen | -| **M4 · Proof of Potato** | Registry, 3 Fenster, Gebühren/Burn, Tickets, überprüfbare Ziehung, Reward, Negativtests | ⬜ Offen | +| **M4 · Proof of Potato** | Registry, 3 Fenster, Gebühren/Fonds, Tickets, überprüfbare Ziehung, Reward, Negativtests | ⬜ Offen | | **M5 · Bedienung & Hardware** | Wallet fertigstellen, Status/Explorer, Linux amd64/arm64, Raspberry-Pi-Benchmarks | ⬜ Offen | | **M6 · Privates Testnet** | Mehrhost-Betrieb, Recovery, Sicherheit, Dokumentation, Test-Releases | ⬜ Offen | | **F1 · Digitale Sammlerstücke** | Einzigartige/limitierte interne Gegenstände, Ownership, Sammlung in der Wallet | 💡 Zukunftsidee | @@ -72,3 +88,25 @@ make run-ui # Nur statische lokale Web-Wallet-Vorschau Nicht mit echten Schlüsseln oder Guthaben experimentieren. `data/`, Seeds, Passwörter, Zugangsdaten und generierte Binaries gehören nicht in Git. **Nächster Schritt:** M1-Protokollparameter sauber festlegen und PoP-Simulation mit Raspberry-Pi-Messungen abgleichen, bevor konsenskritischer Code entsteht. [Projektplan](docs/PROJEKTPLAN.md) · [Gesamthandbuch](docs/GESAMTHANDBUCH.md) · [Codex-Startprompt](handoff/STARTPROMPT_VSCODE_CODEX.md). + +## Hub und Worker: Geräte im Potatium-Cluster + +Potatium wird in **zwei geplanten Installationspaketen** betrieben. Der **Hub** ist eine autonome Full-Node mit eigenem Web-Cockpit, clientseitig verwalteter Wallet und optionalem PoP-Mining; er funktioniert ohne zusätzliche Geräte. **Worker** sind schlanke, an einen Hub gekoppelte Mining-Instanzen (Pi/VM/Alt-PC) mit eigenem Geräteschlüssel, aber **ohne eigene Coin-Wallet oder vollständige Chain**. Im Hub können eigene Worker und später mehrere berechtigte Hubs gemeinsam beobachtet/verwaltet werden. Hub-/Cluster-UX hat Vorrang vor der separaten Admin-WebGUI. + +Jeder zugelassene Hub-Miner und jeder Worker kann pro PoP-Runde jeweils maximal ein Ticket erzeugen. Die PTM-Belohnungen werden der gemeinsamen registrierten **Betreiber-Auszahlungsadresse** gutgeschrieben. Gebühren und Mining-Gewinne der letzten ca. 90 Tage werden **je Betreiber, nicht je Wallet oder Hub** zusammengefasst. Ein Hub-Mining-Platz pro Betreiber bleibt von zusätzlichen Worker-Gebühren ausgenommen; weitere kostenlose Hub-Mining-Plätze durch Splitten sind nicht erlaubt. **Tarife und Worker-Obergrenze sind noch offen**, siehe [Hub/Worker-Architektur](docs/HUB_WORKER_CLUSTER.md), [Worker-Gebührenforschung](docs/WORKER_GEBUEHREN_FORSCHUNG.md) und [ADR 0005](docs/decisions/0005-hub-worker-cluster-und-betreiber.md). + +**Roadmap-Erweiterung:** M1 konkretisiert Operator-Registry und Cluster-Gebührenforschung; M2 liefert den **lokalen Hub und die funktionierende Browser-Wallet**, M3 beobachtbare Full-/Validator-Nodes, M4 gekoppelte Worker und echtes Mining, M5 Cluster-Cockpit und ARM64-Auslieferung, M6 Mehrhost-/Failover-Tests. Frühere Roadmap-Status bleiben unverändert; **nichts davon ist bereits als laufende Chain implementiert**. + +## Netzwerkzugang und Netzwerk-Admin (ADR 0008) + +**Keine zentrale Benutzerplattform erforderlich:** Hubs verbinden sich über lokal überprüfte Chain-ID, Genesis-Prüfsumme und CometBFT-Seed-/Persistent-Peers direkt mit der Blockchain. Es gibt **keinen verpflichtenden** `potatium-network`-Registrierungs-/Bootstrap-Server und **keine zentrale Coin-DB**. Ein optionaler Statussammler gehört nur zur Telemetrie. + +**On-chain-Mining-Registry statt Benutzerkonto:** Zugelassene Betreiber- und Mining-IDs werden durch protokollautorisierte, signierte Freigaben/Sperren gepflegt. Ein lokaler Node-Fingerprint ist ohne Besitznachweis und Freigabe keine Mining-Berechtigung. Ein Hub bleibt autonom, Worker hängen an ihrem Hub; die gemeinsame browserseitige Wallet dient als Auszahlungsziel, private Spend-Keys bleiben lokal im Browser. + +**Nur für den Netzwerkbetreiber:** Getrenntes Network-Admin-Cockpit **ab erstem funktionsfähigem Devnet**, mit echter Block-/Validator-/Supply-/Burn-/Fonds-/Gebühren-/Registry-Sicht und transparentem Status für registrierte, beobachtete und aktiv minende Nodes. Frühe schreibende Funktionen sind **ausschließlich autorisierte Identitätsfreigabe und -sperre** über eine getrennte sichere Signaturkomponente, nach der entsprechenden M4-Implementierung. Die übrige Admin-Ansicht bleibt lesend. On-chain-Zahlen sind technisch für Full-Node-Betreiber nachvollziehbar, auch wenn der Admin-Bildschirm nur dem Betreiber zur Verfügung steht. + +**Betrieb:** Anfangs nur `potatium-dev` mit getrennten Devnet-/Hub-/Admin-Prozessen; später vier Validatoren und optional ein geschützter `ptm-admin`-LXC. Kein Admin-Server ist Voraussetzung für Konsens. [Installationsplan](docs/INSTALLATIONSPLAN_HOMELAB.md) · [Netz/Identitäten](docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md) · [Cockpits](docs/COCKPITS_UND_MONITORING.md) · [ADR 0008](docs/decisions/0008-schlanker-netzzugang-admin-und-identitaeten.md). + +## PoP-Gebührenforschung (Stand 25.10.2026) + +Die aktuelle **36-Läufe-Langzeitsimulation** mit dynamischen Worker-Entscheidungen und hypothetischen Fonds-Ausschüttungen ist dokumentiert: [Auswertung](docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md) · [Vergleichs-CSV](docs/research/po_p_dynamic_20261025_comparison.csv). Ein 5×-Gebührenmodell reduziert die Zahl aktiver Zusatzworker in diesem Szenario deutlich, jedoch können trotz 0 % Burn **bis zu 70 %** der erzeugten PTM im Fonds gebunden sein. Daraus werden **keine endgültigen Gebühren, Workergrenzen oder Fonds-Auszahlungsregeln** abgeleitet. Die einzige entschiedene Gebührenverwendung bleibt **100 % PoP-Fenster-/Workergebühren zum On-chain-Fonds, 0 % Burn dieser Kategorien**. Alle Simulationen sind Forschung, keine funktionierende Blockchain. diff --git a/docs/ARCHITEKTUR.md b/docs/ARCHITEKTUR.md index e71bf2c..fb40d77 100644 --- a/docs/ARCHITEKTUR.md +++ b/docs/ARCHITEKTUR.md @@ -10,7 +10,7 @@ Web-Wallet (clientseitige Schlüssel) Raspberry Pi / alter PC potatiumd RPC/API (lokal) | deterministic Go State Machine: Konten, Nonces, - PoP-Runden/Fenster, Gebühren, Burn, Tickets, Ziehung + PoP-Runden/Fenster, Fonds-Gebühren, Tickets, Ziehung | eigene CometBFT ABCI++ App | @@ -24,13 +24,13 @@ Web-Wallet (clientseitige Schlüssel) Raspberry Pi / alter PC - **Validatoren:** CometBFT legt Blöcke und Reihenfolge fest; die Go-State-Machine lehnt ungültige Transfers, Burn, Proofs und Mint ab. Kein Coin-/Mining-Bezug zur Validator-Blockerzeugung. - **Miner:** separates Go-Programm für Linux amd64/arm64, ressourcenschonende Arbeit, Node-ID-Signatur, bis drei Fenster pro **einer** Runde, höchstens ein gültiges Ticket. Kann rein kostenlos betrieben werden. - **Wallet:** browserlokale Schlüssel-/Backupverwaltung und Signatur, kein Key-Export an Backend. Bezahlte Zusatzfenster nur nach enger runden-/fenster-/betrags-/noncegebundener Autorisierung, niemals pauschale Miner-Keyfreigabe. -- **State:** jeder Validator hält eigenen deterministischen Ledger inklusive Fensterzustand, signierter Proofs, Mining-Gewinnhistorie der letzten ca. 90 Tage, Betreiberzuordnung, Gebühren/Burn und Ticketlisten-Commit. Der 90-Tage-Begriff benötigt deterministische finale Fensterdefinition (Blockzeit versus Blockhöhe). +- **State:** jeder Validator hält eigenen deterministischen Ledger inklusive Fensterzustand, signierter Proofs, Mining-Gewinnhistorie der letzten ca. 90 Tage, Betreiberzuordnung, Gebühren/Fonds und Ticketlisten-Commit. Der 90-Tage-Begriff benötigt deterministische finale Fensterdefinition (Blockzeit versus Blockhöhe). - **Transport:** localhost-only für Entwicklungs-RPC, SSH-Forwarding, sichere später explizit freigeschaltete P2P-Verbindungen. ## Verifikationskette 1. Runde/Fenster mit finalisiertem Startzustand und identitätsgebundener Challenge. -2. Fenster 1 ohne Zahlung; Fenster 2/3 nur nach bestätigter, integergenau berechneter fixer plus progressiver Burn-Gebühr. +2. Fenster 1 ohne Zahlung; Fenster 2/3 nur nach bestätigter, integergenau berechneter fixer plus progressiver Gebühr an den On-chain-Fonds (ADR 0007). 3. Fristgerechter Proof mit Hash und erlaubter Nonce; max. ein Ticket pro Mining-ID pro Gesamtrunde. 4. Tickets endgültig festschreiben, anschließend fremden unbekannten verifizierbaren Beacon-Wert erhalten und Winner/Mint deterministisch berechnen. 5. Gegen Identitätswechsel, Replay, Zensur, RNG-Manipulation, fehlende Tickets, Down-/Restart und Differenzen zwischen Validatoren testen. @@ -38,3 +38,21 @@ Web-Wallet (clientseitige Schlüssel) Raspberry Pi / alter PC ## Kritische Unbekannte Coin-Einheit, Adress-/Tx-/Signaturformat, Fee-Formel und Rundung, Fenstertiming, Staking/Validator-Gebührenanteile, PI-Benchmark, RNG-Quelle/Fallback, Registry-Gruppierung und persistente Datenbank bleiben offen. Dafür ADRs; keine willkürlichen Defaults. + +## Erweiterung: Hub, Worker und Betreibergruppe (ADR 0005) + +Die bisherige Darstellung „Node und Miner“ wird als **Rollen und Prozessgrenzen** präzisiert. Ein eigenständiger **Hub** ist eine vollwertige Node mit Web-Cockpit, Browser-Wallet und optionalem lokalen Miner. Ein **Worker** ist ein an den Hub gekoppelte Lightweight-Miner ohne Ledger und ohne Geld-Wallet. Jeder registrierte Mining-Prozess erhält eigene `mining_id` und signiert Nachweise. Ein physischer Rechner kann mehrere isolierte Prozesse, Ports und Datapaths ausführen, aber zusätzliche Mining-IDs müssen in der Registry erlaubt sein. + +Ein `operator_id` gruppiert Hub/Worker, zugelassene Mining-IDs, jüngste Mining-Belohnungen und Kosten unabhängig von Wallet-Transfers. **Ein** kostenloser Hub-Mining-Platz pro Betreiber; sonstige Mining-IDs (auch extra Hubs) sind wirtschaftlich zusätzliche Worker. Wallet-Seed nur im Browser; UI/Cluster-Verwaltung ersetzt keine Besitzer-/Wallet-Signatur. Hub-Worker-Verbindung authentifizieren, kein reines Fingerprint-Trust. Selbstständige Validatoren bleiben von Worker/PoP getrennt. + +Details und Bedrohungsmodell: `docs/HUB_WORKER_CLUSTER.md`, `docs/WORKER_GEBUEHREN_FORSCHUNG.md`, `docs/decisions/0005-hub-worker-cluster-und-betreiber.md`. Keine bestätigte Datenbank-/CometBFT-Version oder Implementierung. + +## Präzisierung v0.5: Validatoren, Admin und Datenbanken + +Die obige Grafik ist nur eine reduzierte Zielskizze. Die vollständige Deployment-Architektur steht in `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md` und ADR 0006. Jede CometBFT-Full-Node hält lokal ihren CometBFT- und Go-App-State; **ein Hub ist nicht zwangsläufig Validator**. Ohne aktive Validator-Stimmmacht keine Finalisierung. Potatium-Worker halten keine Blockchain-/Wallet-Spend-Keys. Es gibt keinen globalen Master-Ledger in einer zentralen Datenbank. + +Ein dedizierter **read-only Network Admin** gehört schon zum ersten lauffähigen Devnet, als reiner Beobachtungsdienst ohne Konsens-, Wallet- oder Registry-Schreibrechte. Monitoring-DB optional und nicht konsenskritisch. Das lokale Hub-Web-Cockpit zeigt ebenfalls schon früh echten Chainstatus. Die vollständigen Verwaltungsrechte bleiben getrennt und folgen signierten Konsensaktionen. Siehe `docs/COCKPITS_UND_MONITORING.md`. Gebührenverwendung wird separat evaluiert, nicht durch diesen Architekturhinweis geändert. + +## Aktuelle Vereinfachung durch ADR 0008: Keine Pflicht-Control-Plane + +Der separate Dienst `potatium-network` als obligatorisches Registrierungs-/Bootstrap-Portal ist **aufgehoben**. Ein Hub synchronisiert anhand verifizierter lokaler Genesis-/Chain-ID-/Peer-Konfiguration selbst per CometBFT P2P. Mining-Rechte werden ausschließlich über die signierte On-chain-Betreiber-/Mining-Registry vergeben. Nur der Projektbetreiber erhält das separate, ab Devnet lesende Network Admin mit Supply-/Burn-/Fonds-/Validator-/Identitätsstatus; Freigabe und Sperre sind später gezielt signierte Schreibaktionen. Optionales Telemetrie-Backend darf keinen Konsens-/Mining-Pfad blockieren. 100 % PoP-/Worker-Gebühren gehen weiterhin in den On-chain-Fonds, 0 % Burn. Siehe ADR 0008, `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md` und `docs/INSTALLATIONSPLAN_HOMELAB.md`. diff --git a/docs/COCKPITS_UND_MONITORING.md b/docs/COCKPITS_UND_MONITORING.md new file mode 100644 index 0000000..1e042d7 --- /dev/null +++ b/docs/COCKPITS_UND_MONITORING.md @@ -0,0 +1,74 @@ +# Potatium: Benutzer-Cockpit, Clusterübersicht und Network Admin + +**Produktentscheidung, noch nicht implementiert, 25.10.2026.** Überall echte Quellen und klar erkennbare Fehlerzustände. Beide Anzeigeoberflächen **ab der ersten lauffähigen Devnet-Phase**, nicht erst ab M5/M6. Die jeweilige Funktionstiefe wächst mit der Architektur; UI ohne Node darf ausschließlich „nicht verbunden“ anzeigen. + +## Drei getrennte Blickwinkel, zwei Produktlinien + +1. **Potatium Hub (Teilnehmer):** Lokale Full Node mit **eigenem** Web-Cockpit und integrierter browserseitiger Wallet, optionalem eigenem PoP-Mining und Verwaltung der **eigenen** Worker. Man kann mehrere berechtigte Hubs desselben Betreibers im Hub-Cockpit zu einer Clusteransicht aggregieren. Ein Hub bleibt ohne zentralen Cluster-Master bedienbar. Wallet-Schlüssel verbleiben clientseitig. +2. **Potatium Worker (Teilnehmer):** Keine vollständige Blockchain-/Wallet-GUI nötig; einfacher Pairing-/Health-/Diagnosemodus optional. Konfiguration und Status primär über den zugeordneten Hub. Worker meldet signiert, Hub und Chain validieren Ergebnisse unabhängig. Private Wallet-Schlüssel nie auf Worker. +3. **Potatium Network Admin (Netzwerkbetreiber):** **Von Beginn an read-only** und vollständig getrennt vom Benutzer-Hub. Beobachtet bestätigten Chain-State, bekannte Validatoren, registrierte Operator-/Mining-Identitäten und freiwillig angebundene Host-Metriken. Keine Freigabe-/Sperr-/Validator-Änderungs-Buttons in erster Version. Für spätere Änderungen existiert eine getrennte geschützte Admin-CLI, später möglicherweise Web-Workflow mit expliziter Signatur. Ein öffentliches „Super-Admin-Token“ ist nicht vorgesehen. + +## Abgrenzung der Rechte + +| Ansicht/Aktion | Hub (Teilnehmer) | Cluster eigener Hubs | Network Admin read-only | Späterer registrierter Admin | +|---|---|---|---|---| +| Blockchainhöhe, Blöcke, Transaktionen, Netzwerk | Lesen | Lesen | Lesen | Lesen | +| Eigene Wallet-Balance und signierte Transfers | Browser-Wallet | Nur explizit gekoppelt, keine Keys verteilen | **Nein** als Admin-Privileg | **Nein** | +| Eigene Worker starten/pausieren, Limits und Gebührenbudget | Ja, lokale Berechtigung | Ja, je Hub freigegeben | Nein | Nein | +| Eigene Node-Version, Sync, Logs, Ressourcen | Ja | Ja, nur gekoppelte Nodes | Nur publizierte/ausdrücklich freigegebene Daten | ggf. gesonderter Infrastrukturzugang | +| Andere Betreiber-Identitäten anzeigen | Nur zulässige öffentliche Daten | Nur zulässige öffentliche Daten | Registry-/Aggregatdaten soweit freigegeben | auditierbar | +| Miner/Validatoren genehmigen/sperren | Nein | Nein | **Nein** | erst signierte, regelkonforme Aktion | +| Guthaben verändern, Lose festlegen, Konsens umgehen | Niemals | Niemals | Niemals | Niemals | + +## Früheste Anzeige: Datenquellen und Widgets + +| Kennwert | Quelle | Hub | Network Admin | Umgang bei fehlender Quelle | +|---|---|---|---|---| +| Chain-ID/Netzwerk (devnet/testnet/dauerhaft) | lokale geprüfte Nodekonfiguration/Genesis | Ja | Ja | nicht verbunden | +| aktuelle Blockhöhe, letzte Finalisierung | CometBFT-RPC + bestätigter App-State | Ja | Ja | Höhe unbekannt, letzte Messung mit Alter | +| Node-/Validator-Peers, Voting-Power, Sync | CometBFT-RPC/Metriken | eigene Node | alle beobachtbaren | keine Erfindungen, „nicht erreichbar“ | +| App-Hash und Abweichungen | verifizierte Status-/Block-Daten | optional | besonders wichtig | Alarm ohne falsche Ursache | +| TPS/Blockrate, Transaktionsfehler | beobachtete bestätigte Blöcke/Node-Metriken | Netzwerk | Netzwerk | Datenlücke markieren | +| Supply/minted/burned/fee allocation | bestätigter Potatium-App-State | Netzwerk | Netzwerk | „nicht verfügbar“ bis implementiert | +| Registrierte Operator/Mining-IDs, aktive Tickets, Runden | bestätigter Potatium-App-State | zulässige Übersicht | Aggregate/Registry | fehlende Chain-Funktion markieren | +| Wallet Balance/Transfers | signierte Wallet-Adresse + echte Chainabfrage | nur freigeschaltete Wallet | kein privater Wallet-Zugriff | nicht verbunden statt Fake-Balance | +| Hub/Worker CPU/RAM/Temperatur/Status | jeweiliger freigegebener Agent/OS | eigene Geräte | nur opt-in Metriken | nicht beobachtbar | +| Fehler/Logs/Alerts | Node-API, dedizierter Telemetriedienst | eigene Node | freigegebene Ereignisse | Zeitstempel und Quelle anzeigen | + +Erste UI-Runde: **Lese-Dashboard vor vollständigem Mining**, per minimaler `/status`-Schnittstelle mit gesundem Fehlerverhalten, kein Warten bis zur Multi-Host-Phase. „Online“ bedeutet nur eine erfolgreich geprüfte Quelle, niemals implizit, dass die ganze Chain gesund ist. Chain-Daten und hostbezogene Monitoringdaten getrennt beschriften. + +## Architektur des Network Admin read-only + +- Eigenständiger kleiner Go-Prozess/Service und statisches Browserfrontend, zunächst auf `potatium-dev`, später optional auf separater Management-VM. **Kein Validator, keine Full Node und keine Konsensdatenbank erforderlich**; verbindet sich als Client mit bereits vorhandenen geschützten Node-Statusdiensten. +- Inventar enthält Beobachtungsendpunkte und optional rollenbezogene Labels. Speicherung anfänglich kleine lokale Konfiguration, optional SQLite/Zeitreihen-DB für Graphen/Historie. Niemals zentrale Source of Truth für den Ledger. +- Status-Zugriff gegen lokale `127.0.0.1`-Endpoints oder über VPN/SSH-Tunnel/geschützte Monitoring-Agenten. Keine generisch ungesicherten Public-RPC/Prometheus-Endpunkte; kein Remote-Exec/SSH-Root durch das Dashboard. +- Trennung zwischen **Chain-beobachtbaren** Nodes/Mining-IDs und **maschinenbezogenen** Kennwerten, die nur Betreiber mit Opt-in teilen. Der Admin kann nicht alle VMs einer fremden Node automatisch erkennen oder steuern. +- Kein Blockieren des Netzwerks durch Monitoring-Anfragen; Cache/Rate-Limits/Timeouts, Lese-only Konfig, keine privaten Walletdaten im Log. Statuszeitpunkte und Staleness transparent. + +## Sicherheits- und UX-Anforderungen + +- Hub-/Wallet-Endpunkte auf `127.0.0.1`, Zugriff im Entwicklungs-LXC via SSH-Forward; andere Hosts erst per explizit autorisiertem privatem Tunnel, HTTPS und Authentifizierung. +- Browser-Wallet: verschlüsselte lokale Speicherung, signierte Ausgaben ohne Seed-Exfiltration, Backup/Recovery, nicht automatisch auf andere Hub-Browser kopieren. +- Node-/Worker-Kopplung mit zeitlich begrenzter Einladung und gegenseitigem kryptografischem Besitznachweis, nicht nur Fingerprint. Getrennte Operator-/Wallet-/Mining-/Validator-Schlüssel. +- Gebührenreserve und Nutzerlimit für unbeaufsichtigte Worker-Mining-Gebühren erst nach expliziter Autorisierung; read-only Admin kann nie Gebühren abbuchen. +- Ein Hub darf mehrere Workerdatenansichten und mehrere **eigene** Hubs in seiner Clusteransicht zeigen, aber nicht die privaten Daten fremder Betreiber. + +## Akzeptanztests je Ausbaustufe + +- **UI vor Chain:** Beide Dashboards starten/anzeigen klar „nicht verbunden“, keine Fake-Daten. +- **M2 erster Devnet-Kern:** echte Chain-ID, Höhe, Header/Blockstatus; Hub und Admin melden konsistente Werte; Wallet integriert echte Daten, sobald Signaturformat verfügbar. +- **M3 Validatoren:** 1- und 4-Validator-Szenario; Down/Restart, Peer-Status, >2/3 Quorum, AppHash, Stale-Alerts; Admin aus => Chain läuft weiter. +- **M4 Worker:** mehrere Mining-IDs, Worker-Pairing, Hub-/Worker-Mining, Freigaben/Budgets, Gebühren und Gewinne mit echten Chain-Daten. +- **M5/M6:** Multi-Hub-Cluster UX und Mehrhost-Überwachung, Monitoring-Lücken, Recovery, Sicherheitsgrenzen. + +**Keine Produktivitätsbehauptung:** vorhandene `web/wallet` bleibt reine Vorschau. Noch keine Backend-Schnittstellen, GUI/DB/Chain/Minerdienste für die oben beschriebenen Funktionen implementiert. + +## Betreiberansichten und Identitätsverwaltung (ADR 0008) + +**Ab erstem funktionsfähigem Devnet:** Nur für den Projektbetreiber read-only Network Admin mit tatsächlichen Chain-/Validator-/Peerwerten. Anzeige für bestätigte Coin-Emission, aktuelles Supply, kumulierten Burn (0 für PoP-/Workergebühren), Fondsbestand/-Ein-/-Ausgänge, Gebührenparameter, Betreiber-/Mining-Registrierungsstatus, Freigaben/Widerrufe, beobachtbare Peers und PoP-Runden soweit schon implementiert. Jede Kennzahl enthält Chain-ID, Blockhöhe/Messzeit, Datenquelle und Stale-/Fehlerstatus. Für fehlenden Ledger/Registry-State **„nicht verfügbar“ statt Mockzahlen**. + +**Identitätsaktionen:** Anzeige und Filter von `operator_id`, `node_id`, `mining_id`, zugehöriger Reward-Adresse sowie Genehmigungs-, Sperr- und Auditstatus. **Einzige frühen Schreibaktionen:** Neue Betreiber-/Mining-IDs signiert freigeben, sperren/widerrufen (Key-Rotation später nach separater Spezifikation). Bis sichere Signatur-/Protokollfunktionen existieren: read-only, keine Scheinfreigaben. Der Admin-Webserver enthält keinen privaten Admin-Signaturschlüssel; getrennte CLI/Signaturkomponente mit 2-Stufen-Bestätigung, Replay-Schutz und On-chain-Finalisierung. + +**Aktive Nodes:** Die globale Gerätezahl ist nicht allein aus einem lokalen Peer-Set beweisbar. `registriert`, `kürzlich beobachtet`, `signiert meldend` und `PoP-aktiv` getrennt mit Datum/Abdeckung anzeigen. Optional signierte Heartbeats über *freiwilligen*, geschützten Monitoring-Empfänger; SQLite-Historie optional, keine zentrale Registrierungsdatenbank-Pflicht. + +**Vertraulichkeit:** Nur die Admin-GUI und eigene Off-chain-Telemetrie sind zugriffsbeschränkt. On-chain-Fonds, Gebühren und ggf. Registry-Informationen sind über Full Nodes prinzipiell abfragbar. Private Betreiber-Kontakte/Geräte-IP nicht auf replizierten Chain-State schreiben. [ADR 0008](decisions/0008-schlanker-netzzugang-admin-und-identitaeten.md), [Netzzugang](NETWORK_ZUGANG_UND_IDENTITAETEN.md). diff --git a/docs/GESAMTHANDBUCH.md b/docs/GESAMTHANDBUCH.md index bf05647..34ec72d 100644 --- a/docs/GESAMTHANDBUCH.md +++ b/docs/GESAMTHANDBUCH.md @@ -51,7 +51,7 @@ Die abgelehnte Idee „Gebühr pro aktuellem Wallet-Guthaben“ lässt sich durc `Fenster 2 = Basisbetrag2 + Progressive(90-Tage-Ertrag)`; `Fenster 3 = Basisbetrag3 + stärkerer progressiver Anteil`. **Alle exakten Prozentsätze, Stufen, Beträge und Rundungsregeln sind offen**. Bei Erfolg in Fenster 1 oder 2 fallen spätere Gebühren nicht an; Zusatzgebühren im Misserfolgsfall werden nicht erstattet. -**Zusatzgebühren werden verbrannt**, normale Überweisungsgebühren sollen die Validatoren finanzieren (Aufteilungsregeln noch offen). Mining funktioniert kostenlos mit 0 PTM über Fenster 1. Die Wallet gibt kostenpflichtige Fenster einzeln oder per eng begrenzter, widerrufbarer Autorisierung frei. Der Miner erhält niemals Browser-Wallet-Secrets. +**PoP-Fenster-/Workergebühren gehen laut ADR 0007/0008 zu 100 % an den On-chain-Fonds, 0 % Burn dieser Kategorien**; normale Überweisungsgebühren und deren mögliche Validatorfinanzierung sind noch offen. Mining funktioniert kostenlos mit 0 PTM über Fenster 1. Die Wallet gibt kostenpflichtige Fenster einzeln oder per eng begrenzter, widerrufbarer Autorisierung frei. Der Miner erhält niemals Browser-Wallet-Secrets. ### Wichtige Grenzen @@ -93,3 +93,19 @@ Ein sauberes Go-Gerüst und eine HTML-Oberfläche sind **noch keine nutzbare Blo Die im Oktober 2026 ergänzte Vision sieht **nach stabiler Kern-Chain und Web-Wallet** digitale Sammelobjekte (F1), einen in PTM abgewickelten internen Marktplatz (F2), eine Handelsoberfläche für fiktive interne Assets mit Orderbuch, Kursen, Charts und Portfolios (F3) und optional einen strikt getrennten Paper-Trading-Modus (F4) vor. Diese Szenarien sollen PTM einen internen Nutzwert geben. Sie begründen **keinen externen Euro-Kurs**, keine Börsenlistung und keine Anlage- oder Renditeaussage. Asset-Kauf, Sammelgegenstände oder Bestandshöhen dürfen die PoP-Lotterie nicht zusätzlich bevorteilen. Die technischen Details (Assetmodell, Emission, Marktdaten, atomare Trades, Ordermatching, Gebühren und Schutz gegen Wash Trading) sind absichtlich offen. Keine Funktionen sind implementiert. Maßgeblich: `docs/ZUKUNFT_OEKOSYSTEM.md`, `docs/ROADMAP.md` und ADR 0004. Die Arbeiten an M1–M6 behalten Vorrang. + +## Architekturergänzung v0.4: Hub, Worker, Cluster + +Potatium Hub ist die autonome Full-Node mit Client-Wallet, eigenem Mining und lokalem Cockpit. Ein Worker ist ein schlanker an den Hub gebundener Miner ohne eigenständiges Coin-Wallet. Eine Betreiber-Identität gruppiert diese Instanzen und deren Kosten/PoP-Gewinne, auch bei mehreren Hubs/Wallets. Es gibt einen kostenfreien Hub-Mining-Platz je Betreiber; zusätzliche genehmigte Mining-IDs zählen wirtschaftlich als Worker. Jeder zugelassene Miner max. ein Ticket je Runde. Worker-Gebühren zusätzlich zu PoP-Fenster-2/3 sind noch Forschungsmodell. Priorität Node-/Cluster-Cockpit, später separate Admin-WebGUI. Für Details sind `docs/HUB_WORKER_CLUSTER.md`, `docs/WORKER_GEBUEHREN_FORSCHUNG.md` und ADR 0005 maßgeblich. + +## Infrastruktur und Cockpits (Ergänzung v0.5) + +Die Betriebsrollen, Datenspeicherung und Deployment-Stufen werden in `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md` vollständig erklärt. Ab dem ersten funktionalen Devnet werden **beide lesenden Ansichten** umgesetzt: lokaler Potatium-Hub mit Wallet- und Node-Status sowie separater **Network Admin read-only** für Blockchain-/Validatorüberblick. Die administrative Schreibverwaltung kommt später. Begründung und Grenzen dokumentiert ADR 0006; UI-/Metrikkriterien stehen in `docs/COCKPITS_UND_MONITORING.md`. Kein zentraler Chain-Datenbankserver und keine Abhängigkeit der Blockchain vom Monitoring. + +## Netzwerkzugang und Betreiberverwaltung (aktueller ADR 0008) + +**Keine zentrale Benutzerplattform oder Pflichtregistrierung für den P2P-Sync.** Authentische Chain-ID, Genesis und CometBFT-Seed-/Persistent-Peers werden pro Node konfiguriert. Nur PoP-Mining erfordert autorisierte On-chain-Registry und operatorbezogene Gebühren-/Gewinnhistorie; Freigabe/Sperre im geschützten Network Admin über gesonderte signierte Aktionen. Der Network Admin ist ab erstem lauffähigem Devnet zunächst lesend mit echter Supply-/Burn-/Fonds-/Gebühren-/Registry-/Validatorübersicht, inaktiv markierten Freigabeaktionen bis zum vollständigen Signatur-Workflow. Optionales Monitoring-Inventar ist nicht konsenskritisch. Hub und Worker sind die beiden Teilnehmerpakete; Auszahlungs-Wallet-Schlüssel bleiben lokal im Browser. **100 % PoP-/Worker-Gebühren fließen in den On-chain-Fonds**, keine Burns dieser Gebühren. `docs/INSTALLATIONSPLAN_HOMELAB.md`, `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md` und ADR 0008 sind aktueller Stand. + +## Erkenntnisse der Dynamik-/Fonds-Simulation vom 25.10.2026 + +36 Forschungsdurchläufe zeigen, dass dynamische Worker-Steuerung mit höheren Gebühren aktive große Cluster bremst, zugleich aber die Fondsbindung stark ansteigen kann: 3× dynamisch 40,59 % des erzeugten Supply, 5× fix 70,20 % bei gehaltenem Fonds. Die hypothetische Rückverteilung ist ohne Sybil-Schutz nicht als Governance-Beschluss geeignet. **Keine Workergebühr, Rundenlänge, Rewardhöhe oder Fonds-Auszahlung beschlossen.** Siehe `docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md`. diff --git a/docs/HUB_WORKER_CLUSTER.md b/docs/HUB_WORKER_CLUSTER.md new file mode 100644 index 0000000..34c9af8 --- /dev/null +++ b/docs/HUB_WORKER_CLUSTER.md @@ -0,0 +1,90 @@ +# Potatium Hub, Worker und Cluster: Architekturentwurf v0.4 + +**Stand: 25.10.2026 | Produktentscheidung zum Paketmodell, Gebührenparameter weiterhin offen.** + +## Begriffe und zugesagte Eigenschaften + +| Objekt | Was es ist | Wallet / Schlüssel | Funktion | +|---|---|---|---| +| Betreiber / Operator (`operator_id`) | manuell genehmigte Berechtigungs- und Wirtschaftsgruppe | besitzt eine oder mehrere Wallet-Adressen, aber keine serverseitige Master-Seed-Verwahrung | gemeinsame 90-Tage-PoP-Gewinnhistorie und Gebührenbasis | +| Hub (`hub_id`) | vollwertige Potatium-Node mit eigener kryptografischer Node-ID und eigenem lokalen Web-Cockpit | Wallet wird im Browser eingerichtet, signiert clientseitig; Hub-Backend hat **keinen** Seed/Private Key | Chain-/P2P-Teilnahme je Rolle, lokale UI/API, eigener optionaler PoP-Miner, Worker-Anbindung | +| Worker (`worker_id`) | schlanke, an Hub gekoppelte Mining-Instanz, z.B. Raspberry Pi/VM/PC | eigener **Geräteschlüssel**, keine Geld-Wallet, keine Wallet-Seeds | Challenge prüfen, begrenzte PoW-Arbeit, Proof signieren und via Hub fristgerecht einreichen | +| Validator (`validator_id`) | CometBFT-Konsensrolle | separater Validator-Schlüssel, kein Wallet-Key | Blockchain-Blöcke finalisieren, PoP-Lose nicht auswählen/produzieren | +| Auszahlungsadresse | explizit am Operator registrierte Wallet-Adresse | Private Key im Wallet-Client | empfangene PoP-Belohnungen aus Hub und Workern | + +Ein physischer Host kann mehrere Prozesse/Instanzen mit isolierter Konfiguration, Ports, Datenverzeichnissen und eigenen Schlüsseln betreiben. Das bedeutet **nicht** mehrere automatisch zugelassene Mining-Lose: nur ausdrücklich registrierte Mining-IDs zählen. Eine Node-ID beweist keine eigene Hardware und ein Operator-ID-Eintrag keine einzigartige natürliche Person. + +## Zwei Distributionspakete, drei Oberflächen + +1. **Potatium Hub**: autonome, vollständige Node mit Web-Wallet-Bedienung, Explorer, eigener Mining-Funktion, lokaler Status- und Worker-/Cluster-Ansicht. Ist ohne Worker funktionsfähig. Ein Hub ist nicht zwingend Validator. +2. **Potatium Worker**: separater ressourcensparsamer Mining-Agent ohne vollständigen Chain-State und ohne eigenständige Geld-Wallet. Über einen Hub kontrollierbar. Ohne erreichbaren Hub kann er keine rechtzeitige Proof-Übermittlung garantieren. Lokale Setup-/Diagnoseoberfläche optional, aber kein vollständiger Node-Hub. +3. **Potatium Admin**: spätere Betreiberverwaltung (vorerst CLI), streng getrennt von normaler Hub-/Clusterverwaltung. Admin-Tool ist **kein** drittes Mining-Paket und nicht zum willkürlichen Ändern von Guthaben befugt. + +```text + Betreiber / operator_id [Registry] + | Wallet im Browser + | Auszahlung an registrierte Adresse + Potatium Hub (autonom) + Node/Explorer/Wallet/Mining + / | \ + Worker A Worker B Worker C + Gerät-ID Gerät-ID Gerät-ID + + P2P/ABCI++ der Full-/Validator-Nodes bleibt getrennt + vom Hub-Worker-Mining-Kontrollkanal. +``` + +### Kopplung und lokale GUI + +- Hub-GUI zunächst ausschließlich `127.0.0.1:` und SSH-Portforwarding; mehrere lokale Hub-Instanzen nutzen getrennte Ports und Datapaths. Später private HTTPS/VPN-Zugänge mit Login; **keine** öffentliche Admin-/RPC-Bind-Adresse per Default. +- Worker-Kopplung mit **kurzlebigem, einmaligem Enrollment-Token oder signierter Einladung**, QR-Code für Komfort und anschließend gegenseitiger kryptografischer Authentifizierung. **Ein Fingerprint allein ist keine Autorisierung**. +- Hub prüft Registrierungsstatus, Worker-Identität, Challenge/Runde/Fenster, Signatur, Zeitfenster, Nonce-Space und Duplikate vor Weiterleitung; die ABCI++-Anwendung muss dieselben Regeln unabhängig im Konsens durchsetzen. +- Worker kann unter Freigabe widerrufen oder zwischen Hubs desselben Betreibers umgezogen werden; identischer `worker_id` darf in derselben Runde nicht mehrfach abrechnen/teilnehmen. +- Hub-Ausfall: Worker-Status klar als nicht erreichbar/offline kennzeichnen, keine erfundenen Proofs/Gewinne. Backup-Hub und Failover sind spätere Funktionen und dürfen Tickets nicht duplizieren. +- Hub-Cluster-Dashboard verwaltet **eigene** authentifizierte Hubs und Worker; es hat keine Berechtigung, fremde Betreiber zu administrieren. Kein automatisches Wallet-Key-Sharing oder API-Server mit Seed-Phrase. + +## Mining-Teilnahme und gemeinsame Ökonomie + +- **Jede genehmigte Mining-Instanz**, auch ein Hub mit aktivierter PoP-Rolle, kann **maximal ein Ticket pro PoP-Gesamtrunde** erhalten; die bestehende 3-Fenster-Logik gilt für jede Instanz. +- Beim Betreiber werden **alle** PoP-Gewinne seiner Mining-IDs während des später exakt spezifizierten ~90-Tage-Zeitraums zusammengeführt. Wallet-Transfers, Wechsel der Auszahlungsadresse und Worker-Umzug setzen den Fortschritt nicht zurück. +- **Ein kostenloser Hub-Mining-Platz pro Operator** ist die Arbeitsregel des Konzepts. Dieser Hub wird nicht in die Worker-Gebühr eingerechnet; er unterliegt trotzdem den normalen Fenster-2-/Fenster-3-Gebühren. +- Weitere physische Hubs dürfen technisch betrieben werden (z.B. Ausfallsicherheit). Sie erzeugen **keinen zweiten kostenlosen Hub-Mining-Platz**. Zusätzliche Mining-Identitäten gelten bei Kosten/Quoten wie Worker-Instanzen; eine bloße GUI-/Full-Node-Instanz ohne Mining zählt nicht. +- Sämtliche vom Operator zugelassenen Worker müssen in der verbindlichen Chain-Registry diesem Betreiber zugerechnet werden. **Mehrere Hubs/Walletts dürfen keine getrennten kostenlosen Betreiberidentitäten simulieren.** Beim kleinen privaten Netzwerk zunächst manuelle Betreiberzulassung und Audit; kryptografische Verhinderung von Sybil-Angriffen ist damit nicht erreicht. +- Die registrierte Auszahlungsadresse erhält sämtliche Hub-/Worker-Mining-Belohnungen des Betreibers. Eine spätere Änderung braucht signierte, replay-geschützte Autorisierung und eindeutige Wirksamkeitsgrenze. Zusätzliche Wallets für normale Transfers sind möglich, verändern aber Mining-Fee- und Betreiber-Identität nicht. + +### Zahlungsautorisierung ohne Wallet-Seeds auf Nodes + +Mininggebühren (zusätzliche Fenster und eventuell Worker-Aktivierung) sind aus demselben **Betreiber-Budget** zu bezahlen. Eine dauerhaft online erforderliche Freigabe ist nicht praktikabel, wenn die Seed/Private Keys ausschließlich im Browser liegen. **Vorgeschlagene** Lösung: zweckgebundene, on-chain verbuchte Mining-Gebührenreserve je Operator. Befüllung/Rückzahlung signiert die Browser-Wallet; die deterministische App darf daraus ausschließlich die ausdrücklich begrenzten, berechenbaren Mining- und Worker-Gebühren belasten. Zusätzlich: Zeit-/Betragsobergrenzen, Sperrung/Widerruf, Audit und sichere Restguthaben-Rückgabe. Noch keine Implementierungsentscheidung; ein alternatives begrenztes signiertes Mandat ist möglich. Niemals Miner-/Node-/Worker-Schlüssel zu Wallet-Spend-Keys machen. + +## Operatives Cockpit vor Admin-Web + +**Hub lokal:** Blockhöhe, Sync, Peers, Versions- und Ressourcenstatus, Wallet-Verbindung, PoP-Runde/Fenster/Tickets/Gewinne, Gebührenhistorie/Budget, Worker-Anmeldung/Freigabe/Pause, pro Worker CPU-/Fensterlimit, Fehler, einzelne Daten des eigenen Clusters. + +**Cluster-Sicht:** mehrere zugeordnete Hubs/Worker in einer Übersicht mit Host-Status, Versionen, Mining-Modus, zentraler (aber begrenzter) Einstellungs-/Update-Orchestrierung; keine zentralen Wallet-Seeds und keine direkte Chain-State-Manipulation. Der eigenständige Hub bleibt immer einzeln bedienbar; Cluster-Sicht ist optional. + +**Admin (später):** separate authentifizierte Betreiber-/Validator-/Miner-Registry, Revocation, Audit, koordinierte Upgrades, Netzwerkdiagnose. Zunächst sichere CLI ab dem Zeitpunkt, an dem diese Funktionen benötigt werden. + +## Kostenmodell und Sicherheitslücken + +Siehe [`WORKER_GEBUEHREN_FORSCHUNG.md`](WORKER_GEBUEHREN_FORSCHUNG.md). **Keine finalen Beträge**. Für Worker-Aktivierungen ist Zahlung/Reservierung vor der unbekannten Challenge notwendig; sonst können schnelle Geräte nur erfolgreiche Versuche bezahlen. Keine Kosten für Worker, die gar nicht zur Runde angemeldet wurden. Festlegen, wie verpasste Runden, Downtime, Replay und Wiederanmeldung bilanziert werden. + +## Akzeptanzkriterien vor Implementierung + +- Eigenständiger Hub funktioniert ohne Worker, Worker funktioniert nur gekoppelt; getrennte Keys/Rollen, keine Seeds auf Server. +- Zwei Worker mit eigenen ID-Schlüsseln lassen sich koppeln, sperren und eindeutig demselben Betreiber zuordnen. +- Pro ID/Runde höchstens ein gültiges Ticket, unabhängig von parallelen Prozessen/Hubs; Hub selbst als Mining-ID zählt. +- Gebühren/Rewards aggregieren **operator_id**, nicht Wallet-Adresse oder `hub_id`; Identitäts-/Wallet-Wechsel resetten keine Progression. +- Zusätzlicher Hub verschafft keinen zweiten kostenlosen Slot; Registry ist manuell und auditierbar. +- Zahlungssnapshot vor dem jeweiligen Challenge-Seed; verspätete und doppelte Aktivierungen abweisen. +- Funktionsfähige UI erst bei realer API; weder Mock-Balance noch Fake-Tickets als echt anzeigen. +- Deployment native `linux/amd64` / `linux/arm64` zuerst, Docker **später optional**, kein Docker im Entwicklungs-LXC. + +**Status:** Dieses Dokument spezifiziert eine angestrebte Produktarchitektur. Kein Teil davon ist als bereits funktionierend zu betrachten. + +## Ergänzung v0.5: Abgrenzung zum Netzwerk-Admin + +Der **Teilnehmer-Cluster** in einem Hub verwaltet ausschließlich die eigenen authentifizierten Hubs/Worker und verbindet niemals fremde Wallet-Private-Keys. **Potatium Network Admin** ist dagegen eine separate, von Projektbeginn geplante **read-only** Beobachtung des gesamten erreichbaren Chain-/Validatornetzes mit optional freigegebenen Node-Metriken. Er hat keine Workersteuerung fremder Betreiber, keine zentralen Blockchain-Schreibrechte und ist **keine** zusätzliche Chain-Infrastruktur, deren Ausfall Blöcke stoppt. Validatorbetrieb wird anfangs vom Projektbetreiber bereitgestellt. Technische Einzelheiten: `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md`, `docs/COCKPITS_UND_MONITORING.md`, ADR 0006. + +## Netzwerkzugang und Betreiberrechte (aktueller ADR 0008) + +Hubs benötigen zum P2P-Sync kein Benutzerkonto und keinen Pflichtdienst `potatium-network`; die lokale Konfiguration enthält überprüfte Chain-ID/Genesis und Seed-/Peer-Adressen. Eine **separat autorisierte On-chain-Registry** bestimmt, welche Betreiber-/Hub-/Worker-Mining-IDs Tickets einreichen dürfen. Betreiber können mehrere Hubs/Worker und eine gemeinsame Reward-Adresse besitzen. Weitere miningfähige Hubs zählen wirtschaftlich als zusätzliche Worker, nicht als neue kostenlose Betreiberplätze; Sybil-Limit nur durch nachvollziehbare manuelle Freigabe. Im Hub werden eigene Cluster- und Wallet-Daten gezeigt, im Network Admin globale Registry-/Supply-/Fonds-/Validatorübersicht. Identitätsfreigabe/-sperre: geschützte Admin-Signatur, on-chain finalisiert; keine zentralen DB-Flags. Gebühren: 100 % On-chain-Fonds, keine PoP-Burns. Siehe ADR 0008 und `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md`. diff --git a/docs/INFRASTRUKTUR_UND_DEPLOYMENT.md b/docs/INFRASTRUKTUR_UND_DEPLOYMENT.md new file mode 100644 index 0000000..69f838f --- /dev/null +++ b/docs/INFRASTRUKTUR_UND_DEPLOYMENT.md @@ -0,0 +1,127 @@ +# Potatium: Gesamtinfrastruktur, Datenhaltung und Deployment + +**Status: akzeptierte Ziel- und Betriebsarchitektur, nicht implementiert. Stand: 25.10.2026.** Siehe ADR 0006. Der tatsächliche Git-Stand hat Vorrang. Die konkreten CometBFT-Versionen, Datenbank, Ports, Service-Prozesse und API-Schemata sind erst bei der Implementierung verbindlich festzulegen. Alle hier genannten Namen für Deployments und Adressen sind Beispiele, keine bereits laufenden Dienste. + +## Grundsatz: Kein zentraler Blockchain-Master + +Eine funktionierende Chain erfordert aktive CometBFT-Validatoren und die eigene deterministische Potatium-ABCI++-Anwendung. Jeder Full-/Validator-Node verwaltet lokal eine eigene konsistente Kopie der Chain beziehungsweise des Anwendungszustands. **Keine gemeinsame SQL-Datenbank, kein zentraler Coin-Server, kein Admin-Cockpit als Konsensabhängigkeit.** + +```text + Devnet / privates Testnet / dauerhaftes Netz (getrennte Chain-IDs) + +--------------------------------------------------------------+ + | VAL-1 VAL-2 VAL-3 VAL-4 | + | CometBFT + Potatium-App + je eigener lokaler State | + | signieren und finalisieren Blöcke per Konsens | + +-----------------------------+--------------------------------+ + | authentisches P2P/Chain-Protokoll + +-------------------+-------------------+ + | | + HUB Betreiber A HUB Betreiber B + Full Node + API + lokale WebGUI Full Node + API + WebGUI + optional PoP-Miner optional PoP-Miner + Wallet-Signatur NUR im Browser Wallet-Signatur NUR im Browser + | | | + Worker A1 Worker A2 Worker B1 + eigener Mining-Key, keine Full-Chain, keine Wallet-Keys + \\ // + Read-only Network Admin (geschuetztes Monitoring) + liest gesicherte Messwerte / Chainabfragen; keine Coins + optional eigene Messdatenbank, NICHT Konsens-State +``` + +Die logische Hub-Worker-Kopplung ist **nicht** das CometBFT-P2P-Netz. Ein Hub ist eine Full Node und kann optional die Validatorrolle besitzen, aber ein normaler Worker ist weder Validator noch Full Node. Ein Betreiber kann mehrere Hubs kontrollieren; ein Hub kann ohne Worker laufen. Dasselbe physische Gerät kann technisch mehrere sauber getrennte Prozesse ausführen, ohne neue Hardwareidentitäten zu beweisen. + +## Rollen, Prozesse und Eigentum + +| Rolle | Betreibt wer? | Binär-/Dienstkonzept | Lokale Daten | Muss online sein? | +|---|---|---|---|---| +| Dev-Host | Projektbetreiber | Git, Go, Tests, Devnet-Prozesse | Quellcode und *separate* Test-Chains | Nur zur Entwicklung | +| Validator | Anfangs Projektbetreiber, später mögliche genehmigte Mitbetreiber | CometBFT + `potatiumd`-ABCI++-App | eigene Chain- und App-Daten, sensibler Konsensschlüssel | Für Blockproduktion benötigt: >2/3 Stimmmacht aktiv | +| Hub | jeweiliger Potatium-Betreiber | CometBFT als Nichtvalidator + Potatium-Go-App/API + WebGUI + optional lokaler Miner | eigene kopierte Chain, App-State, Node-Key und lokale Hub-Config | Für eigene Wallet-Abfragen/Worker-Mining erreichbar | +| Worker | jeweiliger Betreiber | schlanker `potatium-miner`-Agent, dem Hub zugeordnet | Mining-Geräteschlüssel und sichere lokale Einstellungen, **keine Chain-DB/Spend-Keys** | Nur bei Teilnahme | +| Wallet | Benutzer im Browser, unabhängig von Node-Persistenz | Web-Wallet | verschlüsselte clientseitige Schlüssel, Backups in Verantwortung des Benutzers | Nein | +| Network Admin (read-only) | zunächst allein Projektbetreiber | kleines Go-Observability-Backend + Browser-Ansicht; genaue Verpackung offen | Monitoring-Inventar, optionale Historie/Cache | **Nein, ohne Admin läuft Chain weiter** | +| Registry-Admin (später) | berechtigter Betreiber | geschützte CLI / später getrennte Web-Admin-Aktionen | Admin-Autorisierungsnachweise, Audit | Nur für Verwaltungsänderungen | + +**Sicherheitsgrenzen:** `node_id`/P2P-Key, Validator-Consensus-Key, `operator_id` + Operator-Verwaltungsschlüssel, `mining_id`/Worker-Key sowie Wallet-Spend-Key sind unterschiedliche Rollen. Ein Fingerprint identifiziert einen öffentlichen Schlüssel, erteilt allein keine Berechtigung. Validatoren und Monitoring erhalten keine Wallet-Seeds. Mehrere Wallet-Adressen sind möglich, aber verändern die `operator_id`-Mining-Historie nicht. Reward-Auszahlungsadresse ist an die registrierte Betreiberidentität gebunden; Schlüssel für Wallet-Transaktionen bleiben im Browser. + +## Was läuft wo? + +### Stufe 0: Entwicklung und erster Devnet-Start + +- Bestehender Host: Proxmox, unprivilegierter Ubuntu-26.04-LXC `potatium-dev`, 4 vCPU / 4 GB RAM / 40 GB als **Startgröße**. IDE bleibt lokal via Remote-SSH. Kein Docker-in-LXC, keine Server-GUI installieren. +- Repository: `/home/potatium/src/potatium`. Daten gehören **außerhalb von Git**, z. B. `/home/potatium/.local/share/potatium/devnet/` für zukünftige Devnet-States. Pfad ist ein *Vorschlag*, noch nicht implementiert. +- Erst ein Validatorprozess + zugehörige Potatium-ABCI-App für den ersten echten Konsens-Test. Separat ein Hub/API/Wallet und **vom Beginn des Devnets an** Network Admin read-only; auf demselben LXC nur soweit Ressourcen reichen, Prozesse und Ports klar getrennt. +- Beim allerersten UI-Gerüst ohne laufende Chain: ehrliche „nicht verbunden / keine echten Daten“-Anzeige, keine Mock-Balance oder erfundenen Blöcke. +- Devnet darf nach explizitem Backup/Bestätigung zurückgesetzt werden. Ausschließlich wertlose Test-PTM, eigene Chain-ID und Genesis. + +### Stufe 1: Realistisches lokales Testnetz + +- Vier logisch getrennte Validatoren mit eigenen Datenverzeichnissen und Konsensschlüsseln. Initial dürfen sie zu Testzwecken auf einem Host/mehreren VMs laufen, **nicht als echte physische Redundanz ausgeben**. +- Hub als eigener Full-Node-Prozess (kein Validator erforderlich), Worker auf separatem Pi/Alt-PC; echtes Peer- und API-Verhalten testen. +- Network Admin zeigt alle vier Validatoren, Höhen-Abweichung, letzte Finalisierung, Peer-Verbindungen, Versionen, Block-/Tx-Rate, App-Hash-Abgleich und Ausfälle. Unbekanntes/Offline getrennt anzeigen. +- Test-Coins über Devnet-Genesis, später begrenztes Faucet, niemals als „echte PTM“ ausgeben. + +### Stufe 2: Mehrhost-Privat-Testnet + +- Vier Validatoren auf **verschiedenen Ausfall-Domänen**, idealerweise getrennten Hosts (VMs können praktikable Betriebsform sein; nicht alle auf demselben Proxmox-Host). Zunächst betreibst du sie, später optional andere vertrauenswürdige Teilnehmer. Operator-Hubs laufen auf eigenen VMs/PCs; Worker nativ auf Pi/Alt-PC. +- Read-only Network Admin kann auf separater kleiner Management-VM laufen. Für ein kleines Netz optional mit lokaler persistenter SQLite-Kennwert-Historie oder nur In-Memory; Datenbanktyp noch offen. **Kein** zusätzliches zentrales Chain-Backend. +- Netzwerkzugriff über private, authentifizierte Verbindungen; keine öffentlichen Admin-, CometBFT-RPC-, ABCI- oder Metrics-Ports. P2P-Peers nur gezielt freischalten. Monitoring remote vorzugsweise über kontrollierte Read-only APIs/privates VPN oder Push-Metriken, nie mit pauschalen Betriebssystem-Root-Zugängen. + +### Stufe 3: Dauerhaftes privates Potatium-Netz + +- Neue eigene Chain-ID/Genesis, dauerhafte State-Daten, dokumentierte Validator- und Backup-Strategie. Devnet und Testnet bleiben getrennt. **Keine automatische Migration/Einlösung der Test-PTM**. +- Der Network Admin bleibt nur Beobachter. Registry-/Validator-Änderungen erfordern gesondert definierte, signierte Autorisierung und Konsensverarbeitung; nie freie SQL-Updates gegen den Ledger. +- Native `linux/amd64` und `linux/arm64` zuerst. Docker/Compose als **optionale spätere Deployment-Form** für Hub/Worker/Admin, nicht als Pflicht und nicht innerhalb des Entwicklungs-LXC. + +## Datenspeicherung: Wo ist die Datenbank? + +1. **CometBFT-Speicher jeder Full Node:** Blöcke, Konsens-/Peer- und Indexdaten je nach Konfiguration. Unabhängig pro Instanz. +2. **Potatium-App-State jeder Full Node:** Konten/Nonces, Genesis, `operator_id`-/`mining_id`-Registry, Runden, Gebühren-Snapshots, Tickets, Mint/Burn-/spätere Gebührenverteilung, signaturgeschützte Status-Änderungen; deterministischer State-Hash. Eingebetteter KV-Speicher wird erst nach Vergleich (z. B. bbolt vs. Pebble) und ADR gewählt. +3. **Wallet im Browser:** verschlüsseltes lokales Key-Material, lokal signierte Transaktionen; keine Spend-Keys in 1, 2 oder Network Admin. Ein Wallet kann mehrere Hubs beobachten, aber privater Schlüssel ist nicht automatisch über Geräte/Browsersessions synchronisiert. +4. **Monitoring-Speicher:** optionaler Index/Cache/Time-Series für Metriken, Events, Node-Inventar, Alarmhistorie. Autorität ist immer die Blockchain und die echte Node-API, niemals der Monitoring-Cache. +5. **Worker-Speicher:** Geräteidentität, Hub-Kopplung, sichere Retry-/Diagnosezustände; keine vollständige Blockchain, keine Finanz-Wallet. + +`bbolt`, `Pebble`, `SQLite`, `Prometheus` sind **Kandidaten**, nicht ausgewählte produktive Implementierungen. Die Anwendungs-DB muss crash-/restart-sicher, atomar und zur ABCI++-Commit-Semantik passend sein. Keine Float-Mengen in Konsenszustand. + +## Netzwerkkonnektivität und Beispiele + +| Kommunikationsweg | Zweck | Sicherheitsregel | +|---|---|---| +| Validator ↔ Validator | P2P, Block-/Konsensnachrichten | gezielte authentifizierte Peers; keine offen zugänglichen RPCs | +| Hub ↔ Validator/Peer | P2P, State-Sync, Transaktionsverbreitung | Full Node verarbeitet eigene Chainkopie | +| Wallet-Browser ↔ Hub-API | Status, Wallet-Lesen, signierte Transaktionen | Browser signiert; API erhält niemals Private Key/Seed | +| Worker ↔ Hub | Challenges, Proofs, Status, Workersteuerung | signierter Worker-Key + kurzlebige Kopplung, privater Transport | +| Network Admin ↔ autorisierte Nodes | Beobachtung von Status und Metriken | read-only, explizit freigegebene Daten; Ausfall darf Konsens nicht stoppen | +| Betreiber Admin-CLI → Chain | später Registry-/Validator-Änderungen | getrennte autorisierte signierte On-chain-Aktionen | + +**Ports sind Konfigurationsdetails, nicht Identitäten.** Auf einem Host werden Prozessports und Datenverzeichnisse getrennt; Initialbetrieb bindet Management-RPCs und Cockpit an `127.0.0.1`, Zugriff per SSH-Forwarding. Echte verteilte Hosts benötigen private, authentifizierte Verbindungen und einen gezielten Firewall-Plan. Portwerte bei Implementierung mit konkreten CometBFT-Versionen dokumentieren. + +## Was passiert bei Ausfällen? + +- **Network Admin offline:** nur Monitoring weg, Chain arbeitet unverändert weiter. +- **Hub offline:** zugeordnete Worker können nicht zuverlässig melden; Wallet kann zu einem anderen vertrauenswürdigen Full-Node-Endpunkt wechseln, sofern verfügbar; keine automatischen Schlüsseltransfers. Multi-Hub-Failover für Worker bleibt offen. +- **Worker offline:** nur dessen Mining-Beitrag entfällt, keine Auswirkung auf Blockkonsens. +- **Validator offline:** bei 4 gleich gewichteten Validatoren toleriert die Chain einen Ausfall, bei zwei ist der notwendige >2/3-Konsens nicht erreichbar. +- **App-State beschädigt/abgehängt:** lokale Nodes neu synchronisieren oder verifizierte Backups/Snapshots wiederherstellen. Dafür vor echtem Einsatz Restore-Test und keine gemeinsame DB als „Single Point of Failure“. + +## Testbare Mindestkriterien + +- Devnet mit einer echten Validator-Instanz, bestätigtem AppHash und realem Blockfortschritt; Hub zeigt dieselbe Höhe, Network Admin bestätigt sie lesend. +- Leere/ungültige Chain-Verbindung in beiden Cockpits klar gekennzeichnet, niemals Mockwerte als Echtgeld/Testcoin-Bestände. +- Vier Validatoren: einer offline => weitere Finalisierung (mit genügend Stimmgewicht); zwei offline => keine Finalisierung; Neustart / App-Hash-Vergleich. +- Wallet/Hub/Worker/Admin sind getrennte Sicherheitsdomänen; Schlüssel- und Datentrennung mit Negativtests belegen. +- Admin-Dienst kann abgestellt werden, ohne Konsens oder Wallet-Signaturpfad zu verändern. +- Keine persistente zentrale Datenbankabhängigkeit für Blockchain-Fortschritt; Snapshot-/Backup-Restore reproduzierbar. + +## Offene Entscheidungen + +CometBFT-/Go-Version und Prozessintegration, konkrete Embedded-DB, Genesis/Chain-ID-Regeln, DB-Transaktions-/ABCI-Commit-Grenze, Validator-Anzahl und Stimmmacht im Dauernetz, Konsensschlüsselverwahrung, Monitoring-Stack, Datenaufbewahrung, API-Scope/Transport, öffentliche Knoten-Erreichbarkeit, Statusmetriken, Alert-Regeln, Gebührenempfänger und Coin-Emission. **Gebührenempfänger für PoP-/Worker-Gebühren ist durch ADR 0007 auf 100 % On-chain-Fonds festgelegt. Normale Transaktionsgebühren und Fonds-Auszahlungen sind noch offen.** + +## Korrigierte Betriebsentscheidung (ADR 0008): Zentral nur die Beobachtung, wenn gewünscht + +**Kein verpflichtender zentraler Registrierungs-/Bootstrap-Service `potatium-network`.** Der Projektbetreiber deployt zunächst ausschließlich den bestehenden `potatium-dev`-LXC, darauf getrennte Devnet-Prozesse (CometBFT+App, synchronisierender Hub, read-only Network Admin), sobald die Implementierung existiert. Hubs nutzen lokale Chain-ID-/Genesis-/Peer-Vertrauensanker; P2P-Discovery und unabhängige Validatoren bilden die Chain. Eine signierte On-chain-Registry steuert Mining-Rechte. Optionaler Telemetrie-/Admin-History-Store ist kein Ledger/Registrierungs-Master und braucht keinen separaten SQL-Server. Sobald tatsächliche Multi-Host-Tests starten, vier Validatoren auf getrennten Hosts/VMs (physische Ausfalldomänen beachten); ein optionaler eigener `ptm-admin`-LXC ist später reine Betreiberbequemlichkeit. + +**Freigabe/Sperre** als einzige frühe schreibende Admin-Aktionen sind gegen validierte Registry-Regeln zu signieren und on-chain zu finalisieren, nicht per SQL/GUI direkt. **100 % der PoP-Fenster-/Worker-Gebühren gehen an den On-chain-Fonds**, 0 % Burn; Fonds-Ausgabeordnung weiterhin offen. + +Ausführlicher Plan: `docs/INSTALLATIONSPLAN_HOMELAB.md`; Netzwerkzugang: `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md`; Entscheidung: ADR 0008. diff --git a/docs/INSTALLATIONSPLAN_HOMELAB.md b/docs/INSTALLATIONSPLAN_HOMELAB.md new file mode 100644 index 0000000..1390068 --- /dev/null +++ b/docs/INSTALLATIONSPLAN_HOMELAB.md @@ -0,0 +1,150 @@ +# Potatium | Infrastrukturplan für das Homelab + +**Stand:** 25.10.2026 +**Status:** Architektur- und Installationsplanung, keine Deployments nachgewiesen. +**Repository:** Ab Dokumentationsstand v0.6: aktueller Infrastrukturentwurf nach ADR 0008. Keine Deployments außerhalb des Entwicklungsgerüsts nachgewiesen; Ressourcen und Namen sind Planwerte. + +## 1. Zielbild + +Potatium besteht aus unabhängigen Validatoren und Full Nodes mit lokaler Blockchain-/Anwendungsdatenhaltung sowie optionalen Mining-Workern. Teilnehmer benötigen keine zentralen Konten, um die Blockchain zu synchronisieren. Für **Proof of Potato** ist jedoch eine **on-chain verwaltete Betreiber-/Mining-Registry** mit autorisierter Freigabe und Sperrung vorgesehen. + +```text + POTATIUM-NETZWERK (privates Testnet) + +---------------------------------------------+ + | Validator 1 Validator 2 | + | Validator 3 Validator 4 | + | CometBFT + potatiumd/ABCI++ je Validator | + | Jeweils eigene Chain-DB, App-State und Keys | + +---------------------+-----------------------+ + | P2P + +--------------------+--------------------+ + | | + Betreiber A: Hub A Betreiber B: Hub B + Full Node + WebGUI Full Node + WebGUI + lokale Wallet-Signatur im Browser lokale Wallet-Signatur im Browser + eigener Miner optional eigener Miner optional + | | | + Worker 1 Worker 2 Worker 3 + PoP-Agent PoP-Agent PoP-Agent + + Netzwerk-Admin des Projektbetreibers: + liest Chain/Registry/Fonds/Protokollparameter, beobachtet Netz-Metriken; + genehmigt/sperrt Mining-Identitäten über gesondert autorisierte, + finalisierte On-chain-Aktionen (erst wenn Registry/Signatur existieren). + Admin-Ausfall stoppt nicht die Chain. +``` + +**Festgelegt:** Der Hub besitzt eine technische Node-Identität und bietet eine Wallet-Anbindung. Der private Wallet-Schlüssel verbleibt browserseitig und wird nicht an den Hub-/Admin-Server oder Worker übertragen. Ein Betreiber kann mehrere Hubs und Worker haben, mit einer gemeinsamen registrierten Reward-Adresse. Jeder zugelassene Hub-Miner/Worker kann ein eigenes PoP-Los pro Runde qualifizieren; Gebühren und 90-Tage-Mining-Historie werden pro Betreiber aggregiert. Ein Betreiber erhält höchstens einen kostenlos gezählten Hub-Mining-Platz, sofern das spätere Gebührenprotokoll diese Forschungsentscheidung formal übernimmt. Konkrete Gebühren und Obergrenzen sind **noch offen**. + +**Gebührenentscheidung:** 100 % der PoP-Fenster-/Worker-Gebühren werden auf einen On-chain-Community-Fonds gebucht, 0 % Burn dieser Gebührenarten. Normale Transaktionsgebühren und Fonds-Ausgaberegeln sind offen. `fund_balance` wird nicht zusätzlich zum Umlauf addiert. + +## 2. Maschinen und Ressourcen + +| Name/Rolle | Deploymentort | Initiales Ressourcenbudget (Schätz-/Planwert) | Software/Dienst | Daten | +|---|---|---|---|---| +| `potatium-dev` | Bestehender Proxmox-LXC Ubuntu 26.04, unprivilegiert | **Bereits geplante Basis:** 4 vCPU / 4 GB RAM / 40 GB | Git, Go, VS Code Remote-SSH, Test-/Simulationsskripte | Quellcode unter `/home/potatium/src/potatium`; Simulationen/Testdaten getrennt | +| `ptm-devnet` | Anfangs weitere Prozesse im `potatium-dev`-LXC | Bestehende Ressourcen, bei Druck erweitern | 1× CometBFT + 1× Potatium-ABCI-App | eigene Test-Genesis, Chain-State, Konsensschlüssel | +| `ptm-hub-test` | Anfangs separater Prozess im `potatium-dev`-LXC | Kein zusätzlicher LXC erforderlich, Ressourcen messen | Full Node (Nichtvalidator), Potatium-API, Hub-WebGUI, Wallet-Frontend | eigene Chain-/App-Daten; Wallet-Key nur clientseitig | +| `ptm-admin` | Anfangs eigener Prozess im Dev-LXC; später **separater Management-LXC** | Später als Startbudget etwa 1–2 vCPU / 1–2 GB RAM / 10–20 GB, zu messen | Network-Admin-Web + lesende Datenerfassung; später autorisierte Registry-Freigaben | optional SQLite nur für Monitoring-/Audit-Historie, **kein** Ledger | +| `ptm-val01` bis `ptm-val04` | Für frühe Tests getrennte Prozesse/VMs; später verschiedene physische Ausfallbereiche | **Pro Validator als Planwert:** ca. 2 vCPU / 2–4 GB RAM / 30–60 GB, erst Benchmarks entscheiden | CometBFT + `potatiumd`-ABCI-App | jeweils eigene Chain-DB, State, Node-/Konsensschlüssel | +| `ptm-hub-*` | Teilnehmer-VM, Pi oder alter PC | Pi 4 als Mindestziel; konkrete Messwerte offen | Full Node + WebGUI + optional lokaler Miner | eigene Chainkopie, Node-ID, Konfiguration | +| `ptm-worker-*` | RPi/Alt-PC/VM | leichtgewichtig; Budget erst durch echte PoP-Benchmarks | `potatium-miner` und Hub-Verbindungsconfig | nur Geräte-ID, Mining-/Pairing-Konfiguration, **keine** Chain-DB oder Wallet-Spend-Keys | + +**Ressourcen sind nicht als gemessene Mindestanforderungen auszugeben.** Für vier Validatoren auf einem einzelnen physischen Proxmox-Host gibt es **keine physische Redundanz**. Für einen realistischen Ausfalltest Validatoren auf getrennte physische Hosts/Standorte verteilen. CometBFT kann mit vier gleichgewichteten Validatoren einen ausgefallenen Validator tolerieren; bei zwei Ausfällen ist kein >2/3-Quorum mehr vorhanden. + +## 3. Komponenten je Installationspaket + +| Paket/Rolle | Enthält | Ohne das Paket möglich? | +|---|---|---| +| Hub | CometBFT-Full-Node + Potatium-App-/API, Hub-WebGUI, Browser-Wallet, optional lokaler PoP-Miner | Worker braucht seinen Hub; Hub benötigt keinen Worker | +| Worker | PoP-Rechenagent, eigener Mining-Key, Pairing/Status | Kein vollständiger Chain-Speicher, keine Auszahlung-Wallet | +| Validator | CometBFT mit Konsensschlüssel + Potatium-ABCI-App | Netzwerk benötigt ausreichend aktive Validatoren für neue Blöcke | +| Network Admin | Chain-/Fonds-/Parameterübersicht, Registry-Detailansicht, Netzdiagnose; später signierte Freigaben/Sperren | Blockchain läuft ohne Admin weiter | + +Hub-/Worker-Pakete sind **Zielpakete**, noch keine fertig gebauten Releases. Native amd64/arm64 zuerst, optionale Docker-/Compose-Pakete später; Entwicklungs-LXC bleibt ohne Docker. Eine Validator-Node ist auch ein Node, aber nicht jede Full Node ist Validator. + +## 4. Phase für Phase installieren + +### Jetzt: Bestehenden Entwicklungs-LXC nutzen + +- [ ] Proxmox-LXC `potatium-dev` und SSH-Zugang prüfen (kein neuer Server nötig). +- [ ] Ubuntu 26.04, Go-Toolchain, Git, VS Code Remote-SSH / Codex und privates Git-Remote nach aktuellem Projektstand prüfen/einrichten. +- [ ] `/home/potatium/src/potatium` als Git-Arbeitsverzeichnis; keine Chain-Daten oder Schlüssel committen. +- [ ] Simulationspakete außerhalb des produktiven Chain-States testen. Keine echten PTM erzeugt. +- [ ] Netzplan, zugelassene Ports und Backupstrategie definieren. + +### M2: Erstes lauffähiges isoliertes Devnet + +- [ ] Eine Potatium-ABCI-App und CometBFT als einzelner Validator mit **eigenem Devnet-Genesis** starten. +- [ ] Eine getrennte Hub-Full-Node mit eigener Datenhaltung und tatsächlicher Netzwerkverbindung starten. +- [ ] Hub-WebGUI und Wallet-Basisfunktionen mit tatsächlichen Testdaten anbinden; Wallet-Key bleibt browserseitig. +- [ ] **Read-only Network Admin von Anfang an** mit echten Statusdaten von Devnet/Hub anbinden: Chain-ID, Blockhöhe, finale Blöcke, Validatorstatus, Synchronisierung, Angebot an registrierten IDs soweit verfügbar. +- [ ] Nicht implementierte Metriken ausdrücklich als `nicht verfügbar` kennzeichnen; keine Fake-Bestände oder Fake-Transaktionen. +- [ ] Von Beginn an eigene Chain-ID/Genesis, Datenverzeichnisse und Secrets; Test-PTM können zurückgesetzt werden. + +### M3: Lokales Vier-Validatoren-Netz + +- [ ] Vier CometBFT+App-Instanzen mit **vier verschiedenen Validator-Schlüsseln**, eindeutigen Ports und getrennten Datenverzeichnissen. +- [ ] Netz/State-Synchronisierung, App-Hash, 3-von-4-Quorum, Ausfälle/Neustarts testen. +- [ ] Eine Nichtvalidator-Hub-Full-Node als echter Peer. +- [ ] Network Admin überwacht vier Validatoren und zeigt verlässlich Quorum-/Synchronisationsdaten. + +### M4: Proof of Potato und Identitäten + +- [ ] Operator-/Mining-Registry als protokollvalidierter Chain-State. +- [ ] Hub/Worker-Schlüssel und sichere Kopplung; eine qualifizierte Ticketberechtigung je Mining-ID und Runde. +- [ ] **Admin-Identitätsübersicht ab Start**, autorisierte Freigabe/Sperre/Key-Rotation erst mit implementierter Registry-Signatur/Verifikation. +- [ ] Wallet-autorisierte, begrenzte On-chain-Gebührenbudgets statt Wallet-Spend-Key auf Hub/Worker. +- [ ] 100 % Gebührenziel On-chain-Fonds, nachvollziehbare Mint-/Supply-/Burn-/Fund-Bilanz. +- [ ] Ein echter Worker auf Pi/Alt-PC und mindestens ein Hub-Miner. + +### M5/M6: Mehrhost-Privat-Testnet und später permanentes Netz + +- [ ] Vier Validatoren auf physisch getrennte Ausfall-Domänen, idealerweise verschiedene Hosts. +- [ ] `ptm-admin` in eigenem geschützten Management-LXC, optional Monitoring-Historie in SQLite; keine zentrale Pflicht-DB. +- [ ] Hub-Pakete und Worker-Pakete als native Release-Binaries; Docker nur optional später. +- [ ] P2P-Bootstrap über signiertes/verifiziertes Netzwerkmanifest bzw. verwaltete Seed-/Persistent-Peer-Liste, **ohne** zentralen Web-Registrierungsdienst. +- [ ] Sicherheits-/Crash-/Restore-/Langzeittests, danach getrennte Genesis/Chain-ID für dauerhaftes Netz. + +## 5. Network Admin: Was du sehen und ändern darfst + +| Daten | Quelle | Cockpit-Recht | +|---|---|---| +| Erzeugte PTM, Burn, Umlaufmenge, Fondsbestand | Potatium-App-State/Chain, verifizierte API | Lesen | +| Aktive Protokollparameter, Gebühren und Runden | On-chain-Konfiguration/aktuelle Chain-Daten | Lesen | +| Letzte Blöcke, Finalität, Validatorquorum, Peers | CometBFT RPC/Metriken, Potatium-API | Lesen | +| Registrierte Betreiber- und Mining-IDs, Zuordnungen, Freigabe-/Widerrufstatus | On-chain-Registry | Lesen, Detailansicht | +| Antrag/Signierte Freigabe, Sperre, Schlüsselwechsel | Validierte on-chain Verwaltungsaktion | **Gezielt erlaubte Schreibaktion** nach gesonderter Authentisierung | +| Bekannte und „kürzlich aktive“ Hubs/Worker | On-chain-Registry + optional signierte Heartbeats | Lesen, **Verfügbarkeitsstatus mit Zeitstempel** | +| Guthaben fremder Wallets verändern / Coins frei erzeugen / Gebühren beliebig umstellen | Keine autorisierte Funktion | **Nie** | + +**Globale Online-Zahl ist ohne Telemetrie nicht exakt messbar.** Eine Validator-Peer-Liste zählt nur direkt sichtbare Peers. Der Admin darf unterscheiden zwischen `registriert`, `zuletzt gesehen`, `online laut Heartbeat`, `nicht gemeldet`. Freiwillige signierte Heartbeats können an einen optionalen Monitoring-Sammler gesendet werden; dessen Ausfall darf weder Mining noch Blockchain-Betrieb stoppen. + +Wichtig: Emission/Fondsstatus als On-chain-Daten sind technisch von anderen Full Nodes rekonstruierbar. Nur die Admin-Oberfläche ist exklusiv; auf Chain gespeicherte Daten sind nicht allein durch UI-Rechte vertraulich. + +## 6. Daten und Backup + +**Keine zentrale Ledger-Datenbank.** Jeder Validator und jeder Full-Node-Hub hat sein eigenes CometBFT-Datenverzeichnis und seine eigene persistente Potatium-App-State-Datenbank. Auswahl der Embedded-DB (bbolt/Pebble o. ä.) steht noch aus. Das Admin-Cockpit darf optional SQLite als Monitoring-Cache nutzen, aber keine Quelle für Kontostände oder Mining-Rechte sein. + +Strikte Trennung für `devnet`, `testnet` und permanentes Netz inklusive Keys, Chain-IDs, Genesis, Verzeichnissen und Backups. Validator-Consensus-Key und Signierzustand besonders sichern; nicht denselben Schlüssel auf zwei aktiven Validatoren gleichzeitig einsetzen. Wallet-Seeds niemals im Server, Logs, Datenbankcache oder Git speichern. Recovery und Restarts testen, bevor echte dauerhafte Netzwerke in Betrieb genommen werden. + +## 7. Netzwerk-/Portkonzept + +CometBFT-Beispieldefaults laut Upstream-Konfiguration: P2P `26656/TCP`, RPC `127.0.0.1:26657`, ABCI-App-Verbindung `127.0.0.1:26658`, Prometheus-Metriken häufig `26660`, aber standardmäßig nicht zwingend aktiviert. **Dies sind keine verbindlichen Potatium-Ports.** Mehrere Instanzen auf einem Host brauchen pro Dienst separate Listen-Adressen/Ports. + +- P2P zwischen ausdrücklich erlaubten Peers im privaten Netzwerk oder über VPN und Firewall. +- RPC, ABCI, Metrics sowie Admin-Web standardmäßig nur Loopback beziehungsweise über kontrollierte lokale Proxys/VPN-Verbindungen; niemals unnötig öffentlich. +- Hub-GUI beispielhaft `127.0.0.1:8080`, Admin-GUI `127.0.0.1:8090` (beides **Vorschläge**, nicht implementiert). +- Vom Arbeitsplatz aus SSH-Portforwarding, keine GUI-Desktop-Installation im LXC. +- Kein eigenständiger Bootstrap-Dienst nötig, solange erreichbare Seed-/Persistent-Peers und eine authentische Netzwerk-Konfiguration verfügbar sind. + +## 8. Was noch nicht feststeht + +- CometBFT-/Go-Version und konkrete ABCI++-Prozesskopplung, Embedded-KV-DB, echte Hardware-Minimums. +- Chain-IDs, feste Ports/Hostnamen, Adress-/Schlüssel-/Signaturformate, Restore-Mechanik. +- Betreiber-ID-/Mining-ID-Freigabeprotokoll inklusive Offline-/Admin-Signatur und Auditdetails. +- Gebührenhöhe für 1–10 Worker sowie Fenster 2/3; Ergebnisse der laufenden dynamischen PoP-Simulation stehen aus. +- Fonds-Auszahlungsregeln und Transaktionsgebühren. +- Telemetrie/Heartbeat-Protokoll, Datenschutz/Retention und ob SQLite benötigt wird. + +**Nächster Schritt:** Laufende Dynamik-/Fondssimulation auswerten, anschließend die Repo-Dokumentation (insbesondere `docs/NETWORK_SERVICES_UND_ONBOARDING.md`, ADR 0007, ROADMAP, INFRASTRUKTUR) konsistent auf die vereinfachte Architektur ohne verpflichtenden `potatium-network`-Registrierungsdienst bringen. Keine tatsächlichen Installationen oder Code-Funktionen als abgeschlossen markieren. diff --git a/docs/NETWORK_SERVICES_UND_ONBOARDING.md b/docs/NETWORK_SERVICES_UND_ONBOARDING.md new file mode 100644 index 0000000..fcd7f91 --- /dev/null +++ b/docs/NETWORK_SERVICES_UND_ONBOARDING.md @@ -0,0 +1,9 @@ +# Historischer Entwurf: zentrale Network Services (v0.5) + +**Nicht mehr gültig für die Architekturplanung.** Die in v0.5 vorgeschlagene Pflicht zu `potatium-network` mit zentralem Onboarding, Registrierungsportal und Off-chain-Benutzerverzeichnis wurde vom Projektbetreiber **verworfen**. Sie darf nicht implementiert oder in der Roadmap als Pflicht vorgesehen werden. + +**Aktueller Stand:** [`NETWORK_ZUGANG_UND_IDENTITAETEN.md`](NETWORK_ZUGANG_UND_IDENTITAETEN.md) und [ADR 0008](decisions/0008-schlanker-netzzugang-admin-und-identitaeten.md). + +Weiterhin entschieden: **100 % der PoP-Fenster-2-/3- und Workergebühren an den On-chain-Community-Fonds; kein Burn dieser Gebühren**. Ursprünglich in ADR 0007 festgehalten und durch ADR 0008 bestätigt. + +Zur historischen Nachvollziehbarkeit wird ADR 0007 behalten, aber die dort beschriebene verpflichtende zentrale Control Plane ist durch ADR 0008 überholt. diff --git a/docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md b/docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md new file mode 100644 index 0000000..156a118 --- /dev/null +++ b/docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md @@ -0,0 +1,27 @@ +# Netzzugang, lokale Node-Konfiguration und Mining-Identitäten + +**Aktueller Architekturentscheid:** ADR 0008 (25.10.2026). **Noch keine funktionierende Implementierung.** Das ältere `NETWORK_SERVICES_UND_ONBOARDING.md` beschreibt eine *verworfene* verpflichtende zentrale Registrierung und ist nur noch als historischer Entwurf enthalten. + +## 1. Netzwerk beitreten: Ohne zentralen Benutzeraccount + +Hub-Installation / lokale Konfiguration enthält: Chain-ID, authentischen Genesis-Hash bzw. vertrauenswürdige Genesis-Datei, Seed-/Persistent-Peer-Adressen, eigene P2P-/Node-Schlüssel, lokale Daten- und Portpfade, optional separaten Operator-Fingerprint/Mining-ID. Alle Peers/Ports und Genesis werden gegen die Vertrauensanker geprüft. Die Node verbindet sich P2P und synchronisiert selbst, unabhängig vom Network Admin. + +**Technische Rollen:** Validator (CometBFT-Konsens, eigener State), Hub (Full Node mit WebGUI und browserseitiger Wallet, optional Miner), Worker (Hub-gebundener Miner ohne Chain-State/Spend-Key), Betreiber (`operator_id`, mehrere Hub-/Worker-IDs). Eine Wallet ist keine Node-Identität; der Hub/Worker darf keinen Wallet-Spend-Key empfangen. Ein Fingerprint allein beweist keine Kontrolle über den Schlüssel. + +## 2. Mining-Zulassung: Getrennt von P2P + +Für die erste private Version legt die betreibende Person eine überprüfte Betreiber-/Mining-Identität mit zugehörigen öffentlichen Schlüsseln fest. Anträge können anfänglich manuell als signierte Datensätze/CLI-Datei übermittelt werden, keine verpflichtende Registrierungs-Website. Geschütztes Admin-Werkzeug erstellt erst nach Prüfung die autorisierte **On-chain-Freigabe**; Validatoren prüfen sie deterministisch, danach gilt sie als bestätigt. Die Admin-GUI zeigt Registrierung/Freigabe/Sperre und Audit. Ein Statusfeld in lokaler Konfiguration oder optionaler Monitoring-DB verleiht keinerlei Mining-Recht. + +**Freigaben und Sperren sind schreibende Ausnahmen:** Sie dürfen erst aktiviert werden, wenn Admin-Schlüsselformat, Rechte und Audit mit Negativtests vorliegen. Bis dahin read-only Cockpit und inaktive Schaltflächen mit verständlicher Begründung. Keine Server-Wallet-Keys. + +## 3. Netzwerküberblick und Zählbarkeit + +Die Registry kennt zugelassene Identitäten, nicht automatisch laufende Rechner. Peers einer Node sind eine Teilmenge der Netzverbindungen. Ein optionaler, geschützter Monitoring-Receiver kann nur mit Zustimmung signierte Heartbeats erfassen und Zeitreihen speichern, beispielsweise in SQLite. Anzeigen daher getrennt: **registriert / P2P gesehen / signiert zuletzt online / PoP aktiv** mit Zeitpunkt und Datenabdeckung. Keine vollständige Live-Hostzahl ohne flächendeckend verifizierte Meldungen behaupten. + +## 4. Ausfallverhalten / Sicherheit + +Validatoren und bereits synchronisierte Hubs bleiben bei Admin-Ausfall funktionsfähig. Seed-Fallback über mindestens zwei unabhängige Einträge im späteren Testnet. Keine ungeschützten externen RPC-/Admin-/ABCI-Ports; Entwicklung auf localhost plus SSH-Forward. API-/Pairing-Authentifizierung unabhängig von fremden Wallet-Spend-Keys. Keine zentrale Benutzerpasswortdatenbank. + +## 5. Entwicklungsreihenfolge + +M1: Chain-ID/Genesis-Verifikation, Betreiber-/Mining-/Admin-Keys und Registry-Aktionen spezifizieren. M2: erster Validator, Hub+Wallet und getrenntes Network-Admin-Read-only auf echter Datenquelle. M3: Multi-Peer-/Validator-Betrieb. M4: signierte Freigabe/Sperre, Worker-Pairing, PoP und Fonds; M5/M6 optional Monitoring-Historie und geschützter separater Admin-LXC. diff --git a/docs/POP_CLUSTER_SIMULATION.md b/docs/POP_CLUSTER_SIMULATION.md new file mode 100644 index 0000000..42baf74 --- /dev/null +++ b/docs/POP_CLUSTER_SIMULATION.md @@ -0,0 +1,127 @@ +# Potatium | Hub-Worker-Ökonomie: Offline-Forschung v0.1 + +**Kein Konsenscode, keine realen PTM, keine Netzwerkzugriffe.** Reproduzierbare Monte-Carlo-Tests mit der Python-Standardbibliothek. Diese Ergänzung ersetzt keine bestehenden Simulationsskripte. + +## Ziel + +Verschiedene Clustergrößen (1 eigenständiger Hub plus 0–10 zusätzliche Mining-Worker) auf eine **gemeinsame Betreiberidentität und Wallet** buchen und Kosten vs. Gewinnchance vergleichen. Jede aktive Mining-ID, inklusive Hub, kann **maximal ein Los je PoP-Runde** erarbeiten. Nur ein registrierter Hub-Platz je Betreiber ist von der Worker-Gebühr befreit. Zusätzliche Hub-Mining-IDs wären wirtschaftlich wie Worker zu zählen, ihre physische Hardware kann das Modell nicht erkennen. + +## Modellierte Regeln und Forschungsparameter + +- **Fenster 1:** Bei jeder aktivierten Mining-ID kostenlos, Bernoulli-p je Node; p = 0,55 im Profil `balanced`. +- **Fenster 2/3:** Nur nach bisherigem Qualifikationsfehlschlag derselben ID; jeweils kostenpflichtig und erfolgreich mit p=0,30 bzw. p=0,20. Bei Erfolg keine dritte Aufgabe. Die Fensterwahrscheinlichkeiten bleiben **Forschung**, keine PoP-Konsensparameter. +- **Gewinnauslosung:** Eine Bruttobelohnung je Runde, sofern mindestens ein Los akzeptiert wurde. Jedes Los einer ID ist gleichwertig. Bei mehreren Losen desselben Betreibers kann dieser mehrfach vertreten sein, aber pro Runde höchstens die *eine* Bruttobelohnung erhalten. +- **Betreiber-Wallet:** Alle Worker und der Hub teilen Guthaben, Gebührenbudget und die rollierende **90-Tage-PoP-Gewinnhistorie**. Fensterprogression und Gebührenzahlung werden pro Betreiber zusammengefasst. Wallet-Transfers sind nicht simuliert. +- **Worker-Aktivierung:** Vor der Challenge bezahlt, auch bei Misserfolg. Ist das Guthaben unzureichend, werden weniger Worker aktiviert. Ein Offline-Worker wird im Modell nicht simuliert. +- **Worker-Tarif:** `marginal` (pro Worker: 1–3 je 0,001 PTM, 4–7 je 0,003 PTM, 8–10 je 0,005 PTM) vs. `tier` (pauschal 0,001/0,003/0,005 PTM je Betreiber und Runde). Die Skala kann mit `--worker-fee-scale` geändert werden. **Kein endgültiger Tarif.** +- **Fenster-Tarif:** Fixe F2/F3-Grundgebühr (0,001 / 0,003 PTM) + progressive Abgabe nach PoP-Gewinnen der letzten 90 Tage. `original` = 100 % der v0.2-Progression, `mild` = 10 %, `low` = 1 %. Ausschließlich die Progression wird skaliert, fixe Grundkosten bleiben. +- **Zahlungsstrategie:** `free` stoppt nach F1, `always` kauft erlaubte weitere Fenster soweit finanzierbar, `rational` verwendet eine *vereinfachte* Grenzertragsschätzung mit eigener bereits erreichter Ticketzahl und einer F1-Schätzung fremder Tickets. Es ist **keine optimal spieltheoretische Strategie**. +- **Wettbewerb:** Jedes weitere registrierte Betreiberkonto ist ein eigenständiger Konkurrent; optional besitzen auch die Konkurrenten Worker. Optional kommen neue Betreiber ab einem festgelegten Simulationstag hinzu. +- **Emission:** Beispielhaft 1 PTM pro Runde mit Sieger; Gebühren werden verbrannt. Kein Sieger bedeutet keine Emission. `--rounds-per-day 720` entspricht dem früheren **unverbindlichen** Vorschlag von ca. 2 Minuten je PoP-Runde. + +## Installation im bestehenden Git-Repository + +Das ZIP enthält ausschließlich drei **neue** Pfade, die noch nicht vorhanden sein sollten: + +``` +scripts/simulate_pop_cluster.py +tests/test_pop_cluster.py +docs/POP_CLUSTER_SIMULATION.md +``` + +Vor dem Entpacken mit `unzip -l` prüfen. Mit `unzip -n` werden bestehende Dateien niemals überschrieben. Der Befehl führt selbst noch keine Blockchain-Funktionen aus. + +```bash +cd ~/src/potatium +unzip -n ~/Potatium_PoP_Cluster_Simulation_v0.1.zip -d . +python3 -m unittest discover -s tests -p 'test_pop_cluster.py' -v +``` + +## Schritt 1: Kurzer Funktionstest + +```bash +python3 scripts/simulate_pop_cluster.py \ + --operators 20 --days 7 --rounds-per-day 720 \ + --workers 0 1 3 5 10 --replicates 1 +``` + +## Schritt 2: 180 Tage, Worker-Sweep + +Vergleich von 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Workern mit gemeinsamem Betreiber-Wallet, `balanced`-Fenstern und `low`-Progression: + +```bash +python3 scripts/simulate_pop_cluster.py \ + --operators 20 --days 180 --rounds-per-day 720 \ + --workers 0 1 2 3 4 5 6 7 8 9 10 \ + --profile balanced --window-fee-model low \ + --worker-fee-model marginal --worker-fee-scale 1 \ + --target-policy rational --others-policy free \ + --replicates 3 +``` + +**Hinweis:** Ein Modell mit 19 anderen Betreibern, die nur ihren eigenen kostenlosen Hub nutzen, begünstigt den Ausbau des einen getesteten Clusters. Das ist ausdrücklich ein Extrem-Szenario, kein realistisches Marktgleichgewicht. + +## Schritt 3: Höhere Worker-Gebühren untersuchen + +Gleiche Randbedingungen wie oben, aber beispielsweise fünffache Worker-Gebühren: + +```bash +python3 scripts/simulate_pop_cluster.py \ + --operators 20 --days 180 --rounds-per-day 720 \ + --workers 0 1 2 3 4 5 6 7 8 9 10 \ + --profile balanced --window-fee-model low \ + --worker-fee-model marginal --worker-fee-scale 5 \ + --target-policy rational --others-policy free \ + --replicates 3 +``` + +## Schritt 4: Alle Betreiber besitzen Worker, Neulinge kommen hinzu + +```bash +python3 scripts/simulate_pop_cluster.py \ + --operators 20 --days 180 --rounds-per-day 720 \ + --workers 0 1 3 5 8 10 \ + --others-workers 2 --others-policy rational \ + --newcomers 5 --join-day 90 \ + --worker-fee-model marginal --worker-fee-scale 1 \ + --target-policy rational --replicates 3 +``` + +## Schritt 5: Immer alle Fenster vs. kostenbewusst + +Den gleichen Aufruf jeweils mit `--target-policy always` bzw. `rational` durchlaufen lassen. Bei `always` wird eine Fenstergebühr nur gezahlt, wenn die jeweilige Node zuvor noch kein Ticket erhalten hat und das Wallet genug Guthaben besitzt. + +## Interpretation der Kennzahlen + +- `Gewinn%`: **Betreiberchance auf den Rundengewinn**, nicht nur Qualifikation. +- `Brutto PTM`: Summe seiner erhaltenen PoP-Belohnungen. +- `Netto PTM`: Brutto minus Worker- und Fenstergebühren. Bei `initial_balance=0` identisch mit Endguthaben. +- `Ø aktive`: tatsächlich finanzierte Worker je Runde, ggf. kleiner als beantragt. +- `ΔNetto/Worker`: Unterschied des durchschnittlichen Nettoertrags zwischen zwei *getesteten* Worker-Zahlen, geteilt durch deren Abstand; bei fehlenden Zwischenwerten **nicht** der exakte marginale Wert eines einzelnen Workers. Bei wenigen Seeds statistisch verrauscht. Negativ bedeutet nur: In den simulierten Szenarien hat mehr Worker-Zahl einen geringeren Nettoertrag geliefert. +- `Gini`: Ungleichheit der Wallet-Guthaben **je registrierter Betreiberidentität**, kein Beweis für menschliche Fairness. +- `Burn`: Summe tatsächlich gezahlter Gebühren, keine Token-Pauschale oder einmaliger Gebührenabzug. + +## Ergebnisdateien + +Standardziel: `~/potatium-simulation/`. Das Skript erzeugt: + +| Datei | Inhalt | Senden? | +|---|---|---| +| `pop_cluster_....txt` | Kurzer Szenarienvergleich | **Ja** | +| `pop_cluster_..._comparison.csv` | Alle Kennzahlen je Workerzahl | **Ja** | +| `pop_cluster_..._daily.csv` | Tagesverlauf, Einnahmen und Burn | Bei Rückfragen | +| `pop_cluster_..._operators.csv` | Endzustand je Betreiber | Bei Rückfragen | +| `pop_cluster_....json` | Vollständige reproduzierbare Simulation | Für tiefe Analyse | + +Ein Hash über alle CLI-Parameter im Dateinamen vermeidet das Überschreiben verschiedener Vergleichsläufe mit ähnlichen Namen. Identischer Aufruf überschreibt seine alten Ausgabedateien. + +## Bekannte Grenzen und offene Konsensfragen + +- **Keine Sybil-Sicherheit:** Ein Betreiber mit mehreren künstlichen Betreiberkonten könnte mehrere freie Hubs und separate Gebührentöpfe bekommen. Diese Angriffsstrategie wird **nicht modelliert**. Eine manuelle Betreiber-Registry schließt Doppelanmeldungen nicht kryptografisch aus. +- Keine überwachbaren *fehlgeschlagenen* PoW-Aufwände. Jede Fensteraufgabe wird als unabhängiger Zufall mit angenommener Wahrscheinlichkeit modelliert; echte Pi-4-Zeiten fehlen. +- Keine realistische Übertragung von Gebührenmandaten aus dem Browser, keine verlorenen Schlüssel oder Netzwerkausfälle, keine Validator-Zensur, kein Peer-Latenzmodell. +- Die Bedingung, dass Zahlungsmandate/Worker-Freischaltungen vor der jeweiligen Challenge bindend feststehen, wird **logisch modelliert**, nicht kryptografisch implementiert. +- Keine Preisbindung an EUR/andere Kryptowährungen; PTM hat hier keinen externen Marktpreis und damit keinen externen Ertragswert. +- Gleichzeitige Entscheidungen der Miner, Fee-Scaling nach Anzahl Teilnehmern und strategisch variable Anzahl aktivierter Worker sind nicht optimiert. Der Zielbetreiber versucht stets, so viele der angefragten Worker zu aktivieren, wie sein Wallet finanzieren kann. +- Die Beispiele 1 PTM je Runde, 720 Runden pro Tag und bis zu zehn Worker sind **Forschungswerte, keine beschlossenen Protokollparameter**. +- Für eine echte On-chain-Implementierung ausschließlich ganzzahlige PTM-Basiseinheiten, konsenskritische Negativtests, feste Rundengrenzen, reproduzierbare Zufallsquelle und überprüfbare Signaturen verwenden. diff --git a/docs/POP_DYNAMIC_FUND_SIMULATION.md b/docs/POP_DYNAMIC_FUND_SIMULATION.md new file mode 100644 index 0000000..56461eb --- /dev/null +++ b/docs/POP_DYNAMIC_FUND_SIMULATION.md @@ -0,0 +1,57 @@ +# Potatium: Dynamische Worker und Fonds-Rückflüsse (Forschungsstand v0.1) + +**Status:** Simulationsentwurf, noch keine Protokoll- oder Coinparameter freigegeben. +**Basis:** PoP-Cluster v0.1, Worker-Gebührenforschung v0.4, Gebührenflussanalyse v0.1. + +## Ziel und Fragestellung + +1. Lohnt ein weiterer Worker bei einer Worker-Gebühr von 1x, 3x oder 5x? +2. Unterschied `fixed` (alle bezahlbaren Worker) vs. `dynamic` (je Runde auf Schätzwert basierte Wirtschaftlichkeitsentscheidung). +3. Was passiert mit Fondsbestand, Betreiber-Guthaben und Mining-Chancen bei 100 % Fondszufluss und 0 % Burn? +4. Wie wirkt **hypothetische** Rückgabe von Fondsvermögen auf Aktivierung und Vermögensverteilung? +5. Haben Betreiber mit 0 oder 1 Worker eine reale Chance gegenüber Clustern mit 3, 5, 8, 10 Workern und Neueinsteigern? + +## Abgebildete Regeln + +- Je registriertem Betreiber **ein Hub-Mining-Platz ohne Worker-Gebühr** und bis zu zehn zusätzliche bezahlte Worker-Mining-Identitäten im *Forschungsmodell*. Zweite Hubs sind nicht gratis zusätzliche Mining-Identitäten. +- Eine Mining-ID darf pro Runde höchstens ein gültiges Ticket qualifizieren. Pro Runde höchstens ein Gewinner; Preis beispielhaft 1 PTM. +- Alle drei Fenster werden je Mining-ID sequenziell versucht. F1 kostenlos, F2/F3 nur wenn vorheriger Versuch erfolglos und das gewählte Modell die Gebühr deckt. +- Vor Versuch in jedem Fenster wird die Gebühr bezahlt, *vor* dessen Bernoulli-Entscheid. Worker-Aktivierung ebenfalls vor F1. Keine bezahlte Aktivierung bei Guthabenmangel. +- Alle Mining-Belohnungen und 90-Tage-Erträge je Betreiber zusammengefasst. **Die Gebührenprogression nutzt nur Mining-Belohnungen der letzten 90 Tage, nicht Wallet-Kontostände und nicht Fondsauszahlungen.** +- Auszahlungen gehen ins gemeinsame Betreiber-Wallet. Guthaben im Code nur abstrakter Float-Forschungswert, kein privater Schlüssel. +- Worker-Gebühr pro Runde marginal: W1–3 je 0,001, W4–7 je 0,003, W8–10 je 0,005 PTM, skaliert mit `--scales` 1/3/5. Beispielwerte, keine finale Gebühr. +- `balanced`-Profil: PoP-Fenstererfolge 55 % / 30 % / 20 %. `low`-Fenstergebühren nutzen 1 % der vorher modellierten Progressionskomponente plus jeweilige Grundgebühr. +- Zeitachse 180 bzw. 360 Tage, 72 oder 720 Runden pro Tag (Forschungswerte, keine CometBFT-Blockhäufigkeit). +- Betreiberkapazität wiederholt die Liste 0,1,3,5,8,10. 20 bestehende Betreiber, 5 kommen an Tag 90 hinzu. Zugewiesene Startguthaben gelten bereits als Genesis-Bestand aller Operatoren. +- `hold`: Gebühren verbleiben im Fonds. `redistribute`: alle `--payout-interval` Tage wird `--payout-share` des **aktuellen** Fondsbestands *gleichmäßig an alle aktuell aktiven Betreiber* ausgeschüttet. Verteilung = REINES WHAT-IF, kein Governance-Beschluss. Fonds-Rückflüsse werden nicht erneut als Mining-Ertrag klassifiziert. +- Rechnerische Identität **Genesis + minted = Fonds + alle Wallets** muss an jedem Tag gelten; `burned=0`. + +## Grenzen der Simulation + +**Kein** echter Hash-/Mining-Prozess, keine Ressourcenunterschiede bei Pi/Alt-PC, kein Netzwerk oder Konsens, keine Ausfälle, keine rechts- oder ökonomisch belastbare Bewertung. Wallet-/Betreiber-Identitäten werden nicht als physische Geräte verifiziert. Kein Sybil-Schutz, keine anonymen Mehrfachbetreiber, keine Simulation unterschiedlicher 90-Tage-Historien bei externen Wallet-Transfers. Keine Kursbewertung oder Fiat-Preise. + +`dynamic` ist ausdrücklich *kein mathematisch optimaler Miner*: Die Strategie verwendet für die Workerwahl ein vereinfachtes erwartetes Anteilsschema mit F1-Ticketwahrscheinlichkeit und dem exponentiell geglätteten Rivalen-Ticketstand vergangener Runden. Die Entscheidung für Fenster 2/3 ist ebenfalls eine grobe Heuristik. Ein perfektes oder spieltheoretisches Gleichgewicht wird **nicht** behauptet. + +Das hypothetische gleichmäßige Ausschütten des Fonds würde einen starken **Sybil-Anreiz** schaffen: Mehr Betreiberidentitäten könnten mehr Auszahlungen beanspruchen. Diese Variante ist ausdrücklich kein Vorschlag für die reale Fonds-Governance. Bei `redistribute` fließt der Fonds auch zu Betreibern zurück, die selbst Worker-Gebühren gezahlt haben; Brutto-Zahlungen und Nettoeinnahmen bitte getrennt bewerten. + +Gini berücksichtigt Wallet-Guthaben registrierter Betreiber **ohne** das Gemeinschafts-Fondskonto. Gemessene Gini-Werte sind weder universelle Fairnessmaße noch direkt mit früheren 20-Miner-Szenarien vergleichbar. + +**Vorsicht bei `72` statt `720`:** 72 Runden/Tag macht Tests schneller, verändert aber die Zahl der in 90 Tagen verdienten Coins und damit die Gebührenprogression. Die Ergebnisse sind daher ein eigenständiges Szenario, kein skaliertes Äquivalent zu den früheren 720-Runden-Modellen. + +## Ausgabedateien + +- `_summary.txt`: Vergleich der Szenarien in Textform. +- `_comparison.csv`: Parameter und Durchschnittswerte pro Gebühren- und Fondsvariante; zusätzlich gruppierte Ergebnisse je Clusterkapazität (`capacity_X_net`, `capacity_X_activated`, `capacity_X_wallet` usw.). +- `_operators.csv`: Ergebnis pro Betreiber/Seed, brutto, Gebühren, Fondsrückfluss, Netto, aktive Worker, Gewinne. +- `_daily.csv`: täglicher Fonds-, Emissions-, Wallet-, Gini- und Aktivierungsverlauf. +- `_results.json`: vollständige Szenarios mit Parametern, Tageswerten, Betreiberwerten und Modellgrenzen. +- `*_ERGEBNISSE.zip`: automatisch erzeugtes Uploadarchiv mit den fünf Ergebnisdateien. + +## Verifikation + +```bash +python3 -m unittest discover -s tests -p 'test_pop_dynamic.py' -v +python3 scripts/simulate_pop_dynamic.py --operators 6 --newcomers 2 --join-day 3 --days 5 --rounds-per-day 20 --replicates 1 +``` + +Echte Chain-/Wallet-/Netzwerk-Tests sind dadurch **nicht** ersetzt. Nach den Forschungsruns erst gemeinsames Review, dann gezielte Dokumentations-/ADR-Anpassung im Repository. Unentschiedene Gebühren oder Fondsausgaben nicht als verbindlich markieren. diff --git a/docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md b/docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md new file mode 100644 index 0000000..2e9daf5 --- /dev/null +++ b/docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md @@ -0,0 +1,55 @@ +# Proof of Potato: Dynamische Worker und Fonds · Auswertung vom 25.10.2026 + +**Status: abgeschlossene Forschungsrunde, KEINE Tarife, Emissionen oder Fonds-Ausgaben beschlossen.** + +## Datengrundlage und Reproduzierbarkeit + +- Benutzerarchiv: `potatium_dynamic_20o_180d_720r_ac029505ee_ERGEBNISSE.zip` (SHA-256 `a83786c7d55394f8c1777b147ce50972c8ab59090e31895c9b9f1f8a6d014be3`). +- Enthält 36 vollständige Läufe aus **12 Konfigurationen × 3 Seeds** (20261025, 20261026, 20261027), fünf Dateien (TXT, CSV, JSON); ZIP-Integrität überprüft. +- 20 bestehende Betreiber, fünf ab Tag 90, Kapazitäten 0/1/3/5/8/10 zusätzliche Worker; jeder Betreiber ein kostenloser Hub-Mining-Platz. +- 180 Tage, 720 **hypothetische** PoP-Runden je Tag, 1 hypothetischer PTM-Gewinn je Runde, Balanced-Fensterchancen 0,55/0,30/0,20, Low-Fensterkosten, rationalisierte Fensterwahl. +- Kein Genesis-Guthaben; 129.600 PTM erzeugt, 0 PTM verbrannt, kein realer Proof-of-Work/Chain-/Wallet-Test. +- Querschnitt der zwölf Mittelwerte als [`research/po_p_dynamic_20261025_comparison.csv`](research/po_p_dynamic_20261025_comparison.csv); reproduzierbar mit `python3 scripts/simulate_pop_dynamic.py --operators 20 --newcomers 5 --join-day 90 --capacities 0 1 3 5 8 10 --days 180 --rounds-per-day 720 --scales 1 3 5 --worker-modes fixed dynamic --fund-modes hold redistribute --replicates 3`. + +## Kernzahlen: Fondsanteil des modellierten Supply nach 180 Tagen + +| Workergebühr | Dynamisch, Fonds hält | Fix, Fonds hält | Dynamisch, hypothetische Ausschüttung | Fix, hypothetische Ausschüttung | +|---|---:|---:|---:|---:| +| 1× | 23,92 % (31.001 PTM) | 23,92 % | 10,13 % (13.131 PTM) | 10,13 % | +| 3× | 40,59 % (52.604 PTM) | 65,87 % (85.363 PTM) | 16,55 % (21.443 PTM) | 29,07 % | +| 5× | 26,81 % (34.750 PTM) | 70,20 % (90.985 PTM) | 11,07 % (14.348 PTM) | 35,77 % | + +Die feste 5×-Variante bindet ungefähr 70 % der 129.600 neu erzeugten PTM, obwohl die Coins nicht verbrannt werden. Das ist ein ernstes Liquiditätsproblem, **kein Supply-Verlust**. Der Fonds gehört zur Gesamtmenge und wird nicht doppelt gezählt. + +## Worker-Aktivierung und Nettoerträge + +| Worker-Skala, Modus | Alle Betreiber: aktive Zusatzworker je Runde, Ø | Großer Betreiber (Kapazität 10): tatsächlich aktiviert, Ø | Netto-Miningertrag Kapazität 0, Ø | Netto-Miningertrag Kapazität 10, Ø | +|---|---:|---:|---:|---:| +| 1× dynamisch | 94,93 | 10,00 | 1.184 PTM | 8.121 PTM | +| 3× dynamisch | 73,09 | 5,99 | 1.402 PTM | 4.628 PTM | +| 3× fix | 92,41 | 9,99 | 1.193 PTM | 557 PTM | +| 5× dynamisch | 46,21 | 3,03 | 1.956 PTM | 5.348 PTM | +| 5× fix (Fonds hält) | 66,06 | 2,63 | 1.530 PTM | 1 PTM | + +Bei 1× unterscheiden sich feste und dynamische Strategie praktisch nicht; die Kosten sind zu niedrig, um Worker abzuschalten. Bei 3× begrenzt die dynamische Heuristik große Cluster auf etwa sechs, bei 5× auf etwa drei aktive Worker. **5× erzeugt trotz höherem Stückpreis weniger Fondseinnahmen als 3×**, weil deutlich weniger Worker bezahlen. Diese Rückkopplung ist für die Ökonomie zentral. + +**Netto-Miningertrag** (`mining_net = earned - worker_paid - window_paid`) enthält keine hypothetischen Fondsausschüttungen; `wallet` enthält sie. Daher können in der 5×-Fix-/Ausschüttungsvariante einzelne Betreiber **negative Mining-Nettoerträge** haben, obwohl ihr finales Wallet-Guthaben nicht negativ ist. Netto und Wallet nie verwechseln. + +## Fonds-Auszahlungen: reiner Belastungstest + +Das Szenario `redistribute` zahlt **alle 30 Tage 25 % des zu diesem Zeitpunkt verbleibenden Fonds** gleichmäßig an aktive Betreiber. Dies ist **keine vereinbarte oder empfohlene Governance-Regel**. Es verschiebt Bestände (z. B. 3× dynamisch: Fonds 40,59 % → 16,55 %, kumulierte Ausgaben 31.161 PTM), schafft aber keine Coins. Gleiche Zahlung je Betreiber bietet ohne robuste Betreiberprüfung einen zusätzlichen Sybil-Anreiz. Eine tatsächliche Fondsverwendung muss als separate Produktentscheidung mit Zwecken, Berechtigungen, Belegen und Missbrauchstests spezifiziert werden. + +## Neueinsteiger, Ungleichheit und Grenzen + +- Bei 3× dynamisch beträgt der modellierte tägliche Netto-Miningertrag der Neueinsteiger im Mittel 19,44 PTM, bei Altbetreibern 18,96 PTM; bei 5× dynamisch 23,50 zu 23,41 PTM. **Kein Beweis fairer Einstiegsbedingungen**, da unterschiedliche Teilnahmedauer/Kapazitäten und nur drei Seeds. +- Der Gini aus den **Operator-Wallets** sinkt bei Fonds-Rückverteilung, berücksichtigt aber nicht den gesperrten Fondsbestand oder die tatsächliche Nutzbarkeit von PTM. Ein niedrigerer Wallet-Gini ist allein kein Fairnessbeweis. +- Ein Miner kann keine physische Maschine beweisen; mehrere Betreiberidentitäten, Gerätefälschung, Validator-Vorteile, P2P-Ausfälle, Hashleistung, Stromkosten, Marktpreise und reale Wallet-Autorisierung sind nicht modelliert. +- Die `dynamic`-Strategie optimiert eine **myopische F1-only-Ertragsprognose**, die Kosten der Fenster 2/3 und zukünftige Fonds-Rückflüsse nicht vollständig in die Worker-Wahl einpreist. Kein spieltheoretisches Gleichgewicht. +- Die 90-Tage-Basis, 720 Runden/Tag, 1 PTM/Runde, Tarife, Maximalkapazität zehn und periodische Auszahlungen sind Forschungsannahmen. Keine davon als PoP-Implementierungsparameter übernehmen. +- Der Simulator verwendet Fließkommazahlen und eine einfache Monte-Carlo-Ziehung, niemals für deterministische Konsensberechnung verwenden. + +## Entscheidung und nächster Schritt + +**Bestehende Produktentscheidung bestätigt:** 100 % der PoP-Fenster- und Workergebühren gehen zunächst an den On-chain-Community-Fonds, **0 % Burn dieser Gebühren**. Die Studie entscheidet **nicht**: Workerfaktor 3×/5×, maximale Workerzahl, Fonds-Ausschüttung, Gebühr je Fenster, Coin-Emission. + +Nächster Schritt vor Festlegung der Tarife: echte Hash-/Latenzmessung auf Pi 4 und altem PC, mehr Seeds / heterogene Netzwerkgrößen, Freigabe- und Sybil-Szenarien, Fondsverwendungs-/Missbrauchsmodell. Danach eigenes ADR mit Integer-Basiseinheiten, Rundungs- und Timingregeln. diff --git a/docs/POP_GEBUEHREN_VERTEILUNG_FORSCHUNG.md b/docs/POP_GEBUEHREN_VERTEILUNG_FORSCHUNG.md new file mode 100644 index 0000000..e7f1392 --- /dev/null +++ b/docs/POP_GEBUEHREN_VERTEILUNG_FORSCHUNG.md @@ -0,0 +1,68 @@ +# PoP-Gebührenverteilung: Forschungsanalyse + +**Kein Konsens-Code. Keine neuen beschlossenen Coinparameter.** Das Skript benutzt `float` nur für die Offline-Modellrechnung. In der Blockchain sind ausschließlich deterministische ganzzahlige Basiseinheiten vorgesehen. + +## Motivation + +Die bisherige PoP-Cluster-Simulation zieht Worker- und Fenster-Gebühren vom Betreiber-Wallet ab und **verbrennt** sie. Die Gebühren begrenzen die wirtschaftliche Attraktivität zusätzlicher Mining-IDs, aber ein hoher Burn entzieht der späteren geschlossenen Sammler-/Marktplatz-Ökonomie Coins. + +Gebührenhöhe und Gebührenziel sind daher **getrennte Designentscheidungen**: + +- Gebühr: wie stark kostet zusätzlicher Worker bzw. Fenster 2/3? +- Ziel: Verbrennung, Validatoren, gesonderter Ökosystem-Fonds oder Mischung? +- Empfängeridentität: Validator und Betreiber können in der Praxis dieselbe Person sein. Dadurch verändern sich ökonomische Anreize und möglicherweise die Mining-Strategie. + +## Off-chain-Forschungsprofile + +| Name | Burn | Validatoren | Fonds | +|---|---:|---:|---:| +| `burn_100` | 100 % | 0 | 0 | +| `validators_100` | 0 | 100 % | 0 | +| `treasury_100` | 0 | 0 | 100 % | +| `validators_80_treasury_20` | 0 | 80 % | 20 % | +| `validators_70_treasury_25_burn_5` | 5 % | 70 % | 25 % | +| `worker_treasury_window_validators` | 0 | Fenster-Gebühren | Worker-Gebühren | + +Die vier Validatoren sind in diesem Skript **separate, gleichberechtigte Empfängerkonten**, keine Mining-Betreiber. Es gibt keine privaten Schlüssel und keine automatischen Auszahlungen. Das Community-Fonds-Guthaben gilt im Modell als vorerst reserviert. Der Gini-Koeffizient umfasst nur Mining-Betreiber und darf **nicht** zur Gesamtverteilung inklusive Validatoren/Fonds umgedeutet werden. + +## Installation und Ausführung + +Dieses Paket fügt `scripts/analyze_pop_fee_flows.py` und `tests/test_pop_fee_flows.py` neu hinzu und **ändert die bestehende Cluster-Simulation nicht**. + +```bash +cd ~/src/potatium +python3 -m unittest discover -s tests -p 'test_pop_fee_flows.py' -v +python3 scripts/analyze_pop_fee_flows.py \ + --input ~/potatium-simulation.zip \ + --out ~/potatium-fee-analysis +``` + +Die bisherige ZIP kann alternativ auch als Dateiverzeichnis eingelesen werden: `--input ~/potatium-simulation`. + +Eigene prozentuale Verteilung für Worker- und Fenster-Gebühren (Reihenfolge **Burn, Validatoren, Fonds**, Summe 100): + +```bash +python3 scripts/analyze_pop_fee_flows.py \ + --input ~/potatium-simulation.zip \ + --custom-worker 0,50,50 --custom-window 0,90,10 +``` + +Die Ausgabe liegt in `~/potatium-fee-analysis/potatium-fee-analysis.zip` und kann in den Chat hochgeladen werden. + +## Rechnung und Invariante + +`gesamt_vorher = Genesis + Minting`, `gesamt_nachher = gesamt_vorher - Burn`. + +`gesamt_nachher = Miner-Wallets + Validator-Konten + Treasury-Konto`. + +Alle vorgegebenen Gebühren bleiben **vollständig vom Miner bezahlt**. Deshalb bleiben bei gleicher Ausgangssimulation die Mining-Ergebnisse und das Netto-Guthaben des Miners gleich, ob eine Gebühr verbrannt oder an externe Empfänger gezahlt wird. + +**Diese Analyse rechnet Einnahmen nur buchhalterisch um**. Sie ist keine neue Simulation, wie sich Empfänger später verhalten und die erhaltenen PTM ausgeben. Bei Rückflüssen an Miner, gleichzeitig validatorenden Minern, gedeckelten Guthaben, Fonds-Ausschüttungen und Ein-/Ausstieg von Betreibern muss die eigentliche Mining-Simulation erweitert und erneut ausgeführt werden. + +## Nächste Prüfung + +- Original-Gebühren ×1 / ×3 / ×5 mit echten konkurrierenden Betreiber-Clustergrößen und mindestens zehn Seeds vergleichen. +- Auszahlung an Validatoren mit **Überlappung von Validator- und Miner-Rollen** modellieren, inklusive Rückflüssen ins Miningbudget. +- Fonds mit transparentem Mandat und Auszahlungsregeln simulieren; Nichtnutzung parkt Coins faktisch. +- Regeln zu Gebührenbelastung, progressiven 90-Tage-Erfolgen und maximal erlaubter Mining-Kapazität getrennt festlegen. +- Protokoll-ADR erst nach ökonomischer sowie Sicherheitsprüfung erstellen; derzeit nur Forschung. diff --git a/docs/PROJEKTPLAN.md b/docs/PROJEKTPLAN.md index c2c5324..ab5230d 100644 --- a/docs/PROJEKTPLAN.md +++ b/docs/PROJEKTPLAN.md @@ -30,7 +30,7 @@ Potatium (PTM, vor öffentlicher Nutzung Name/Ticker prüfen) ist eine **eigene, Eine PoP-Runde enthält **drei hintereinander liegende Aufgabenfenster** mit einer neuen, pro Identität/Chain/Runde/Fenster gebundenen, begrenzten PoW-Aufgabe. Fenster 1 ist kostenlos, Fenster 2 und 3 freiwillig und **solange kein gültiges Ticket vorliegt** gegen vorherige bestätigte Gebühren nutzbar. Die Gebühren enthalten jeweils fixe Kosten und eine progressive Komponente. Als Maßstab dienen finalisierte PoP-Gewinne der letzten ungefähr 90 Tage pro registrierter Identität/Betreibergruppe, **nicht die aktuelle Wallet-Balance**; alter Erfolg fällt aus der Bemessung heraus, kein permanent fortschreitender Langzeitvorteil. -Nach dem ersten erfolgreichen Nachweis erhält diese Identität **maximal ein Ticket** und stoppt bis zur nächsten Runde. Lose müssen vor Bekanntwerden eines später verifizierbaren Zufallswerts unwiderruflich feststehen. Die Gebühren für Zusatzfenster werden **verbrannt**; normale Transfergebühren sind zur Validator-Finanzierung vorgesehen, genaue Verteilung offen. +Nach dem ersten erfolgreichen Nachweis erhält diese Identität **maximal ein Ticket** und stoppt bis zur nächsten Runde. Lose müssen vor Bekanntwerden eines später verifizierbaren Zufallswerts unwiderruflich feststehen. Die Gebühren für Zusatzfenster und Worker werden laut ADR 0007/0008 **zu 100 % in den On-chain-Fonds gebucht und nicht verbrannt**; normale Transfergebühren und Validator-Finanzierung sind noch offen. Die **Schwelle, Nonce-Raum, Rundendauer, Fensterlängen, Gebührenhöhe, PTM-Emission, Coin-Maximum, Zufallsbeacon und Validatoranzahl** sind weiter unentschieden. Alle genannten Zahlen aus früheren Beispielen bleiben hypothetisch. Pi-4-Benchmark, Simulation und ein neues ADR sind Pflicht vor Implementierung konsenskritischer PoP-Regeln. @@ -47,7 +47,7 @@ Die **Schwelle, Nonce-Raum, Rundendauer, Fensterlängen, Gebührenhöhe, PTM-Emi 1. **M1:** Architektur-/Coin-/PoP-Parameter dokumentieren, Gebühren-/Fairness-Simulation, Pi-4-Benchmark, ADRs akzeptieren. 2. **M2:** Ledger, Accounts, deterministischer State, Signaturen, Genesis, Nonce, Tests; **parallel funktionale Web-Wallet** nach festgelegter Schnittstelle. 3. **M3:** CometBFT anbinden (1, danach 4 Validatoren), Synchronisation/Ausfalltests. -4. **M4:** PoP-Registry, Fenster, signierte Proofs/Gebühren, Ticketcommit, nachweisbare Ziehung, Mint/Burn und Negativtests. +4. **M4:** PoP-Registry, Fenster, signierte Proofs/Fondsgebühren, Ticketcommit, nachweisbare Ziehung, Mint-/Fonds-Buchungen und Negativtests. 5. **M5–M6:** Pi-/Office-PC-Builds, Web-Wallet vervollständigen, private Mehrhost-Tests, Doku/Release. Jeder Schritt: erst Akzeptanzkriterien, dann kleinste sinnvolle Implementierung, Tests inkl. `gofmt`, `go test ./...`, `go vet ./...`, kurze Dokumentation und Git-Review. Keinen Fortschritt erfinden. @@ -57,3 +57,23 @@ Jeder Schritt: erst Akzeptanzkriterien, dann kleinste sinnvolle Implementierung, Erst **nach M6** dürfen wir getrennte Erweiterungsphasen erwägen: **F1 digitale Sammlerstücke**, **F2 interner PTM-Marktplatz**, **F3 Handelsplattform für fiktive interne Assets mit PTM-Orderbuch, Kursen und Portfolio**, optional **F4 separat gekennzeichnetes Paper-Trading/Börsenlabor**. Diese Phasen sollen PTM als geschlossene interne Währung einen zusätzlichen spielerischen Nutzwert geben, nicht einen angeblichen Fiat-Kurs oder einen Mining-Vorteil erzeugen. Keine implizite Echtgeld-Einlösung, Börsennotierung oder Renditeversprechen. **Verbindliche Projektgrenze:** Kein F1–F4-Code vor funktionierender Chain, sicherer Wallet und bestandenem Mehrhost-Testnet. Eigentum/Transaktionen müssen kryptografisch überprüfbar sein, Sammlerbesitz beeinflusst die PoP-Lotterie nicht. Für Asset-Minting, sichere Marktorders, Matching, Gebühren und Rechtliches sind vor Implementierung gesonderte Entscheidungen und ADRs nötig. Details in `docs/ZUKUNFT_OEKOSYSTEM.md` und `docs/decisions/0004-zukunftsvision-internes-oekosystem.md`. + +## v0.4-Ergänzung: Installationspakete, Cluster und Betreiberidentität + +Vereinbartes Produktmodell: **Hub** = eigenständige Full-Node mit eigener GUI, Browser-Wallet und optionalem PoP-Miner. **Worker** = schlanke Mining-Instanz, Hub-abhängig und ohne separate Geld-Wallet. Multi-Hub-/Worker-Cockpit wird priorisiert; Admin-Web erst später, Admin-CLI ab Bedarf. Wallet-/Node-/Miner-/Betreiber-Identitäten bleiben getrennt. Jeder genehmigte Hub-Miner/Worker bis zu ein Ticket/Runde; alle Gewinne/Fees werden pro `operator_id` zusammengefasst und an registrierte Browser-Wallet-Adresse ausgezahlt. Ein gebührenfreier Hub-Mining-Platz pro Betreiber; weitere miningfähige Hubs dürfen nicht als kostenloser Ausweg dienen. + +**Noch zu testen:** Worker-Gebühren je Mining-Runde, Tarife, progressive Stufen vs. Pauschalstufen, maximale Worker pro Betreiber (10 nur Simulationskandidat), Fee-Reserve und Failover. Keine bestehenden PoP-Parameter oder Validatorwahl werden dadurch stillschweigend entschieden. Details: `docs/HUB_WORKER_CLUSTER.md`, `docs/WORKER_GEBUEHREN_FORSCHUNG.md`, ADR 0005. + +## Betriebsarchitektur und frühe Beobachtbarkeit (ADR 0006, 25.10.2026) + +**Wer betreibt was?** `potatium-dev` bleibt reiner Entwicklungs-/Devnet-LXC. Potatium benötigt zusätzlich aktive CometBFT-Validatoren (1 in erster lokaler Devnet-Phase, später vier für Ausfall-/Mehrhosttests), jeweils mit eigener App-DB und eigener Chain-Datenbank, nicht aber einen zentralen Blockchain-SQL-Server. Validatoren zunächst vom Projektbetreiber, später optional von anderen genehmigten Betreibern. Teilnehmer installieren eigenständig Hub-Full-Nodes und Worker; das Wallet-Keymaterial bleibt clientseitig. + +**Neue verbindliche UX-Priorität:** Web-Hub-Cockpit und gesondertes **read-only Network-Admin-Cockpit** parallel zum ersten funktionalen Devnet, mit echten Messwerten und transparentem Nichtverbunden-Zustand. Node-/Validator-Aufsicht darf keine on-chain Schreib-, Coin-, Validator- oder Wallet-Schlüssel besitzen; administrative Änderungen erst per geschützten signierten Aktionen in späterer Phase. Ohne Network Admin läuft der Konsens weiter. Monitoring-Historie/Cache kann eine kleine separate Datenbank verwenden, ist aber nicht autoritativ. + +Siehe `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md`, `docs/COCKPITS_UND_MONITORING.md`, `docs/decisions/0006-validator-infrastruktur-und-cockpits-ab-devnet.md`. **Gebührenverwendung nach ADR 0007:** 100 % der PoP-/Worker-Gebühren gehen an den On-chain-Fonds; Höhe/Tx-Gebühren/Fonds-Ausgaben sind weiterhin offen. ADR 0003 bleibt historisch dokumentiert und wurde durch ADR 0007 gezielt präzisiert. + +## Netzwerkzugang, Identity Admin und Homelab (ADR 0008) + +Vereinbarte Vereinfachung: **kein** verpflichtender `potatium-network`-Service, kein Nutzer-Registrierungsportal und keine zentrale Coin-DB. Ein Hub verbindet sich mit überprüfter Chain-ID/Genesis und direkt konfigurierten CometBFT-Peers. Mining-Rechte werden über On-chain-Registry mit protokollautorisierter Freigabe/Sperre verwaltet. Ab erstem lauffähigem Devnet: eigenständiges echtes Hub-Cockpit und separates Network Admin mit bestätigten Supply-/Burn-/Fonds-/Fees-/Identitäts-/Validatorwerten. **Frühe schreibende Admin-Funktionen ausschließlich signierte Freigabe/Sperre**, erst sobald die Register- und Signaturprüfungen implementiert sind. Monitoring-SQL optional, keine Pflicht-Control-Plane. Fonds: 100 % PoP-/Workergebühren, kein Burn. Tarife und Fonds-Ausgaben offen. + +Auswertung: `docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md`, die Modelle sind keine Konsensparameter. Infrastrukturplan: `docs/INSTALLATIONSPLAN_HOMELAB.md`, ADR 0008. diff --git a/docs/PROTOKOLL_ENTWURF.md b/docs/PROTOKOLL_ENTWURF.md index f95a2cd..9ff3c8e 100644 --- a/docs/PROTOKOLL_ENTWURF.md +++ b/docs/PROTOKOLL_ENTWURF.md @@ -11,7 +11,7 @@ 5. Die Gebührenbasis ist die **nachweisbare, einer registrierten Mining-Identität/Betreiberzuordnung zugerechnete PoP-Gewinnsumme der zurückliegenden ca. 90 Tage**, **nicht** das gerade sichtbare Wallet-Guthaben. Genaues Zeit-/Höhenfenster und Verbundidentitäten noch spezifizieren. Langjährige Aktivität an sich darf keinen permanenten Bonus/Malus auslösen. 6. Progression: Fenster 3 soll teurer sein und zugleich **deutlich geringeren zusätzlichen Nutzen** als Fenster 2 bieten. **Keine unlimitierten Zusatzchancen, kein Kauf einer weiteren Identität.** Konkrete Tarife und Erfolgswahrscheinlichkeiten erst nach Simulation entscheiden. 7. Ein qualifizierter Miner erhält **maximal ein Ticket** je Runde. Nur vollständig gültige Proofs aus zulässigen Fenstern zählen. Nach dem ersten gültigen Proof endet die Teilnahme dieser Identität für diese Runde. -8. Zusatzfenster-Gebühren werden **verbrannt** (aus dem spendierbaren Umlauf entfernt). Normale Transaktionsgebühren sollen die Validatoren finanzieren; Zuteilungsmechanik ist noch offen. +8. **ADR 0007 gilt:** 100 % der Zusatzfenster- und Worker-Gebühren werden dem On-chain-Community-Fonds gutgeschrieben, 0 % Burn dieser Gebühren. Normale Transaktionsgebühren und Fondsauszahlungen bleiben offen. 9. Ohne PTM kann jede zugelassene Identität an Fenster 1 teilnehmen. Gebühren und Mining-Erfolg dürfen keine Gewinnergarantie erzeugen. 10. Registrierte Identität ≠ nachweislich einzigartige physische Maschine oder Person. Für das kleine private Netzwerk zunächst manuelle Freigabe, Audit und Sperrmöglichkeit. Mehrere Geräte/Identitäten und gemeinsamer Betreiber müssen gesondert geregelt werden. @@ -22,10 +22,10 @@ Runde R beginnt; Identitäten / Regeln / Gebührenbasis werden eingefroren Fenster 1: kostenlose, identitätsgebundene PoW-Challenge | gültiger Proof? -> 1 Ticket; fertig | andernfalls - Fenster 2: vorher autorisierte, bestätigte Gebühr verbrennen + Fenster 2: vorher autorisierte, bestätigte Gebühr in den On-chain-Community-Fonds buchen (ADR 0007) | neue, zuvor unbekannte PoW-Challenge; Proof? -> 1 Ticket; fertig | andernfalls - Fenster 3: höhere Gebühr vorab verbrennen (Fenster 2 muss zuvor freigeschaltet sein) + Fenster 3: höhere Gebühr vorab in den On-chain-Community-Fonds buchen (Fenster 2 muss zuvor freigeschaltet sein) | neue PoW-Challenge; Proof? -> 1 Ticket; fertig | andernfalls kein Ticket Fristende: akzeptierte Tickets endgültig fixieren @@ -71,3 +71,15 @@ Runde R beginnt; Identitäten / Regeln / Gebührenbasis werden eingefroren | P-10 | Registry-Administration, Zweitgeräte-Regel, Validator- und normale Gebührenverteilung | **Wichtig:** Zahlen aus älteren Diskussionen wie 70/91/97,3 %, 1 PTM Belohnung, 10 Mio. PTM, 10-s-Blockzeit oder 0,001-PTM-Grundkosten bleiben ausschließlich **Rechenbeispiele**. Sie sind nicht implementiert, beschlossen oder für öffentliche Nutzung freigegeben. + +## Ergänzung v0.4: Betreibergruppe, Mining-IDs und Cluster + +Die zulassungsfähige Mining-Einheit ist eine `mining_id` mit zugehöriger `operator_id`, Signaturschlüssel und Rollenmerkmal (`hub_miner`, `worker`). **Jede genehmigte Mining-ID erhält maximal ein Ticket je Gesamtrunde**, unabhängig vom Gerätetyp oder Hub-Prozess. `operator_id` aggregiert sämtliche PoP-Belohnungen für die progressive ca. 90-Tage-Fee-Basis; eine registrierte Auszahlungsadresse empfängt alle Rewards. Wallet-/Hub-Wechsel erzeugt keine neue Betreiberhistorie. Pro Betreiber nur **ein** kostenloser Hub-Mining-Platz; weitere miningfähige Hubs zählen bei Worker-Kosten wie Workerslots. Registry-Aufnahme und Revocation bleiben manuell auditierbar. + +**Zusätzliche Worker-Aktivierungsgebühren** werden als Konzept weiter untersucht (nicht final). Falls eingeführt, ist eine Teilnahmefreigabe **je PoP-Gesamtrunde** im finalisierten Zustand vor Veröffentlichung der neuen Challenge zu bezahlen/reservieren; sonst könnte die Node zuerst rechnen und nur bei Treffer zahlen. Fenster-2/3-Gebühren sind davon separat und ebenfalls vor Bekanntgabe der neuen Fenster-Challenge fällig. Die Bedienoberfläche muss beide Kostenarten sichtbar trennen. Festzulegen: exakte Gebührenformel in Integer-Basiseinheiten, Snapshot, fee allowance/reserve, Kosten bei Hub-Ausfall, Verbindlichkeit des Aktivierungszeitpunkts, eindeutige Workerzahl und Höchstzahl. **Keinerlei vorgeschlagene Zahlen als endgültige Konsensregeln implementieren.** Siehe ADR 0005. + +## ADR 0007: On-chain-Fonds statt PoP-Burn und zentrale Aufnahme + +PoP-Fenster-2-/3-Gebühren sowie zulassungs-/rundenbezogene Worker-Aktivierungsgebühren fließen **zu 100 % in den On-chain-Fonds** und werden **nicht verbrannt**. Fonds ist Teil des umlaufenden Supply, nicht zusätzliche Emission; deterministische `fund_balance`-/`fund_collected_total`-/`fund_spent_total`-Buchungen und eine eindeutig definierte Gebührenreservierung/Refund-Regel müssen vor Implementierung festgelegt werden. Normale Transaktionsgebühren und Fonds-Ausgaberechte bleiben offen. Frühere Burn-Angaben in ADR 0003 werden für diese Kategorien durch ADR 0007 ersetzt; Burn-Zähler bleibt für echte eventuelle Burns erhalten. + +**ADR 0008 gilt:** Keine verpflichtende zentrale Registrierungsplattform. Hubs synchronisieren per CometBFT-P2P anhand konfigurierter/verifizierter Genesis-/Chain-/Peer-Daten. PoP-Zulassung nur nach autorisierter On-chain-Freigabe, Widerruf signiert/auditiert. Siehe `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md` und ADR 0008. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 8207001..943acfd 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -10,7 +10,7 @@ ## M1: Konsens- und Coin-Regeln **zuerst** -- [x] Produktprinzip für **3 Mining-Fenster**, kostenloses Fenster 1, optionale fixe + progressive Zusatzgebühren für Fenster 2/3, 90-Tage-Mininggewinn als Basis, Burn, max. 1 Ticket dokumentiert (`ADR 0003`). +- [x] Produktprinzip für **3 Mining-Fenster**, kostenloses Fenster 1, optionale fixe + progressive Zusatzgebühren für Fenster 2/3, 90-Tage-Mininggewinn als Basis und max. 1 Ticket dokumentiert; **Gebührenverwendung durch ADR 0007 auf 100 % Fonds statt Burn geändert** (`ADR 0003`). - [ ] Tokenomics mit Coinsupply, Genesis, Belohnung, Basis-Einheiten, Gebührensystem, Signatur-/Adressschema entscheiden. - [ ] PoP-Runden/Fenster/Schwierigkeit/Nonce-Räume und Zahlungs-/Finalitätsbedingungen spezifizieren. - [ ] Rein analytische, ausdrücklich nicht konsenskritische Simulation (5/10/20/50 Miner), Fairness- und Kostenvergleich. @@ -36,7 +36,7 @@ ## M5–M6: Bedienung und privates Mehrhost-Testnet - [ ] UX, Explorer/Status, Dokumentation, Linux amd64/arm64-Releases, Sicherung/Recovery. -- [ ] Hosts über mehrere Geräte, Restart/Fault-Tests, Monitoring und Warnungen zur verbleibenden zentralen Registrierung. +- [ ] Hosts über mehrere Geräte, Restart/Fault-Tests, Monitoring und Ausfallprüfungen des optionalen Admin-Dienstes. ## F1–F4: Zukunftsvision, erst nach stabilem M6 @@ -50,3 +50,38 @@ Die folgenden Erweiterungen sind **als spätere Produktideen aufgenommen**, aber **Abhängigkeit:** erst funktionsfähige Chain, sichere Web-Wallet, PoP, Mehrhost-Testnet und verifizierte Transaktionssignaturen. Vor jedem F-Schritt: neues ADR, Datenmodell, Sicherheits-/Risikobewertung und eigene Akzeptanztests. Kein Börsenlisting, keine Wert-/Gewinngarantie. **Keine Implementierungs- oder Testergebnisse behaupten, die nicht tatsächlich ausgeführt wurden.** + +## Ergänzende Arbeitspakete: Hub/Worker und Cluster (ADR 0005) + +- [ ] **M1:** `operator_id`, Hub-/Worker-/Mining-Identitäten, Zweckreserve, Gebühren-Snapshot, 1 freier Hub-Mining-Platz/Operator und Mehridentitätsrisiken präzise spezifizieren; Worker-Preise/Obergrenze simulieren; Pi-4-Benchmark. +- [ ] **M2:** Vollständiger eigenständiger Hub inklusive **echter browserseitiger Wallet** und lokalem Status-Cockpit (keine serverseitigen Wallet-Keys), Devnet/Test-PTM. +- [ ] **M3:** Full-/Validator-Node-Status und sichere API, 1 dann 4 Validatoren. Worker sind keine Validatoren. +- [ ] **M4:** Hub-eigener PoP-Miner; Hub-gebundene Worker mit eindeutiger Registrierung, widerrufbarer Pairing-Autorisierung, Pre-Paid-Gebührenfreigabe, fristgerechten Proofs und 1 Ticket/ID/Runde. +- [ ] **M5:** Multi-Node-/Cluster-Ansicht für eigene Hubs/Worker, native amd64/arm64-Releases; optional später Docker, nicht im Entwicklungs-LXC. +- [ ] **M6:** Betreiber-/Worker-Recovery, Ausfall/Failover, Mehrhost, Registry-Audit und Admin-CLI/Web nach tatsächlichem Bedarf. Kein öffentlicher Adminzugang. + +**Status:** Planung, nichts davon als implementiert markieren. Phase F1–F4 bleibt unverändert nach stabilem Testnet. + +## Prioritätsanpassung v0.5: Infrastruktur und echte Cockpits ab Devnet (ADR 0006) + +**Diese Aufgaben konkretisieren und priorisieren die Phasen M0–M6; sie ändern keine Emissions-, Mining- oder Gebührenwerte.** + +- [ ] **M0/M1:** Systemrollen und klare Datenverantwortung dokumentieren, Protokoll von zentraler Admin/DB-Abhängigkeit freihalten. Read-only Network Admin und Teilnehmer-Hub-GUI als feste frühzeitige Lieferbestandteile festhalten. Bei Gerüstbetrieb ausschließlich „nicht verbunden“, keine simulierte Live-Chain. +- [ ] **M2, erstes lauffähiges Devnet:** Ein realer Validator/Go-App-Kern, eigenständiger synchronisierender Hub, Web-Wallet sobald sichere Transaktionsschnittstelle definiert, **lokales Hub-Status-Cockpit und separater read-only Network Admin** für Chain-ID, Blockhöhe, letzte Bestätigung, Sync/Fehleranzeige. Noch nicht implementiert. +- [ ] **M3:** Vier getrennte Validatoren und Netzwerkstatus in beiden Cockpits, AppHash-/Quorum-Überwachung, Restart-/Mehrprozess-Tests. Einheitliche echte Datenquellen und Stale/Offline-Erkennung. +- [ ] **M4:** Operator-/Mining-Registry, PoP, Gebührenreserve/Worker, Hub-Mining, sichere Pairing- und Fee-Freigabe, Monitoring aller echten Runden und Worker. Schreibende Admin-CLI nur mit Autorisierung. +- [ ] **M5/M6:** Multi-Hub-Clusteransicht und Mehrhost-Monitoring/Alerting vervollständigen, optionale Admin-Web-Schreibfunktionen *erst danach* nach Rechteprüfung. Native VM/Pi zuerst, Docker optional später. Sicherheits-, Backup-/Recovery-Tests. + +Spezifikation: `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md`, `docs/COCKPITS_UND_MONITORING.md`, ADR 0006. In der ursprünglichen M5-Phase ist der **Ausbau** von Status/Explorer gemeint, nicht erst der Start der Cockpits. + +## Prioritätsanpassung v0.6: Vereinfachter Netzzugang, Identity Admin und Forschung (ADR 0008) + +- [x] **M1 Forschung:** 36 Szenarien (1×/3×/5× Workergebühren, fix/dynamisch, Fonds halten/hypothetisch rückverteilen) in `docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md` dokumentiert. **Kein** Ergebnis als verbindliche Coin-/Gebührenregel kodiert. +- [ ] **M1:** On-chain-Identitäts-/Zulassungs- und Widerrufsmodell, Operator-/Node-/Mining-/Wallet-Schlüsseltrennung, Gebührenreserve und Fonds-Übertragungen in Integer-Basiseinheiten definieren. Bootstrap über überprüfbare lokale Chain-ID/Genesis/Peer-Liste, keine zentrale Benutzer-Registrierung. +- [ ] **M2:** Ein Validator mit Go-App, eigenständig synchronisierender Hub, funktionsfähige Web-Wallet und **read-only Network Admin ab dem ersten lauffähigen Devnet** mit echten Chain-/Validatorwerten und ehrlichem `nicht verfügbar` für künftige Statistiken. Alle Dienste initial gemeinsam auf `potatium-dev` in getrennten Prozessen möglich. +- [ ] **M3:** Vier separat konfigurierte Validatoren, AppHash-Tests, physische Hosttrennung später; Admin zeigt Peers/Quorum/Status. +- [ ] **M4:** On-chain-Operator-/Mining-Registry, manuell genehmigte signierte **Freigabe-/Sperraktionen im Admin** (getrennte Schlüssel/Signatur), Worker-Pairing, PoP und deterministische Fondsbuchung. Kein Backdoor-Guthaben, keine zentrale SQL-Freigabe. +- [ ] **M5/M6:** Native ARM64/AMD64-Distribution, Multi-Hub-Cluster, reale Pi-/Alt-PC-Mining-Last, optional geschützter `ptm-admin`-LXC und Heartbeat-Historie. Bootstrap-Fallback und Admin-Ausfall ohne Konsensabbruch testen. +- [ ] **M1 vor Tariffestlegung:** Pi-4-Hashmessungen, mindestens 10 Seeds und heterogene Betreiber-/Sybil-Szenarien, Definition zulässiger Fonds-Ausgaben. Erst danach neues Gebühren-/Emission-ADR. + +Die frühere Pflicht-`potatium-network`-Registrierung nach ADR 0007 ist **durch ADR 0008 aufgehoben**. 100 % PoP-Fenster-/Workergebühren für den On-chain-Fonds bleiben beschlossen, 0 % Burn dieser Gebühren. Alle WebGUI-/Ledger-/Admin-Funktionen sind weiterhin **nicht implementiert**. diff --git a/docs/WEB_WALLET.md b/docs/WEB_WALLET.md index af7ec5b..69463f5 100644 --- a/docs/WEB_WALLET.md +++ b/docs/WEB_WALLET.md @@ -13,7 +13,7 @@ ## Zusatzfenster und Gebühren (PoP v0.2) - Miner startet immer ohne PTM mit **kostenlosem Fenster 1**. -- Die Web-Wallet kann (nach Festlegung des Protokolls) optional erlauben, für **Fenster 2/3** eine eng begrenzte Gebühr zu bezahlen. Fixpreis und progressive Gebühr anhand der aus dem finalisierten Chain-State berechneten **Mining-Gewinne der letzten ca. 90 Tage** der zugeordneten Identität/Gruppe anzeigen. Die Zahlung wird **verbrannt**. +- Die Web-Wallet kann (nach Festlegung des Protokolls) optional erlauben, für **Fenster 2/3** eine eng begrenzte Gebühr zu bezahlen. Fixpreis und progressive Gebühr anhand der aus dem finalisierten Chain-State berechneten **Mining-Gewinne der letzten ca. 90 Tage** der zugeordneten Identität/Gruppe anzeigen. Die PoP-Fenstergebühr wird dem **On-chain-Fonds** zugeführt, nicht verbrannt (ADR 0007/0008). - Nutzer sieht vor Autorisierung den **konkreten Preis je Zusatzfenster**, die maximale mögliche Gesamtausgabe, Gebührenrisiken und den Unterschied zwischen Qualifikations- und tatsächlicher Gewinnchance. - Gebühr nur nach **explizit signierter** Freigabe für Chain-ID, Miner-ID, Runde, Fenster, Betrag, Ablauf und Nonce. Ggf. später vorsichtig modellierbares widerrufbares Budget statt unbeschränkter Dauerfreigabe. Miner benötigt keine Wallet-Secrets. - Vor finaler Gebühren-/Signaturspezifikation alle Zahlungs-Aktionen deaktiviert lassen. Keine automatische Abbuchung nur wegen „Mining aktiviert“. @@ -22,3 +22,17 @@ ## Sicherheitstests Replay über Runden, falsche Chain-ID, abgelaufene Autorisierung, negative/überlaufende Beträge, divergierende Fee-Snapshots, manipulierte Fees/Proofs, Backup-/Recovery-Fehler und Offline-Stati. Browser-/Go-Kompatibilität der gewählten Signatursuite erst nach ADR garantieren. + +## Erweiterung: Operator-Wallet und Hub-/Worker-Mining (ADR 0005) + +Die Registrierung eines Hub ist **keine automatisch auf dem Server erzeugte Wallet**. Die Wallet entsteht/importiert und signiert weiterhin **nur im Browser**; Hub/Worker erhalten keine Wallet-Seeds/-Spend-Keys. Alle legitimen PoP-Belohnungen des Hub und seiner Worker werden an eine vom Betreiber autorisierte Auszahlungsadresse gebucht. Normale Benutzer können zusätzliche Wallets führen; damit ändern sich weder Operator-Registry noch progressive 90-Tage-Gewinnhistorie. + +Die Wallet muss den **separaten** Betrag für Worker-Aktivierung vor Runde und für Zusatzfenster 2/3 vor neuer Challenge anzeigen. Der Nutzer benötigt eine begrenzte vorab bewilligte Gebührenreserve oder ein widerrufbares Zweckmandat, damit Pi-Worker unbeaufsichtigt minen können, ohne die Wallet privat an Backend/Worker auszuhändigen. Gebühren-/Fonds-Historie (und echte separate Burns, falls vorhanden), Reserve-Restbetrag, Rückzahlung, Obergrenzen und Alarmmeldungen gehören ins Hub-Cockpit. Konkretes Signatur-/Reserveformat bleibt offen. Keine selbsttätigen Abbuchungen ohne klare, widerrufbare Autorisierung. + +## Wallet, Hub und Network Admin (ADR 0006) + +Ein Hub hat eine **eingebundene Browser-Wallet**, aber keine Pflicht, für jede Full-Node-Instanz eine neue Geld-Wallet zu erzeugen. Eine Betreiber-Auszahlungsadresse kann für mehrere Hub-Miner und Worker gelten; mehrere Browser/Hubs übertragen private Schlüssel nicht automatisch. Ein Network Admin ist **nur beobachtend**, hat keine privaten Wallet-Schlüssel und keine Ausgaberechte. Wallet-Kennzahlen im Hub nur aus echter Chain-Antwort und nach clientseitiger Autorisierung anzeigen; „nicht verbunden“ statt ausgedachtem Guthaben. Siehe `docs/COCKPITS_UND_MONITORING.md`. + +## Wallet-Anmeldung ist nicht Mining-Zulassung (ADR 0008) + +Wallet-Erstellung/Import funktioniert lokal ohne zentrale Benutzerregistrierung; Wallet-Spend-Keys verbleiben verschlüsselt auf Clientseite. Hub und Worker besitzen eigene technische Identitäten; signierte On-chain-Betreiber-/Mining-Freigabe ist eine eigenständige Berechtigung und wird vom geschützten Network Admin autorisiert. Ein registrierter Betreiber kann mehrere Hubs/Worker einer Auszahlungsadresse zuordnen. Hubs synchronisieren per verifiziertem P2P-Bootstrap ohne zentralen Anmelde-Server. Details: `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md` und ADR 0008. diff --git a/docs/decisions/0006-validator-infrastruktur-und-cockpits-ab-devnet.md b/docs/decisions/0006-validator-infrastruktur-und-cockpits-ab-devnet.md new file mode 100644 index 0000000..760bf15 --- /dev/null +++ b/docs/decisions/0006-validator-infrastruktur-und-cockpits-ab-devnet.md @@ -0,0 +1,42 @@ +# ADR 0006: Validator-Infrastruktur, getrennte Datenspeicherung und Cockpits ab Devnet + +> **Entscheidungsverlauf, Stand v0.6:** Die verpflichtende zentrale `potatium-network`-Control-Plane und ein Registrierungsportal sind durch [ADR 0008](0008-schlanker-netzzugang-admin-und-identitaeten.md) **aufgehoben**. Unverändert gelten das frühe Hub-/Network-Admin-Monitoring, die On-chain-Mining-Registry und 100 % PoP-/Worker-Gebühren für den Fonds. Historische Abschnitte unten dokumentieren den früheren Stand. + +**Datum:** 2026-10-25 +**Status:** Produkt-/Betriebsarchitektur akzeptiert; technische Komponenten und Ports offen + +## Kontext + +Der Projektbetreiber möchte nachvollziehen, welche Komponenten er neben Entwickler-LXC, Teilnehmer-Hubs und Workern selbst deployen muss. Gleichzeitig sollen Betreiber und Teilnehmer **vom ersten lauffähigen Devnet an** echte Statusinformationen erhalten. ADR 0005 plante Admin-Web zunächst später; das gilt für **schreibende Verwaltung**, nicht mehr für read-only Beobachtung. + +## Entscheidung + +1. Keine zentrale Blockchain- oder Coin-Datenbank. Potatium benötigt aktive **CometBFT-Validatoren**, jeweils mit eigener Potatium-ABCI++-Anwendung und eigenem lokalen Chain-/App-State. Der Network Admin ist ausdrücklich **nicht** für Konsens/Mining erforderlich. +2. Erst ein Validator für das Devnet, danach vier mit anfangs fester, autorisierter Validatorgruppe für lokale/multihost Testnet-Tests. Initial betreibt der Projektbetreiber diese Infrastruktur. Die spätere Validatorrolle anderer Teilnehmer ist offen; Mining-Worker werden nicht automatisch Validatoren. +3. **Potatium Hub** bleibt eine autonome Full Node inklusive Browser-Wallet, lokaler Node-GUI und optionalem Mining; Worker bleiben schlanke, gekoppelte Mining-Agenten ohne eigene Full-Chain-/Wallet-Schlüssel. Mehrere eigene Hubs können in der Teilnehmer-Clusteransicht zusammengefasst werden. +4. **Ab dem ersten lauffähigen Devnet:** Hub-Cockpit und gesonderter **read-only Network Admin** zeigen echte Werte wie Chain-ID, Blockhöhe, Validator-/Peerstatus, Fehlerzustände. Beim initialen UI-Gerüst ohne Chain ausschließlich ehrliche „nicht verbunden“-Werte. Beide werden parallel zur Kernimplementierung iterativ erweitert, nicht auf M5/M6 verschoben. +5. Network Admin als kleiner separater Monitoring-Dienst, im Dev-LXC zum Anfang, später optional eigene Management-VM. Optionaler Monitoring-Speicher/SQLite/Zeitreihen ist nur Beobachtung, keine Konsensabhängigkeit. Kein allgemeiner zentraler Hostagent oder öffentliches RPC als Voraussetzung. +6. Netzwerkweite schreibende Betreiberaktionen bleiben späterer separater Admin-CLI mit gültig signierten On-chain-Autorisierungen vorbehalten. Read-only Admin erhält keine Spend-/Validator-/Operator-Admin-Schlüssel und darf weder Guthaben verändern noch Mining-Gewinner beeinflussen. +7. Native Deployments `linux/amd64`, `linux/arm64` zuerst, Docker später optional. Dev-LXC ohne Docker, APIs zunächst loopback/SSH-Forward; Multi-Host-Verkehr nur mit dediziertem Sicherheits- und Firewall-Konzept. + +## Begründung + +Klare Trennung von Konsens, Nutzerbetrieb, Worker-Steuerung und Netzwerkbeobachtung; frühe Fehlersichtbarkeit; keine zentralisierte Coins-Datenbank oder Single Point of Failure; keine versteckte Wallet-Schlüsselübergabe. Die im Hub eingebettete Wallet gehört einem Betreiber, nicht technisch jeder Node. Mehrere Hubs/Worker können dieselbe registrierte Auszahlungsadresse verwenden. + +## Alternativen + +- Zentraler Masterserver samt SQL-Ledger: abgelehnt, widerspricht dezentrales L1-Konzept. +- Admin-Cockpit als Voraussetzung für Blockproduktion: abgelehnt, wäre zentrale Verfügbarkeitsabhängigkeit. +- Admin-Web vollständig erst nach M6: für **Statuslesen** abgelehnt, für sensible Schreibverwaltung weiter zurückgestellt. +- Jeder Worker als Validator/full node: abgelehnt, Ressourcenaufwand und Rollenvermischung. +- Jeder Hub zwangsweise Validator: abgelehnt, Full Nodes ohne Abstimmungsrecht sind zulässig. + +## Konsequenzen und offene technische Entscheidungen + +M1/M2 erhalten neue Akzeptanzkriterien für read-only Node-/Netzwerkstatus. M3 muss Validatorausfall und gleiche App-Hashes verifizieren. Die Monitoring-API und Datenbank, CometBFT-Version, Validator-Key-Verwahrung, Portkonfiguration, Metrikenaufbewahrung und Überwachungsberechtigungen werden vor Implementierung geprüft. Gebührenverwendung (Burn/Validator/Fonds) und Emissionsformeln werden **nicht** durch dieses ADR entschieden; bisherige Burn-Entscheidungen sind durch neuere Simulationen erneut in Diskussion. + +**Referenzen:** `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md`, `docs/COCKPITS_UND_MONITORING.md`, ADR 0002/0005 und aktuelle offizielle CometBFT-Dokumentation. + +## Spätere Präzisierung + +**ADR 0007** konkretisiert: Ein zentraler `potatium-network`-Dienst **ist** für bequemen Bootstrap, Anträge und weltweites/privates Netzwerkinventar vorgesehen. Dessen off-chain Managementdatenbank widerspricht **nicht** dem in ADR 0006 abgelehnten zentralen **Coin-/Blockchain-Master**. Hub und Validatoren synchronisieren unabhängig direkt P2P. Die Gebührenverwendung für PoP-/Worker-Gebühren ist seit ADR 0007 auf **100 % On-chain-Fonds, 0 % Burn** festgelegt. diff --git a/docs/decisions/0007-network-services-onboarding-und-gebuehrenfonds.md b/docs/decisions/0007-network-services-onboarding-und-gebuehrenfonds.md new file mode 100644 index 0000000..6046285 --- /dev/null +++ b/docs/decisions/0007-network-services-onboarding-und-gebuehrenfonds.md @@ -0,0 +1,41 @@ +# ADR 0007: Zentrale Network Services als Control Plane und 100 % PoP-Gebühren an On-chain-Fonds + +> **Entscheidungsverlauf, Stand v0.6:** Die verpflichtende zentrale `potatium-network`-Control-Plane und ein Registrierungsportal sind durch [ADR 0008](0008-schlanker-netzzugang-admin-und-identitaeten.md) **aufgehoben**. Unverändert gelten das frühe Hub-/Network-Admin-Monitoring, die On-chain-Mining-Registry und 100 % PoP-/Worker-Gebühren für den Fonds. Historische Abschnitte unten dokumentieren den früheren Stand. + +**Datum:** 2026-10-25 +**Status:** Akzeptiert als Produkt-/Betriebsprinzip, **nicht implementiert**. Details zu Login, Registry-Transaktionen, Signaturformat, Fonds-Auszahlungen, Tarifen offen. +**Präzisiert:** ADR 0005/0006; **ersetzt die Burn-Verwendung für PoP-/Worker-Gebühren aus ADR 0003**, nicht dessen drei Mining-Fenster oder Ticketregeln. + +## Kontext + +Im privaten Potatium-Netz ist die dezentrale P2P- und Konsensinfrastruktur nicht automatisch eine bequeme Zugangsverwaltung. Neue Betreiber benötigen authentische Bootstrap-Informationen, einen Registrierungsantrag und eine nachvollziehbare manuelle Genehmigung. Der Netzwerkbetreiber braucht gleichzeitig eine vollständige registrierte Node-Liste und einen lesenden Gesamtüberblick, ohne Datenbanken als Konsensquelle einzuführen. Aggressive PoP-Gebühren-Burns entziehen der geplanten geschlossenen Hobby-Ökonomie Liquidität. + +## Entscheidung + +1. Zentral betriebene **Potatium Network Services** (Arbeitstitel `potatium-network`) als **Control Plane** für signierte Bootstrap-Konfiguration, Registrierungsvorgänge, Betreiber-/Geräteinventar und Telemetrie; ein geschütztes **read-only Network Admin**-Dashboard ist von Beginn des nutzbaren Devnets an Bestandteil. Für eingeschriebene Benutzer ist keine zentrale Online-Authentisierung je Blockchain-Transaktion erforderlich. +2. Der zentrale Dienst führt **keine Coin-Datenbank** und kontrolliert keine Blockproduktion. CometBFT-Validatoren und Full Nodes replizieren den maßgeblichen Ledger und genehmigte Betreiber-/Mining-Identitäten auf Chain. Eine zentrale DB darf nur Anträge, Kontaktdaten nach Minimalprinzip, Beobachtungen und Status-Caches enthalten. +3. **Authentisierung getrennt:** Wallet-Spend-Key bleibt im Browser; `operator_id`, Node-P2P-Key und Mining-/Worker-Key sind andere Schlüsselrollen. Ein Besitzer kann mehrere Hubs/Worker und eine gemeinsame registrierte Reward-Wallet verwenden. Ein Fingerprint allein erlaubt keine Registrierung. Zentraler Antrag benötigt Besitznachweis, Mining-Zulassung erst nach signiertem, finalisiertem On-chain-Verwaltungsakt. Initial manuelle Freigabe mit gesonderter CLI, kein Web-Superuser zum Ledger. +4. **Bootstrap:** `chain_id`, vertrauenswürdiger Genesis-Hash, signiertes Manifest und Seed-/Persistent-Peer-Liste; Node synchronisiert **P2P direkt**, nicht per zentraler Coin-API. Netz darf private VPN/Peer-Zulassung verwenden. Mehrere unabhängige Seed-Kontaktpunkte/Fallback sind vorzusehen. +5. **100 % der PoP-Fenster-2-/3- sowie Worker-Aktivierungsgebühren fließen an den deterministischen, On-chain gebuchten Community-Fonds; 0 % Burn für diese Kategorien.** Fondsausgaben benötigen eigene, noch zu spezifizierende Governance/Autorisierung. Normale Transaktionsgebühren bleiben separat offen. Tarife/Emission bleiben offen. `burned_total` wird nicht gelöscht, aber PoP-/Worker-Gebühren erhöhen ihn nicht. Der Fonds wird **nicht** zusätzlich zum Umlauf addiert. +6. Bereits registrierte Betreiber und Validatoren bleiben ohne zentralen Dienst funktionsfähig, solange ausreichend P2P/Quorum, Hub und Finanzierung vorhanden sind; Registrierungs-Portal und gesamtnetzweites Monitoring fallen ggf. aus. Admin-Ausfall ist **kein Chain-Stopp**. +7. Das normale Hub-UI zeigt nur das eigene Node-/Wallet-/Cluster-Inventar und freigegebene öffentliche Netzwerkdaten. Registrierte Betreiber-Geräte, Fonds-/Supply-Details, alle beobachtbaren Nodes, Host-Status und Pending-Anträge bleiben **im Betreiber-Admin**. + +## Alternativen + +- **Nur Peer Discovery/ohne Benutzerverzeichnis:** einfach, aber im privaten Netz unpraktische Zulassung, schlechtes Inventar und schwache Administrierbarkeit. +- **Zentraler Coin-Server mit SQL-Guthaben:** abgelehnt, unterläuft Blockchain-Finality und schafft Single Point of Failure. +- **Zentraler Login für jeden Block/Miner-Ticket:** abgelehnt, schafft unnötige Betriebsabhängigkeit und Zensurpunkt. +- **Automatische Freigabe durch Wallet-Fingerprint:** abgelehnt, Sybil-/Geräteproblem ungelöst. +- **PoP-/Worker-Gebühren weiterhin verbrennen:** gegenüber 100-%-Fonds verworfen; Burn-Forschung bleibt als Vergleich dokumentiert. + +## Konsequenzen + +M1: Signatur-/Registry-/Bootstrap- und Fonds-Buchungsmodell spezifizieren, den zentralen Dienst und den Local-App-Store getrennt auswählen. M2: minimalen Network Services/Bootstrap-Dienst mit echtem read-only Network Admin und Hub-Status anbinden; M3: mehrere echte Peers/Seeds und Validatoren; M4: Antrags-/Genehmigungsworkflow und on-chain PoP-Registry, Worker-Registrierung, Gebührenfonds, Negativtests. M5/M6: gesicherter Betreiberbetrieb, Monitoring-Historie, Backup, Redundanz und Recovery. Die Anmeldung/Registrierung ist **nicht gleich** Wallet-Erstellung und **nicht gleich** P2P-Handshake. + +## Noch offen + +Operator-Signaturschema, Challenge/Response, Portal-Sitzungen, Minimal-Personendaten/Aufbewahrung, private P2P-Netz-Zugangspolitik, exaktes Enrollment-/Revocation-Format, Verantwortlichkeiten mehrerer Admins, Governance/Fonds-Ausgaben, Backend-Technologie (SQLite möglicher Kandidat), Ports, Zertifikatserneuerung, Relaying-/NAT-Szenarien und Sybil-Grenze. Keine der offenen Entscheidungen in Code hineinraten. + +**Referenz:** `docs/NETWORK_SERVICES_UND_ONBOARDING.md`, `docs/INFRASTRUKTUR_UND_DEPLOYMENT.md`, `docs/COCKPITS_UND_MONITORING.md`. + +**Vertraulichkeitsgrenze:** Die Betreiber-WebGUI darf exklusiv sein; **On-chain-Fakten (Emission, Fondsbestand, Gebühren und ggf. öffentliche Registry) sind für technisch versierte Betreiber eigener Full Nodes grundsätzlich selbst abfragbar**. Ein UI-Verbot schützt keine Blockchain-Daten. Nicht-öffentliche Betreiber-Kontaktinformationen, unveröffentlichte Geräteadressen und vollständige Heartbeat-/Host-Inventare gehören ausschließlich in die geschützte Off-chain-Control-Plane. Wenn diese Daten wirklich geheim bleiben sollen, **nicht** auf eine für Full Nodes replizierte Chain schreiben. diff --git a/docs/decisions/0008-schlanker-netzzugang-admin-und-identitaeten.md b/docs/decisions/0008-schlanker-netzzugang-admin-und-identitaeten.md new file mode 100644 index 0000000..d188a88 --- /dev/null +++ b/docs/decisions/0008-schlanker-netzzugang-admin-und-identitaeten.md @@ -0,0 +1,42 @@ +# ADR 0008: Schlanker P2P-Netzzugang, On-chain-Identitäten und frühes Betreiber-Cockpit + +**Datum:** 2026-10-25 +**Status:** **Akzeptierte Produkt-/Betriebsentscheidung, nicht implementiert.** +**Ersetzt teilweise:** ADR 0007 (verpflichtende zentrale `potatium-network`-Registrierungs-/Bootstrap-Control-Plane). **Bestätigt ausdrücklich:** ADR 0007: 100 % PoP-Fenster-/Worker-Gebühren an On-chain-Fonds, 0 % Burn dieser Gebühren. + +## Anlass + +Der Projektbetreiber will keine Benutzerplattform, keine zentrale Passwort-/Wallet-Datenbank und keine überflüssigen zentralen Dienste. Gleichzeitig soll ein privates Netzwerk einsehbar und Mining-Identitäten manuell kontrollierbar sein. Die bisherige verpflichtende Registrierungs-Control-Plane aus ADR 0007 würde diese Anforderungen unnötig verkomplizieren. + +## Verbindliche Entscheidung + +1. **P2P-Zugang ist nicht Benutzerregistrierung.** Ein Hub bekommt lokal eine überprüfbare Chain-ID, Genesis-Prüfsumme und Seed-/Persistent-Peer-Konfiguration. Nodes synchronisieren Blockchain direkt über CometBFT. Kein `potatium-network`-Dienst, Login, zentrale SQL-Datenbank oder Online-Genehmigung für regulären Blockchain-Sync zwingend. +2. **On-chain-Mining-Registry bleibt Pflicht für PoP.** Betreiberidentität (`operator_id`), öffentliche Hub-/Worker-/Mining-Schlüssel, Betreiberzuordnung, eine registrierte Reward-Adresse, Berechtigung, Sperr-/Widerrufsstatus und Audit sind deterministisch im Chain-State. Eine lokale `node.toml` o. Ä. kann Identitäten angeben, **aber keine eigene Berechtigung herbeikonfigurieren**. Kopieren von Schlüsseln, VMs oder Wallets erzeugt nicht automatisch neue gültige Tickets. +3. **Freigaben und Sperren sind die einzigen frühen schreibenden Betreiberfunktionen.** Ein Betreiber/Worker liefert einen öffentlichen Schlüssel mit Besitznachweis; der Netzwerkbetreiber prüft Anträge/Schlüssel zunächst manuell und autorisiert On-chain-Verwaltungsaktionen mit separater geschützter Signierinstanz/CLI. Sobald ausreichend abgesichert, darf das Admin-Cockpit den Freigabe-/Sperrvorgang **auslösen**, aber die schreibende Freigabe ist nie eine reine DB-Flag-Änderung. Das genaue Signatur-/Rechteschema und die Übermittlung sind M1/M4-Protokollaufgaben. Keine pauschalen Ledger-Admin-Sonderrechte. +4. **Network Admin ab erstem lauffähigen Devnet, Hub-Cockpit parallel.** Getrennter Betreiberzugang. Das Betreiber-Cockpit liest Chain-ID, Blockhöhe, Validatoren, bestätigte Emission, verbrannte Gesamtmenge, Fondsbestand und Bewegungen, Gebühren- und Fensterparameter, Betreiber-/Mining-Registry, angemeldete IDs/Status. Keine Fake-Werte. Identitätsverwaltung zeigt den Antrags-/Genehmigungszustand, sobald der jeweilige Datenpfad existiert. Bei noch nicht implementiertem State ehrlich „nicht verfügbar“. +5. **Statuszählungen trennen:** on-chain registriert ≠ P2P-kürzlich beobachtet ≠ signiert meldend ≠ tatsächlich minend. Globales Live-Inventar ist ohne freiwillige signierte Heartbeats oder eine dedizierte Beobachtungsinfrastruktur nicht vollständig. **Optionaler Telemetrie-/Inventarspeicher** (z. B. SQLite) ist zulässig, aber keine Pflicht und nicht Konsensquelle. Datenherkunft, Messzeit und Abdeckungsgrad immer ausgeben. +6. **Hub und Worker** bleiben die zwei Teilnehmerpakete. Hub = eigenständige Full Node mit lokaler Node-/Cluster-GUI, Web-Wallet mit browserseitigen Spend-Keys und optionalem Miner. Worker = an Hub gebundener schlanker Mining-Agent mit eigenem Mining-Key, ohne Full-Chain-/Spend-Key. Gemeinsam zugelassene Betreiber-Einnahmen und -Gebühren, maximal ein Ticket je zugelassener Mining-ID und Runde. Höchstens ein kostenloser Hub-Mining-Platz je genehmigtem Betreiber; Sybil-Risiko bleibt ohne manuelle Prüfung. +7. **Fonds**: 100 % der PoP-Zusatzfenster- und Workergebühren werden dem on-chain Fonds zugeschrieben. Fondsausgaben, Tarif- und Transaktionsgebührenentscheidung weiterhin offen. Das Admin-Cockpit darf nicht eigenmächtig Fonds-PTM transferieren. +8. **Deployments:** zuerst `potatium-dev` allein mit isolierten Devnet-Prozessen; später getrennte Validatoren und nach Bedarf geschützter `ptm-admin`-LXC. Weder die Administration noch ein optionale Statussammler darf Konsens/PoP-Gültigkeit blockieren. Native VM/Pi zuerst, Docker optional später. + +## Alternativen + +- Pflichtdienst `potatium-network` für Anmeldung/Bootstrap/Mining: **verworfen** (unnötiger Single Point of Failure und Nutzerverwaltung). +- Nur lokale Node-Konfiguration für PoP-Rechte: **verworfen** (beliebig vermehrbare Node-/Mining-IDs). +- Volle Web-Administration ab Projektstart: **verworfen** (Signatursicherheit nicht ausreichend spezifiziert); reine Beobachtung plus begrenzte genehmigende/sperrende Aktion sobald kryptografisch korrekt. +- Zentraler Ledger/SQL-Master: **verworfen**. + +## Akzeptanzbedingungen + +- Hub synchronisiert über mehrere bootstrapfähige Peers, auch wenn Network Admin offline ist; authentischer Genesis-/Chain-ID-Abgleich. +- Ohne finale On-chain-Freigabe kein Ticket, Sperre wirkt ab spezifizierter Höhe; Replay/Double-Enroll/Schlüsselkopie mit Negativtests behandeln. +- Admin-Ansicht zeigt reale Supply-/Burn-/Fund-/Registry-Werte, Datenquelle, Blockhöhe, Messzeit, Stale-Zustand; nie simulierte Live-Werte. +- Signierende Admin-Schlüssel niemals im lesenden Webserver, im Hub oder Worker; signierte Admin-Aktionen auditieren. +- Operator-/Wallet-/Node-/Worker-/Validator-Schlüssel getrennt, keine privaten Wallet-Schlüssel auf Servern. +- Keine zentrale Datenbankänderung kann Guthaben, PoP-Registry, Fonds oder Validatorzustand ersetzen. + +## Offene Implementierungsparameter + +Kompatible CometBFT-Version, Embedded-State-DB, Ports, Go-API, Operator-Schlüsselschema, pairing/binding-Verfahren, Admin-Signaturschema, On-chain-Antragstransport, IP/Peer-Schutz, vollständige Telemetrie, Wallet-Integration, Rücksetzbarkeit des Devnets. Keine Werte durch einen Codegenerator eigenmächtig entscheiden. + +**Referenzen:** `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md`, `docs/COCKPITS_UND_MONITORING.md`, `docs/INSTALLATIONSPLAN_HOMELAB.md`, `docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md`. diff --git a/docs/research/po_p_dynamic_20261025_comparison.csv b/docs/research/po_p_dynamic_20261025_comparison.csv new file mode 100644 index 0000000..8e8c549 --- /dev/null +++ b/docs/research/po_p_dynamic_20261025_comparison.csv @@ -0,0 +1,13 @@ +worker_scale,worker_mode,fund_mode,seeds,fund_balance,fund_pct_supply,fund_distributed,fund_worker_in,fund_window_in,minted,gini_wallets,avg_workers_total,newcomer_wins,newcomer_net_daily,incumbent_net_daily,capacity_0_net,capacity_0_wallet,capacity_0_gross,capacity_0_rebate,capacity_0_activated,capacity_1_net,capacity_1_wallet,capacity_1_gross,capacity_1_rebate,capacity_1_activated,capacity_3_net,capacity_3_wallet,capacity_3_gross,capacity_3_rebate,capacity_3_activated,capacity_5_net,capacity_5_wallet,capacity_5_gross,capacity_5_rebate,capacity_5_activated,capacity_8_net,capacity_8_wallet,capacity_8_gross,capacity_8_rebate,capacity_8_activated,capacity_10_net,capacity_10_wallet,capacity_10_gross,capacity_10_rebate,capacity_10_activated +1.0,dynamic,hold,3,31000.9627,23.9205,0.0,28618.8737,2382.089,129600.0,0.35426,94.9258,15573.667,24.9277,24.2727,1184.4618,1184.4618,1328.0833,0.0,0.0,2123.8369,2123.8369,2356.9167,0.0,0.9995,4017.5246,4017.5246,4506.4444,0.0,2.9989,5361.3234,5361.3234,6619.4444,0.0,4.9964,7216.1895,7216.1895,9888.0,0.0,7.9895,8121.0838,8121.0838,12081.5556,0.0,9.9972 +1.0,dynamic,redistribute,3,13130.7071,10.1317,17870.2555,28618.8737,2382.089,129600.0,0.30471,94.9258,15573.667,24.9277,24.2727,1184.4618,1960.3123,1328.0833,775.8505,0.0,2123.8369,2899.6874,2356.9167,775.8505,0.9995,4017.5246,4793.3751,4506.4444,775.8505,2.9989,5361.3234,6137.1739,6619.4444,775.8505,4.9964,7216.1895,7992.04,9888.0,775.8505,7.9895,8121.0838,8896.9343,12081.5556,775.8505,9.9972 +1.0,fixed,hold,3,31000.9627,23.9205,0.0,28618.8737,2382.089,129600.0,0.35426,94.9258,15573.667,24.9277,24.2727,1184.4618,1184.4618,1328.0833,0.0,0.0,2123.8369,2123.8369,2356.9167,0.0,0.9995,4017.5246,4017.5246,4506.4444,0.0,2.9989,5361.3234,5361.3234,6619.4444,0.0,4.9964,7216.1895,7216.1895,9888.0,0.0,7.9895,8121.0838,8121.0838,12081.5556,0.0,9.9972 +1.0,fixed,redistribute,3,13130.7071,10.1317,17870.2555,28618.8737,2382.089,129600.0,0.30471,94.9258,15573.667,24.9277,24.2727,1184.4618,1960.3123,1328.0833,775.8505,0.0,2123.8369,2899.6874,2356.9167,775.8505,0.9995,4017.5246,4793.3751,4506.4444,775.8505,2.9989,5361.3234,6137.1739,6619.4444,775.8505,4.9964,7216.1895,7992.04,9888.0,775.8505,7.9895,8121.0838,8896.9343,12081.5556,775.8505,9.9972 +3.0,dynamic,hold,3,52603.6148,40.5892,0.0,49515.871,3087.7438,129600.0,0.24586,73.0943,15471.333,19.4428,18.9575,1401.5994,1401.5994,1628.25,0.0,0.0,2317.8133,2317.8133,2827.5,0.0,0.9993,4111.283,4111.283,5394.1111,0.0,2.9987,4409.7564,4409.7564,7942.6667,0.0,4.9387,4640.5973,4640.5973,9387.8889,0.0,5.9871,4628.1886,4628.1886,9377.2222,0.0,5.9898 +3.0,dynamic,redistribute,3,21442.5825,16.5452,31161.0323,49515.871,3087.7438,129600.0,0.18946,73.0943,15471.333,19.4428,18.9575,1401.5994,2762.1606,1628.25,1360.5611,0.0,2317.8133,3678.3745,2827.5,1360.5611,0.9993,4111.283,5471.8441,5394.1111,1360.5611,2.9987,4409.7564,5770.3176,7942.6667,1360.5611,4.9387,4640.5973,6001.1584,9387.8889,1360.5611,5.9871,4628.1886,5988.7497,9377.2222,1360.5611,5.9898 +3.0,fixed,hold,3,85362.78,65.8663,0.0,82933.581,2429.199,129600.0,0.32795,92.4115,13460.667,8.769,11.192,1192.9846,1192.9846,1348.8333,0.0,0.0,1901.9854,1901.9854,2393.1667,0.0,0.9993,3332.4923,3332.4923,4596.0,0.0,2.996,3168.1911,3168.1911,6755.4444,0.0,4.9956,2245.5886,2245.5886,10092.2222,0.0,7.988,557.498,557.498,12280.1111,0.0,9.9864 +3.0,fixed,redistribute,3,37673.5072,29.0691,49740.6412,85006.161,2407.9874,129600.0,0.18972,94.185,14955.667,7.4858,10.7826,1177.4129,3331.422,1328.0,2154.0091,0.0,1877.9021,4031.9112,2369.0833,2154.0091,0.9993,3289.0478,5443.057,4552.5556,2154.0091,2.996,3044.9689,5198.9781,6632.2222,2154.0091,4.9956,2102.8108,4256.82,9949.4444,2154.0091,7.988,428.498,2582.5071,12151.1111,2154.0091,9.9864 +5.0,dynamic,hold,3,34750.0621,26.8133,0.0,30255.565,4494.4971,129600.0,0.22236,46.2116,15111.667,23.505,23.4091,1956.2867,1956.2867,2230.3333,0.0,0.0,3051.4123,3051.4123,3886.5833,0.0,0.9995,5386.176,5386.176,7491.6667,0.0,2.9992,5360.7506,5360.7506,7523.8889,0.0,3.0235,5319.4582,5319.4582,7484.5556,0.0,3.0253,5347.5764,5347.5764,7506.7778,0.0,3.0255 +5.0,dynamic,redistribute,3,14347.7968,11.0708,20402.2652,30255.565,4494.4971,129600.0,0.19161,46.2116,15111.667,23.505,23.4091,1956.2867,2845.2417,2230.3333,888.955,0.0,3051.4123,3940.3673,3886.5833,888.955,0.9995,5386.176,6275.131,7491.6667,888.955,2.9992,5360.7506,6249.7056,7523.8889,888.955,3.0235,5319.4582,6208.4132,7484.5556,888.955,3.0253,5347.5764,6236.5314,7506.7778,888.955,3.0255 +5.0,fixed,hold,3,90985.0921,70.2045,0.0,87811.1967,3173.8954,129600.0,0.46911,66.0563,13671,8.2827,9.691,1530.1551,1530.1551,1755.5833,0.0,0.0,2262.2745,2262.2745,3048.9167,0.0,0.9994,3809.6369,3809.6369,5880.0,0.0,2.9966,2744.0213,2744.0213,8691.6667,0.0,4.9983,17.5649,17.5649,12498.4444,0.0,7.6456,1.4354,1.4354,5166.8889,0.0,2.6287 +5.0,fixed,redistribute,3,46355.6195,35.7682,60290.02,103694.88,2950.7595,129600.0,0.31169,74.5228,14317.667,2.2037,6.1007,1423.9797,4031.1171,1606.0833,2607.1374,0.0,2084.6015,4691.7389,2869.25,2607.1374,0.9994,3452.101,6059.2384,5520.0,2607.1374,2.9966,2179.3546,4786.492,8127.0,2607.1374,4.9983,-999.8246,1607.3129,11878.8889,2607.1374,7.8911,-1988.8431,618.2943,6934.4444,2607.1374,4.5586 diff --git a/handoff/CHATGPT_PROJEKTANWEISUNGEN.md b/handoff/CHATGPT_PROJEKTANWEISUNGEN.md index d400cbc..8a6b039 100644 --- a/handoff/CHATGPT_PROJEKTANWEISUNGEN.md +++ b/handoff/CHATGPT_PROJEKTANWEISUNGEN.md @@ -18,7 +18,7 @@ Go, CometBFT BFT/P2P und **eigene Potatium-ABCI++-Applikation**, zunächst **ohn **Eine PoP-Mining-Runde hat drei zeitlich aufeinanderfolgende Aufgabenfenster.** Die Fenster sind **keine separaten Blockchain-Runden**; die Blockzahl pro Fenster ist noch offen. Jede zugelassene Miner-Identität kann **maximal ein gültiges Ticket in der Gesamtrunde** erhalten. Erfolg in einem Fenster: Ticket, sofortiger Stopp für diese Runde. -**Fenster 1 immer kostenlos.** Solange noch kein gültiges Ticket angenommen wurde, können **Fenster 2 und 3 freiwillig** genutzt werden, jeweils erst nach bestätigter **fixer Grundgebühr plus progressiver Zusatzgebühr**. Höhere Kosten und abnehmender zusätzlicher Qualifikationsnutzen sollen Fenster 3 weniger attraktiv machen. Gebührenmaßstab: **finalisierte PoP-Mining-Gewinne des Miners/der zugeordneten Betreibergruppe im gleitenden Zeitraum der letzten ca. 90 Tage**, **nicht das aktuelle Wallet-Guthaben**. Kein lebenslang kumulierender Erfolgsbonus/-malus; ältere Gewinne fallen heraus. Gebühren für Zusatzfenster werden **verbrannt**. Normale Transaktionsgebühren sollen Validatoren finanzieren; genaue Verteilung weiter offen. +**Fenster 1 immer kostenlos.** Solange noch kein gültiges Ticket angenommen wurde, können **Fenster 2 und 3 freiwillig** genutzt werden, jeweils erst nach bestätigter **fixer Grundgebühr plus progressiver Zusatzgebühr**. Höhere Kosten und abnehmender zusätzlicher Qualifikationsnutzen sollen Fenster 3 weniger attraktiv machen. Gebührenmaßstab: **finalisierte PoP-Mining-Gewinne des Miners/der zugeordneten Betreibergruppe im gleitenden Zeitraum der letzten ca. 90 Tage**, **nicht das aktuelle Wallet-Guthaben**. Kein lebenslang kumulierender Erfolgsbonus/-malus; ältere Gewinne fallen heraus. Gebühren für Zusatzfenster und Worker werden **zu 100 % dem On-chain-Fonds gutgeschrieben; 0 % Burn**. Normale Transaktionsgebühren sollen Validatoren finanzieren; genaue Verteilung weiter offen. Leichte echte kryptografische Arbeit (SHA-256 als Kandidat) mit **begrenztem pro-Identität-Nonce-Raum** und erst zum Fensterstart freigeschalteter Herausforderung. GPU/ASIC-/CPU-Wettrennen vermeiden; stärkere CPUs können schneller rechtzeitig fertig werden, vollständige Hardware-Gleichheit nicht behaupten. Pi-4-Benchmarks und Simulationen mit 5/10/20/50 Minern vor Festlegung der Schwierigkeit/Kosten. Begrenzter Nonce-Raum garantiert **keine** reale Arbeitszeit; erfolglose Versuche sind nicht beweisbar. @@ -39,3 +39,8 @@ Keine privaten Schlüssel, Seeds, Tokens, Passwörter, `data/`, Binaries, echte ## Reihenfolge M1: offenen Parameterkatalog + Simulation/Fairness/Pi-4-Benchmark + ADR. M2: Ledger/Keys/Accounts/Tx/Genesis mit Tests und **parallel funktionale Web-Wallet**, sobald Tx-/Signaturschnittstelle feststeht. M3: CometBFT anbinden, 1 dann 4 Validatoren mit Synchronisation/Ausfällen. M4: drei PoP-Fenster, Identity-Registry, Kosten/Autorisierung, Burn, Proof, Ticketcommit, RNG/Mint mit harten Negativtests. M5: Wallet fertig, Explorer/Status, ARM64-Builds/Pi-Vergleich. M6: Mehrhost-Privat-Testnet, Backup/Recovery, Doku, Release. Fokus: wartbares, lehrreiches Hobbyprojekt statt öffentlichem Mainnet. + + +## Aktualisierung 25.10.2026 · ADR 0008 und Forschung + +**Neuere Entscheidung hat Vorrang:** Kein verpflichtender `potatium-network`-Registrierungsserver. Hub synchronisiert per lokaler Chain-ID/Genesis/CometBFT-Peers; Mining-Zulassung ausschließlich per On-chain-Registry und signierter Admin-Freigabe/Sperre. Separater Network Admin ab erstem lauffähigem Devnet: lesende globale Supply-/Burn-/Fonds-/Parameter-/Identitäts-/Validatoransichten, keine Fake-Werte. Der vorhandene Homelab-Installationsplan ist `docs/INSTALLATIONSPLAN_HOMELAB.md`. Gebührenhöhe/Emission/Maximalworker und Fondsausgaben bleiben offen. 36-Läufe-Simulation ist lediglich Forschung: `docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md`. diff --git a/handoff/REPO_UPDATE_V05.md b/handoff/REPO_UPDATE_V05.md new file mode 100644 index 0000000..7c6fe49 --- /dev/null +++ b/handoff/REPO_UPDATE_V05.md @@ -0,0 +1,13 @@ +> **HISTORISCHER STAND v0.5:** Die damals verpflichtende zentrale `potatium-network`-Anmeldung wurde durch ADR 0008 verworfen. Aktuell gilt `docs/NETWORK_ZUGANG_UND_IDENTITAETEN.md`. Die 100-%-Fondsentscheidung bleibt gültig. + +# Repo-Update v0.5: Gesamtinfrastruktur und erste Cockpits + +Stand 25.10.2026, ausgehend vom unveränderten Referenzstand v0.4.0. Keine Go-/ABCI++-/Web-Implementierung. Nur Planung, README, AGENTS und ADR 0006. + +Akzeptierte Betriebsentscheidung: Projektbetreiber betreibt initial Validatoren; alle Full Nodes halten ihren eigenen Chain-/App-State; kein zentraler Coin-Server. Potatium Hub und separater read-only Network Admin mit **echtem Status** ab erstem lauffähigem Devnet, bei zunächst fehlender Chain nur ehrliche Fehlerzustände. Cluster-Ansicht eigener Hubs/Worker und Betreiber-Admin sind weiterhin verschiedene Rollen. Docker optional später, kein Docker-in-LXC. Alte Gebühren-Burn-Regel nicht still geändert, Verwendung wird nach Forschung bewusst zu entscheiden sein. + +**Nach Anwendung:** `git diff --check`, `git diff`, `go test ./...`, `go vet ./...`, Tests nur als tatsächlich ausgeführt angeben. Vor Protokoll-/Wallet-/API-Implementierung Versionen, konkrete Signaturen, Monitoring-Auth und Datenbank festlegen. Keine Geheimnisse/Chain-State committen. + +## Integrierte finale Entscheidung: Network Services und On-chain-Fonds + +Die zunächst rein lesende Admin-Infrastruktur wird durch einen separaten Betreiber-Service `potatium-network` ergänzt: verifizierter Bootstrap, Operator-Registrierungsanträge, Inventar und signierte Heartbeats mit **eigener Off-chain DB**. Registrierung muss durch **gesonderte signierte On-chain-Admin-Aktion** finalisiert werden. Es handelt sich nicht um eine zentrale Chain-/Guthaben-Datenbank. Hub-P2P-/Walletbetrieb wird nicht vom zentralen Service je Block autorisiert. Gebührenverwendung ist nun beschlossen: **100 % PoP-/Worker-Gebühren an On-chain-Fonds, 0 % Burn**; Tx-Gebühren und Fonds-Ausgaben weiterhin offen. Siehe ADR 0007 und `docs/NETWORK_SERVICES_UND_ONBOARDING.md`. diff --git a/handoff/REPO_UPDATE_V06.md b/handoff/REPO_UPDATE_V06.md new file mode 100644 index 0000000..c1b649a --- /dev/null +++ b/handoff/REPO_UPDATE_V06.md @@ -0,0 +1,9 @@ +# Dokumentations- und Forschungsupdate v0.6 (25.10.2026) + +Ziel: Auswertung der 36 PoP-Dynamik-/Fonds-Läufe nachvollziehbar sichern; die inzwischen verworfene Pflicht zu zentralen Network Services/Registrierungsportal konsistent aus den *aktuellen* Handlungsanweisungen ersetzen; Infrastrukturplan und frühe Netzwerk-Admin-Identitätsverwaltung als feste Produktplanung integrieren. + +**Produktentscheidungen:** (1) keine zentrale Registrierung für P2P, (2) PoP-Registry on-chain mit signierter Freigabe/Sperre, (3) Network Admin und Hub-Cockpit ab erstem Devnet mit echten Daten, (4) 100 % PoP-/Workergebühren an Fonds, 0 % Burn dieser Gebühren. **Nicht entschieden:** Gebührenskalierung 1×/3×/5×, 1 PTM/Runde, 720 Runden/Tag, 10-Worker-Kappe, periodische Fonds-Ausschüttung oder zukünftige Finanzierungsmodelle. + +**Implementierungsstatus:** Nur Markdown-/CSV-Dokumentation und Python-Monte-Carlo-Simulation mit Tests; keine Blockchain, Wallet, Identitätsfreigabe oder CometBFT-Integration funktionsfähig. + +**Verifikation:** `python3 -m unittest discover -s tests -p 'test_pop_dynamic.py' -v`, `go test ./...`, `go vet ./...`, `git diff --check`; der sichere Update-Plan prüft SHA256-Basisstände und bricht bei geänderten lokalen Dateien ab. Siehe Paket-README. Ohne Zugriff auf das echte Gitea-Repo keine Aussage zum dortigen Git-/Implementierungsstand. diff --git a/handoff/STARTPROMPT_VSCODE_CODEX.md b/handoff/STARTPROMPT_VSCODE_CODEX.md index a07d657..aed1ecf 100644 --- a/handoff/STARTPROMPT_VSCODE_CODEX.md +++ b/handoff/STARTPROMPT_VSCODE_CODEX.md @@ -12,7 +12,7 @@ Du bist technischer Architekt, leitender Go-Entwickler und Reviewer für **Potat - Pro Mining-**Runde** genau drei nacheinander freigeschaltete **Fenster** (nicht 3 Blockchain-Runden); je zugelassene Mining-Identität maximal **ein** Ticket je Gesamtrunde. - Fenster 1 kostenlos; Fenster 2 und 3 freiwillig solange noch kein Ticket angenommen wurde, mit **fixer plus progressiver Gebühr**. Progressionsbasis = zugeordnete PoP-Gewinne der zurückliegenden **ca. 90 Tage**, **nicht** aktuelles Wallet-Guthaben; kein lebenslanger Malus. -- Kosten von Fenster 3 höher bei sinkendem Grenznutzen; zusätzliche Gebühren werden verbrannt. Überweisungsgebühren sind separat für Validatoren vorgesehen, Verteilungsmechanik offen. +- Kosten von Fenster 3 höher bei sinkendem Grenznutzen; zusätzliche PoP-/Worker-Gebühren gehen zu 100 % in den On-chain-Fonds; 0 % Burn dieser Gebühren. Überweisungsgebühren sind separat für Validatoren vorgesehen, Verteilungsmechanik offen. - Schwache Rechner/Pi 4 sollen begrenzte SHA-256-PoW-Aufgaben rechtzeitig bearbeiten können, harte Fairness- und Sybil-Grenzen dokumentieren. Für die Ziehung muss die Ticketliste vor Bekanntwerden des überprüfbaren künftigen Zufallswerts gebunden sein. - CometBFT/ABCI++ mit eigener Go-App ist Architekturpräferenz, **ohne Cosmos SDK**. Blockvalidator und Miner sind getrennt. - Web-Wallet mit echter Kontoerstellung/Import/verschlüsseltem Backup, Balance, Senden/Empfangen, Status, History, Lock/Unlock gehört **ab erstem funktionalem MVP** dazu. Bestehende UI ist nur statische Vorschau. Niemals Backend-/Minerzugriff auf private Browser-Wallet-Schlüssel. @@ -31,3 +31,12 @@ Du bist technischer Architekt, leitender Go-Entwickler und Reviewer für **Potat ## Spätere Vision, vorerst ausdrücklich NICHT implementieren Nach stabilen M1–M6-Meilensteinen sind digitale Sammlerstücke, ein PTM-Marktplatz und eine interne Handelsplattform für fiktive Assets/Kurse/Portfolio sowie optional getrenntes Paper-Trading vorgesehen. Details: `docs/ZUKUNFT_OEKOSYSTEM.md` und ADR 0004. **Kein** Asset-/Börsen-/NFT-Code jetzt, keine neuen Dependencies deswegen und keine Veränderung beschlossener PoP-Regeln. Im aktuellen Schritt bei M1 und anschließend Ledger/Web-Wallet bleiben. + +## Neue zu berücksichtigende Architekturentscheidung (ADR 0005) + +Vor nächsten Designänderungen `docs/HUB_WORKER_CLUSTER.md`, `docs/WORKER_GEBUEHREN_FORSCHUNG.md` und `docs/decisions/0005-hub-worker-cluster-und-betreiber.md` lesen. Zwei geplante Pakete: voller eigenständiger Hub mit Browser-Wallet/Status/PoP/Cluster-Cockpit; Hub-gebundener Lightweight-Worker nur mit signierender Geräte-ID, **keine** Geld-Wallet. Betreiber-Identität aggregiert sämtliche Gewinne/Fees; genau ein gebührenfreier Hub-Mining-Platz je Betreiber; andere Hub-Miner zählen wirtschaftlich als Worker. Jedes zugelassene Mining-ID maximal 1 Ticket/Runde. Seed/Private Wallet Key nur im Browser. Gebühren vor Challenge; Staffelbeträge und Worker-Limit sind **Forschungsparameter, nicht zu implementieren**. Priorität Hub-/Cluster-Cockpit vor Admin-Web; Admin-CLI später bei Bedarf. Nichts aus den Docs als bereits implementiert ausgeben. + + +## Aktualisierung 25.10.2026 · ADR 0008 und Forschung + +**Neuere Entscheidung hat Vorrang:** Kein verpflichtender `potatium-network`-Registrierungsserver. Hub synchronisiert per lokaler Chain-ID/Genesis/CometBFT-Peers; Mining-Zulassung ausschließlich per On-chain-Registry und signierter Admin-Freigabe/Sperre. Separater Network Admin ab erstem lauffähigem Devnet: lesende globale Supply-/Burn-/Fonds-/Parameter-/Identitäts-/Validatoransichten, keine Fake-Werte. Der vorhandene Homelab-Installationsplan ist `docs/INSTALLATIONSPLAN_HOMELAB.md`. Gebührenhöhe/Emission/Maximalworker und Fondsausgaben bleiben offen. 36-Läufe-Simulation ist lediglich Forschung: `docs/POP_DYNAMIK_FONDS_AUSWERTUNG_2026-10-25.md`. diff --git a/scripts/analyze_pop_fee_flows.py b/scripts/analyze_pop_fee_flows.py new file mode 100644 index 0000000..d05d907 --- /dev/null +++ b/scripts/analyze_pop_fee_flows.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +"""Potatium: research-only fee allocation from an existing cluster simulation. + +No simulation state is modified. This is an ACCOUNTING COUNTERFACTUAL, not +an incentive-aware rerun: miners still make the same decisions because fees +are paid to recipient accounts outside the participating miner population. + +Input: existing pop_cluster_*.json produced by simulate_pop_cluster.py, + either in a directory or inside a ZIP (including the user's archive). +Output: TXT, CSV, JSON and one ZIP for subsequent evaluation. +All float calculations are OFF CHAIN and never consensus implementation. +""" +from __future__ import annotations + +import argparse +import csv +import json +import math +import statistics +import zipfile +from collections import defaultdict +from dataclasses import dataclass +from pathlib import Path + +MAX_FILE_SIZE = 30 * 1024 * 1024 +MAX_INPUT_FILES = 100 +TOLERANCE = 0.002 # old reports round wallet balances to six decimals + + +@dataclass(frozen=True) +class Split: + """Fractions for (burn, validator, treasury). Must sum to exactly 1.""" + burn: float + validators: float + treasury: float + + def __post_init__(self): + if any(not math.isfinite(x) or x < 0 or x > 1 for x in (self.burn, self.validators, self.treasury)): + raise ValueError('Split fractions must be finite in [0,1]') + if not math.isclose(sum((self.burn, self.validators, self.treasury)), 1.0, abs_tol=1e-9): + raise ValueError('Split fractions must sum to 1') + + +@dataclass(frozen=True) +class Policy: + name: str + worker: Split + window: Split + + +def presets() -> list[Policy]: + burn = Split(1, 0, 0) + validator = Split(0, 1, 0) + treasury = Split(0, 0, 1) + return [ + Policy('burn_100', burn, burn), + Policy('validators_100', validator, validator), + Policy('treasury_100', treasury, treasury), + Policy('validators_80_treasury_20', Split(0, .8, .2), Split(0, .8, .2)), + Policy('validators_70_treasury_25_burn_5', Split(.05, .7, .25), Split(.05, .7, .25)), + Policy('worker_treasury_window_validators', treasury, validator), + ] + + +def parse_split(value: str) -> Split: + try: + parts = [float(x.strip()) for x in value.split(',')] + except ValueError as ex: + raise ValueError('Expected three comma-separated percentages (burn,validators,treasury)') from ex + if len(parts) != 3: + raise ValueError('Expected exactly three percentages (burn,validators,treasury)') + if not math.isclose(sum(parts), 100, rel_tol=0, abs_tol=1e-7): + raise ValueError('Percentages must sum to 100') + return Split(*(p / 100 for p in parts)) + + +def _read_json(data: bytes, name: str) -> list[dict]: + try: + doc = json.loads(data) + except (ValueError, UnicodeError) as ex: + raise ValueError(f'{name}: invalid JSON: {ex}') from ex + if not isinstance(doc, dict) or not isinstance(doc.get('scenarios'), list) or not doc['scenarios']: + raise ValueError(f'{name}: expected JSON containing nonempty scenarios[]') + return doc['scenarios'] + + +def load_sources(path: Path) -> tuple[list[dict], list[str]]: + """Never extract ZIP content; parse only recognized JSON entries by name.""" + found = [] + if path.is_dir(): + for f in path.rglob('pop_cluster_*.json'): + if not f.is_file() or f.stat().st_size > MAX_FILE_SIZE: + raise ValueError(f'oversized/inaccessible input file {f}') + found.append((str(f), f.read_bytes())) + elif path.is_file() and path.suffix.lower() == '.zip': + with zipfile.ZipFile(path) as archive: + for item in archive.infolist(): + if item.is_dir() or not Path(item.filename).name.startswith('pop_cluster_') or not item.filename.endswith('.json'): + continue + if item.file_size > MAX_FILE_SIZE: + raise ValueError(f'ZIP member exceeds size limit: {item.filename}') + found.append((item.filename, archive.read(item))) + elif path.is_file() and path.name.startswith('pop_cluster_') and path.suffix.lower() == '.json': + if path.stat().st_size > MAX_FILE_SIZE: + raise ValueError('JSON file exceeds size limit') + found.append((str(path), path.read_bytes())) + else: + raise ValueError('Provide ZIP, directory of pop_cluster_*.json, or a pop_cluster_*.json file') + if not found or len(found) > MAX_INPUT_FILES: + raise ValueError(f'Expected 1..{MAX_INPUT_FILES} pop_cluster_*.json files, got {len(found)}') + scenarios = [] + for name, blob in sorted(found): + for source in _read_json(blob, name): + scenarios.append(source) + return scenarios, [name for name, _ in sorted(found)] + + +def _number(v: object, desc: str) -> float: + if type(v) not in (int, float) or not math.isfinite(v): + raise ValueError(f'missing/invalid {desc}') + return float(v) + + +def key_for(parameters: dict) -> str: + """Research group with target worker count and seed removed.""" + stable = {k: v for k, v in parameters.items() if k not in ('target_workers', 'seed')} + return json.dumps(stable, sort_keys=True, separators=(',', ':')) + + +def project(record: dict, policy: Policy, validators: int) -> dict: + if not (isinstance(validators, int) and validators > 0): + raise ValueError('validators must be positive') + if not isinstance(record, dict): + raise ValueError('invalid scenario') + p, s, ops = (record.get('parameters'), record.get('summary'), record.get('operators')) + if not isinstance(p, dict) or not isinstance(s, dict) or not isinstance(ops, list) or not ops: + raise ValueError('scenario lacks parameters, summary or operators') + worker = _number(s.get('burned_workers'), 'burned_workers') + window = _number(s.get('burned_windows'), 'burned_windows') + old_burn = _number(s.get('burned_total'), 'burned_total') + minted = _number(s.get('minted'), 'minted') + origin = _number(p.get('initial_balance'), 'initial_balance') * len(ops) + miner_wallets = sum(_number(op.get('wallet'), 'operator.wallet') for op in ops) + if min(worker, window, minted, origin, miner_wallets) < -1e-8: + raise ValueError('negative source accounting amount') + if abs(old_burn - worker - window) > TOLERANCE: + raise ValueError('source fee components do not match') + if abs((minted + origin - old_burn) - miner_wallets) > TOLERANCE: + raise ValueError('source accounting not conserved (operator wallets vs mint minus fees)') + burn = worker * policy.worker.burn + window * policy.window.burn + paid_validators = worker * policy.worker.validators + window * policy.window.validators + paid_treasury = worker * policy.worker.treasury + window * policy.window.treasury + total_fees = worker + window + if not math.isclose(total_fees, burn + paid_validators + paid_treasury, rel_tol=1e-10, abs_tol=1e-6): + raise AssertionError('fee destination split violated conservation') + outstanding = minted + origin - burn + cash_with_users_and_validators = miner_wallets + paid_validators + if not math.isclose(outstanding, cash_with_users_and_validators + paid_treasury, rel_tol=1e-10, abs_tol=TOLERANCE): + raise AssertionError('post-fee balances violate conservation') + # A fee diverted to an external account remains spent by its originating miner. + return { + 'policy': policy.name, + 'group': key_for(p), + 'target_workers': p.get('target_workers'), + 'seed': p.get('seed'), + 'minted': minted, + 'genesis': origin, + 'gross_supply': minted + origin, + 'fees_worker': worker, + 'fees_window': window, + 'fees_total': total_fees, + 'burned': burn, + 'burn_pct_minted': 100 * burn / minted if minted else 0.0, + 'paid_validators': paid_validators, + 'validator_each': paid_validators / validators, + 'paid_treasury': paid_treasury, + 'outstanding_supply': outstanding, + 'miner_wallets': miner_wallets, + 'non_treasury_balances': cash_with_users_and_validators, + 'treasury_share_outstanding_pct': (100 * paid_treasury / outstanding if outstanding else 0.0), + 'target_miner_net': _number(s.get('target_net'), 'target_net'), + 'target_win_pct': _number(s.get('target_win_percent'), 'target_win_percent'), + 'gini_miners_only': _number(s.get('gini_operator_wallets'), 'gini_operator_wallets'), + 'validators': validators, + 'operators': len(ops), + 'other_workers': p.get('others_workers'), + 'worker_fee_scale': p.get('worker_fee_scale'), + 'newcomers': p.get('newcomers'), + 'days': p.get('days'), + } + + +def summarize(records: list[dict]) -> list[dict]: + by = defaultdict(list) + for r in records: + by[(r['group'], r['target_workers'], r['policy'])].append(r) + metrics = ('minted', 'fees_worker', 'fees_window', 'fees_total', 'burned', + 'burn_pct_minted', 'paid_validators', 'validator_each', 'paid_treasury', + 'outstanding_supply', 'miner_wallets', 'non_treasury_balances', + 'treasury_share_outstanding_pct', 'target_miner_net', 'target_win_pct', + 'gini_miners_only') + rows = [] + for (_, _, _), v in sorted(by.items(), key=lambda kv: (kv[1][0]['other_workers'], kv[1][0]['worker_fee_scale'], kv[0][1], kv[0][2])): + r = v[0] + rows.append({k: r[k] for k in ('group', 'target_workers', 'policy', 'other_workers', + 'worker_fee_scale', 'newcomers', 'days', 'validators', 'operators')} + | {'seeds': len(v), 'seed_list': ','.join(str(x['seed']) for x in v)} + | {k: statistics.mean(x[k] for x in v) for k in metrics}) + return rows + + +def _format_line(r: dict) -> str: + return (f"{r['target_workers']:>2} {r['policy']:<38} " + f"{r['fees_total']:>11,.1f} {r['burned']:>11,.1f} " + f"{r['paid_validators']:>11,.1f} {r['paid_treasury']:>11,.1f} " + f"{r['outstanding_supply']:>13,.1f} {r['target_miner_net']:>12,.1f}") + + +def output(out: Path, rows: list[dict], runs: list[dict], inputs: list[str]) -> list[Path]: + out.mkdir(parents=True, exist_ok=True) + t, c, d, j, z = [out / f for f in ('fee_distribution_summary.txt', + 'fee_distribution_comparison.csv', + 'fee_distribution_per_run.csv', + 'fee_distribution.json', + 'potatium-fee-analysis.zip')] + for dest, content in ((c, rows), (d, runs)): + with dest.open('w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=list(content[0])) + writer.writeheader() + writer.writerows(content) + j.write_text(json.dumps({'inputs': inputs, 'aggregates': rows, 'runs': runs}, + indent=2, ensure_ascii=False) + '\n', encoding='utf-8') + lines = ['POTATIUM | Gebuehrenverteilung | FORSCHUNG, KEINE PROTOKOLLREGEL', + 'Gleiche Mining-Ergebnisse je Szenario; nur das Ziel der Gebuehren wird umgebucht.', + 'Validatoren sind hier EXTERNE Empfaenger und nehmen selbst NICHT am Mining teil.', + 'Fonds-Guthaben ist nicht verbrannt, aber im Modell vorerst nicht ausgabefreigegeben.', + 'Guthaben der Miner / Mining-Strategien bleiben daher bei allen Policies identisch.', + 'Alle Betragswerte in PTM. Float nur fuer Forschungsrechnung.', ''] + groups = defaultdict(list) + for r in rows: + groups[r['group']].append(r) + for ix, (_, group) in enumerate(groups.items(), 1): + first = group[0] + lines.extend([f'SZENARIO {ix}: {first["operators"]} Betreiber (inkl. Neuer), ' + f'andere Worker={first["other_workers"]}, ' + f'Tariffaktor={first["worker_fee_scale"]}, Neue={first["newcomers"]}, ' + f'Tage={first["days"]}, je W {first["seeds"]} Seeds', + 'W Gebuehrenziel Gebuehren Burn Validatoren Fonds Supply gesamt Netto Miner']) + # Only representative worker counts to keep TXT manageable. + workers = sorted({r['target_workers'] for r in group}) + selected = {x for x in (0, 3, 7, 8, 10) if x in workers} + if not selected: selected = set(workers) + for r in group: + if r['target_workers'] in selected: + lines.append(_format_line(r)) + lines.append('') + lines += ['GRENZEN / KEIN BEWEIS FUER VERTEILUNGSGERECHTIGKEIT:', + '* Alle Gebuehren sind bereits in den Simulator-Ergebnissen bezahlt.', + '* Validator-/Fonds-Zufluesse werden nicht als Miningbudget wieder ausgegeben.', + '* Validatoren koennen real auch Miner sein; Rueckfluesse muessen separat simuliert werden.', + '* Administrations-/Treasury-Governance, Transfers, Sybil und reale CPU fehlen.', + '* Der Fonds kann einen grossen Teil der PTM blockieren, obwohl nichts verbrannt wird.', + '* Bei anderen Verwendungszwecken und geaenderten Anreizen muss PoP neu simuliert werden.', + '* Keine verbindliche Coin-/Gebuehrenregel, kein On-chain-Float.'] + t.write_text('\n'.join(lines) + '\n', encoding='utf-8') + with zipfile.ZipFile(z, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=6) as archive: + for f in (t, c, d, j): + archive.write(f, arcname=f.name) + return [t, c, d, j, z] + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--input', type=Path, required=True, help='prior ZIP, JSON file, or directory with pop_cluster_*.json') + parser.add_argument('--out', type=Path, default=Path.home() / 'potatium-fee-analysis') + parser.add_argument('--validators', type=int, default=4, help='separate validator recipients in this accounting test') + parser.add_argument('--custom-worker', type=str, help='percentage triplet burn,validator,treasury, e.g. 0,50,50') + parser.add_argument('--custom-window', type=str, help='percentage triplet; if omitted same as --custom-worker') + args = parser.parse_args() + try: + if args.validators < 1 or args.validators > 100: + raise ValueError('validators must be between 1 and 100') + policies = presets() + if args.custom_worker or args.custom_window: + w = parse_split(args.custom_worker or args.custom_window) + f = parse_split(args.custom_window or args.custom_worker) + policies.append(Policy('custom', w, f)) + sources, files = load_sources(args.input) + runs = [project(source, policy, args.validators) for source in sources for policy in policies] + rows = summarize(runs) + paths = output(args.out, rows, runs, files) + except (ValueError, OSError, zipfile.BadZipFile, KeyError) as ex: + parser.error(str(ex)) + print(f'Gelesen: {len(files)} JSON-Datei(en), {len(sources)} simulierte Szenarien, {len(policies)} Gebührenmodelle.') + for f in paths: + print(f' {f}') + print('Sende mir vorzugsweise potatium-fee-analysis.zip zur Auswertung.') + + +if __name__ == '__main__': + main() diff --git a/scripts/simulate_pop_cluster.py b/scripts/simulate_pop_cluster.py new file mode 100644 index 0000000..022e6a0 --- /dev/null +++ b/scripts/simulate_pop_cluster.py @@ -0,0 +1,556 @@ +#!/usr/bin/env python3 +"""Potatium Hub/Worker PoP Monte-Carlo simulation. + +RESEARCH ONLY; NOT an on-chain/protocol implementation. All PTM amounts and +probabilities are floats solely to compare hypotheses, never for consensus. +No network, no wallet keys, no third-party Python dependencies. + +Simplifications: independent Bernoulli proof success, identical CPUs, always +online, no sybil resistance, single consensus decision per round, no hash-time +or chain latency, no network costs. The rational policy uses a *heuristic*. +""" +from __future__ import annotations + +import argparse +import hashlib +import csv +import json +import math +import random +import statistics +from dataclasses import dataclass, field +from pathlib import Path + +WINDOW_PROFILES = { + "balanced": (0.55, 0.30, 0.20), + "original": (0.70, 0.70, 0.70), + "cautious": (0.50, 0.30, 0.20), +} +WINDOW_FEE_MULTIPLIERS = {"original": 1.0, "mild": 0.1, "low": 0.01} +HISTORY_DAYS = 90 +MAX_WORKERS = 10 # simulation boundary, NOT an approved protocol limit + + +def worker_fee(count: int, model: str = "marginal", scale: float = 1.0) -> float: + """Total fee in PTM for activating count paid IDs for one PoP round. + + 'marginal': per-worker marginal fees (1-3 .001, 4-7 .003, 8-10 .005). + 'tier': one collective tier fee for an entire operator. + """ + if not isinstance(count, int) or not 0 <= count <= MAX_WORKERS: + raise ValueError("worker count must be an integer in range 0..10") + if model not in ("tier", "marginal") or not math.isfinite(scale) or scale < 0: + raise ValueError("invalid worker fee model/scale") + if model == "marginal": + base = (min(count, 3) * .001 + + min(max(count - 3, 0), 4) * .003 + + max(count - 7, 0) * .005) + else: + base = 0.0 if count == 0 else (.001 if count <= 3 else .003 if count <= 7 else .005) + return base * scale + + +def progressive_fee(rewards_90d: float) -> float: + """Exact baseline fee *formula* from v0.2 research, still nonbinding.""" + if not math.isfinite(rewards_90d) or rewards_90d < 0: + raise ValueError("invalid 90d rewards") + return (min(rewards_90d, 10.0) * .00005 + + min(max(rewards_90d - 10.0, 0.0), 90.0) * .0002 + + max(rewards_90d - 100.0, 0.0) * .0005) + + +def window_fee(window: int, rewards_90d: float, model: str) -> float: + if window == 1: + return 0.0 + if model not in WINDOW_FEE_MULTIPLIERS or window not in (2, 3): + raise ValueError("invalid window/model") + progressive = progressive_fee(rewards_90d) * WINDOW_FEE_MULTIPLIERS[model] + return (.001 + progressive) if window == 2 else (.003 + 2 * progressive) + + +def gini(nonnegative: list[float]) -> float: + if any(not math.isfinite(x) or x < -1e-8 for x in nonnegative): + raise ValueError("invalid balances") + values = sorted(max(0., x) for x in nonnegative) + if not values or sum(values) == 0: + return 0.0 + n = len(values) + return 2 * sum((i + 1) * x for i, x in enumerate(values)) / (n * sum(values)) - (n + 1) / n + + +@dataclass +class Operator: + ident: int + target: bool + desired_workers: int + policy: str + join_day: int = 0 + balance: float = 0.0 + recent: float = 0.0 + earned: float = 0.0 + burned_worker: float = 0.0 + burned_windows: float = 0.0 + wins: int = 0 + tickets: int = 0 + active_workers_total: int = 0 + window2: int = 0 + window3: int = 0 + skipped_worker_budget: int = 0 + skipped_window_budget: int = 0 + skipped_window_policy: int = 0 + history: list[float] = field(default_factory=lambda: [0.0] * HISTORY_DAYS) + + def expire_day(self, day: int) -> None: + idx = day % HISTORY_DAYS + self.recent = max(0.0, self.recent - self.history[idx]) + self.history[idx] = 0.0 + + def add_win(self, day: int, amount: float) -> None: + self.balance += amount + self.earned += amount + self.recent += amount + self.history[day % HISTORY_DAYS] += amount + self.wins += 1 + + @property + def total_fees(self) -> float: + return self.burned_worker + self.burned_windows + + +def activate_workers(op: Operator, model: str, scale: float) -> int: + """Pay full activation charge BEFORE draws. Cost is incremental by count. + + A Hub is free. If budget is short, activate the most affordable prefix. + The node/miner never receives a wallet private key in this abstract model. + """ + wanted = op.desired_workers + actual = wanted + while actual > 0 and worker_fee(actual, model, scale) > op.balance + 1e-10: + actual -= 1 + charge = worker_fee(actual, model, scale) + if actual < wanted: + op.skipped_worker_budget += wanted - actual + op.balance -= charge + op.burned_worker += charge + op.active_workers_total += actual + return actual + + +def marginal_share_value(reward: float, own_tickets: int, external_tickets: float) -> float: + """Heuristic additional operator payout if another of its nodes qualifies. + + Given k current operator tickets and m expected external tickets, an + additional ticket increases its payout share by m/((k+m)(k+m+1)). + With k=0, it creates the first operator ticket: 1/(m+1). + """ + if own_tickets < 0 or external_tickets < 0: + raise ValueError("invalid ticket estimate") + if own_tickets == 0: + return reward / (1 + external_tickets) + total = own_tickets + external_tickets + return reward * external_tickets / (total * (total + 1)) + + +def should_buy(op: Operator, window: int, charge: float, profile: tuple[float, float, float], + window_model: str, reward: float, external_ticket_estimate: float, + own_qualified: int) -> bool: + if op.balance + 1e-10 < charge: + op.skipped_window_budget += 1 + return False + if op.policy == "free": + op.skipped_window_policy += 1 + return False + if op.policy == "always": + return True + if op.policy != "rational": + raise ValueError("invalid policy") + # Simplified, deliberately non-optimal economic rule. No future information. + per_ticket_value = marginal_share_value(reward, own_qualified, external_ticket_estimate) + if window == 2: + future_charge = window_fee(3, op.recent, window_model) + future_value = (max(0.0, profile[2] * per_ticket_value - future_charge) + if op.balance - charge + 1e-10 >= future_charge else 0.0) + expected = profile[1] * per_ticket_value + (1 - profile[1]) * future_value + else: + expected = profile[2] * per_ticket_value + if expected > charge + 1e-12: + return True + op.skipped_window_policy += 1 + return False + + +def simulate(*, operators: int = 20, target_workers: int = 3, + others_workers: int = 0, days: int = 180, rounds_per_day: int = 720, + profile_name: str = "balanced", window_model: str = "low", + worker_model: str = "marginal", worker_scale: float = 1.0, + target_policy: str = "rational", others_policy: str = "free", + reward: float = 1.0, seed: int = 20261025, + newcomers: int = 0, join_day: int = 90, + initial_balance: float = 0.0) -> dict: + """Simulate one target operating a free Hub plus W paid mining Workers. + + All nodes independently qualify; exactly one ticket per node maximum. + All PTM, mining income, 90d history and worker/window costs aggregate by operator. + Other operators are full competitors, not a static ticket-rate assumption. + """ + if not (1 <= operators <= 1000 and 0 <= newcomers <= 1000 + and 1 <= days <= 3650 and 1 <= rounds_per_day <= 100000 + and 0 <= join_day and isinstance(seed, int) + and math.isfinite(reward) and reward > 0 + and math.isfinite(initial_balance) and initial_balance >= 0): + raise ValueError("invalid simulation size, reward, seed, or initial balance") + if (not isinstance(target_workers, int) or not 0 <= target_workers <= MAX_WORKERS or + not isinstance(others_workers, int) or not 0 <= others_workers <= MAX_WORKERS): + raise ValueError("worker count outside research range 0..10") + if profile_name not in WINDOW_PROFILES or window_model not in WINDOW_FEE_MULTIPLIERS: + raise ValueError("unknown PoP profile/window fee model") + if worker_model not in ("tier", "marginal") or not math.isfinite(worker_scale) or worker_scale < 0: + raise ValueError("invalid worker fee model/scale") + if target_policy not in ("free", "rational", "always") or others_policy not in ("free", "rational", "always"): + raise ValueError("invalid window policy") + profile = WINDOW_PROFILES[profile_name] + rng = random.Random(seed) + persons = [Operator(i, i == 0, target_workers if i == 0 else others_workers, + target_policy if i == 0 else others_policy, balance=initial_balance) + for i in range(operators)] + persons += [Operator(operators + i, False, others_workers, others_policy, + join_day=join_day, balance=initial_balance) + for i in range(newcomers)] + target = persons[0] + issued = 0.0 + no_ticket = 0 + total_tickets = 0 + daily = [] + + for day in range(days): + living = [op for op in persons if op.join_day <= day] + for op in living: + op.expire_day(day) + day_worker_burn = day_window_burn = day_wins = day_tickets = 0 + for _ in range(rounds_per_day): + # First pay worker activation for *all* operators, then release F1. + nodes: list[Operator] = [] + for op in living: + n = activate_workers(op, worker_model, worker_scale) + nodes.extend([op] * (1 + n)) # free Hub plus paid worker identities + rng.shuffle(nodes) # fair order if fees cannot cover every window + qualified: list[Operator] = [] + failures: list[Operator] = [] + own_tickets: dict[int, int] = {op.ident: 0 for op in living} + ids_per_operator: dict[int, int] = {} + for op in nodes: + ids_per_operator[op.ident] = ids_per_operator.get(op.ident, 0) + 1 + for op in nodes: + if rng.random() < profile[0]: + qualified.append(op) + own_tickets[op.ident] += 1 + else: + failures.append(op) + # F2/F3 charging happens strictly before drawing the challenge result. + # Estimate unknown rival tickets from their active mining IDs. + # This deliberately does not peek at actual rivals' F1 outcomes. + # Their paid-window strategy is omitted from this naive estimate. + for stage in (2, 3): + if not failures: + break + rng.shuffle(failures) + next_failures: list[Operator] = [] + for op in failures: + fee = window_fee(stage, op.recent, window_model) + rivals = len(nodes) - ids_per_operator[op.ident] + if not should_buy(op, stage, fee, profile, window_model, + reward, rivals * profile[0], + own_tickets[op.ident]): + continue # leaves PoP round with no ticket + op.balance -= fee + op.burned_windows += fee + if stage == 2: + op.window2 += 1 + else: + op.window3 += 1 + if rng.random() < profile[stage - 1]: + qualified.append(op) + own_tickets[op.ident] += 1 + elif stage == 2: + next_failures.append(op) + failures = next_failures + for ticket_owner in qualified: + ticket_owner.tickets += 1 + total_tickets += len(qualified) + day_tickets += len(qualified) + if qualified: + winner = rng.choice(qualified) + winner.add_win(day, reward) + issued += reward + day_wins += 1 + else: + no_ticket += 1 + + burned_worker = sum(op.burned_worker for op in persons) + burned_windows = sum(op.burned_windows for op in persons) + burned_total = burned_worker + burned_windows + previous = daily[-1] if daily else None + day_worker_burn = burned_worker - (previous["worker_burn_total"] if previous else 0.0) + day_window_burn = burned_windows - (previous["window_burn_total"] if previous else 0.0) + balances = [op.balance for op in living] + daily.append({ + "day": day + 1, "active_operators": len(living), + "issued_total": round(issued, 6), + "worker_burn_total": round(burned_worker, 6), + "window_burn_total": round(burned_windows, 6), + "burn_total": round(burned_total, 6), + "supply": round(initial_balance * len(persons) + issued - burned_total, 6), + "gini_operator_wallets": round(gini(balances), 6), + "target_wallet": round(target.balance, 6), + "target_90d_wins": round(target.recent, 6), + "rounds_won": day_wins, + "tickets": day_tickets, + "worker_burn_today": round(day_worker_burn, 6), + "window_burn_today": round(day_window_burn, 6), + }) + n_rounds = days * rounds_per_day + burned_worker = sum(op.burned_worker for op in persons) + burned_windows = sum(op.burned_windows for op in persons) + burn = burned_worker + burned_windows + final_balances = [op.balance for op in persons] + conservation = initial_balance * len(persons) + issued - burn + # The daily rounding is presentation only; raw floats are used for checks. + assert all(op.balance >= -1e-7 for op in persons) + assert math.isclose(sum(op.earned for op in persons), issued, rel_tol=1e-9, abs_tol=1e-5) + assert math.isclose(sum(final_balances), conservation, rel_tol=1e-9, abs_tol=1e-5) + assert sum(op.wins for op in persons) + no_ticket == n_rounds + all_mining_ids = sum(1 + op.desired_workers for op in persons) + return { + "parameters": { + "operators": operators, "newcomers": newcomers, "join_day": join_day, + "target_workers": target_workers, "others_workers": others_workers, + "days": days, "rounds_per_day": rounds_per_day, + "profile": profile_name, "success_per_window": profile, + "window_fee_model": window_model, "worker_fee_model": worker_model, + "worker_fee_scale": worker_scale, + "target_policy": target_policy, "others_policy": others_policy, + "reward": reward, "seed": seed, "initial_balance": initial_balance, + }, + "summary": { + "rounds": n_rounds, "no_ticket_rounds": no_ticket, + "minted": round(issued, 6), "burned_workers": round(burned_worker, 6), + "burned_windows": round(burned_windows, 6), + "burned_total": round(burn, 6), "circulating": round(conservation, 6), + "burn_percent_of_minted": round(burn / issued * 100, 4) if issued else 0.0, + "gini_operator_wallets": round(gini(final_balances), 6), + "total_tickets": total_tickets, + "target_wins": target.wins, + "target_win_percent": round(target.wins / n_rounds * 100, 6), + "target_ticket_percent": round(target.tickets / total_tickets * 100, 6) if total_tickets else 0.0, + "target_earned": round(target.earned, 6), + "target_worker_fees": round(target.burned_worker, 6), + "target_window_fees": round(target.burned_windows, 6), + "target_net": round(target.earned - target.total_fees, 6), + "target_wallet": round(target.balance, 6), + "target_recent90": round(target.recent, 6), + "target_avg_active_workers": round(target.active_workers_total / n_rounds, 4), + "target_window2": target.window2, "target_window3": target.window3, + "target_worker_budget_denials": target.skipped_worker_budget, + "target_window_budget_denials": target.skipped_window_budget, + "unused_possible_id_count": all_mining_ids, + }, + "operators": [ + {"id": op.ident, "target": op.target, "join_day": op.join_day, + "workers_requested": op.desired_workers, "policy": op.policy, + "wins": op.wins, "tickets": op.tickets, + "earned": round(op.earned, 6), + "paid_workers": round(op.burned_worker, 6), + "paid_windows": round(op.burned_windows, 6), + "net": round(op.earned - op.total_fees, 6), + "wallet": round(op.balance, 6), + "last90": round(op.recent, 6), + "avg_active_workers": round(op.active_workers_total / n_rounds, 4), + "window2": op.window2, "window3": op.window3, + "skipped_worker_budget": op.skipped_worker_budget, + "skipped_window_budget": op.skipped_window_budget, + "skipped_window_policy": op.skipped_window_policy} + for op in persons + ], + "daily": daily, + "warnings": [ + "RESEARCH ONLY: float is prohibited in consensus state/fee computation.", + "Unlinked owner identities and Sybil attacks not modeled; operator registry assumed valid.", + "Mining proofs are independent Bernoulli outcomes, not real bounded-hash attempts.", + "Windows simulated sequentially per PoP round; timing/network/validator censor absent.", + "Worker activation before F1 is charged even if no worker qualifies.", + "Rational is a naive per-operator marginal-payout heuristic, not a validated optimal strategy.", + "Rational expects rivals' F1 success only, not their paid-window decisions or future actions.", + "No multi-instance-for-one-physical-device detection or wallet transfer attacks.", + "Concentration/Gini refers to registered operators, not real persons.", + "Rewards and block count/round length are illustrative, not protocol parameters.", + ], + } + + +def compare(scenarios: list[dict]) -> list[dict]: + """Return scenario comparison and marginal net gains per additional worker. + + Comparing requested W is not a counterfactual with identical RNG paths. + Pair many seeds to reduce noise. Marginal gain divided by count difference. + """ + rows = [] + ordered = sorted(scenarios, key=lambda r: (r["parameters"]["target_workers"], r["parameters"]["seed"])) + workers = sorted({r["parameters"]["target_workers"] for r in scenarios}) + for w in workers: + ss = [r["summary"] for r in ordered if r["parameters"]["target_workers"] == w] + if not ss: + continue + sample = { + "workers": w, "replicates": len(ss), + "win_pct": statistics.mean(s["target_win_percent"] for s in ss), + "avg_active": statistics.mean(s["target_avg_active_workers"] for s in ss), + "net_ptm": statistics.mean(s["target_net"] for s in ss), + "gross_ptm": statistics.mean(s["target_earned"] for s in ss), + "worker_burn": statistics.mean(s["target_worker_fees"] for s in ss), + "window_burn": statistics.mean(s["target_window_fees"] for s in ss), + "burn_pct": statistics.mean(s["burn_percent_of_minted"] for s in ss), + "gini": statistics.mean(s["gini_operator_wallets"] for s in ss), + "wallet": statistics.mean(s["target_wallet"] for s in ss), + "window2": statistics.mean(s["target_window2"] for s in ss), + "window3": statistics.mean(s["target_window3"] for s in ss), + } + sample["net_range"] = (min(s["target_net"] for s in ss), max(s["target_net"] for s in ss)) + rows.append(sample) + for i, row in enumerate(rows): + row["delta_net_per_added_worker"] = None if not i else ( + (row["net_ptm"] - rows[i-1]["net_ptm"]) / (row["workers"]- rows[i-1]["workers"])) + base = next((r["net_ptm"] for r in rows if r["workers"] == 0), None) + for row in rows: + row["net_gain_vs_hub"] = None if base is None else row["net_ptm"] - base + return rows + + +def write_results(out: Path, scenarios: list[dict], rows: list[dict], tag: str) -> list[Path]: + out.mkdir(parents=True, exist_ok=True) + stem = "pop_cluster_" + tag + paths = [out / (stem + ext) for ext in (".txt", ".json", "_comparison.csv", "_daily.csv", "_operators.csv")] + t, j, c, d, o = paths + j.write_text(json.dumps({"comparison": rows, "scenarios": scenarios}, indent=2, + ensure_ascii=False) + "\n", encoding="utf-8") + with c.open("w", newline="", encoding="utf-8") as handle: + writer = csv.DictWriter(handle, fieldnames=[key for key in rows[0] if key != "net_range"]) + writer.writeheader() + writer.writerows({k: val for k, val in row.items() if k != "net_range"} for row in rows) + with d.open("w", newline="", encoding="utf-8") as handle: + fields = ["workers", "seed"] + list(scenarios[0]["daily"][0].keys()) + writer = csv.DictWriter(handle, fieldnames=fields) + writer.writeheader() + for r in scenarios: + for day in r["daily"]: + writer.writerow({"workers": r["parameters"]["target_workers"], + "seed": r["parameters"]["seed"], **day}) + with o.open("w", newline="", encoding="utf-8") as handle: + fields = ["scenario_workers", "seed"] + list(scenarios[0]["operators"][0].keys()) + writer = csv.DictWriter(handle, fieldnames=fields) + writer.writeheader() + for r in scenarios: + for op in r["operators"]: + writer.writerow({"scenario_workers": r["parameters"]["target_workers"], + "seed": r["parameters"]["seed"], **op}) + p = scenarios[0]["parameters"] + lines = [ + "POTATIUM | Hub + Worker + 3 Fenster | FORSCHUNG, KEINE PROTOKOLLREGEL", + f"Operatoren={p['operators']} (+{p['newcomers']} ab Tag {p['join_day']}) | Tage={p['days']} | Runden/Tag={p['rounds_per_day']}", + f"PoP={p['profile']} {p['success_per_window']} | Fenster={p['window_fee_model']} | Worker={p['worker_fee_model']} x{p['worker_fee_scale']:g}", + f"Strategie Ziel={p['target_policy']} Andere={p['others_policy']} (je {p['others_workers']} Worker) | Reward={p['reward']:g} PTM", + f"Seeds: {', '.join(str(s) for s in sorted({r['parameters']['seed'] for r in scenarios}))}", + "", " W Ø aktive Gewinn% Brutto PTM Netto PTM Worker-Geb. Fenster-Geb. ΔNetto/Worker Gini", + ] + for r in rows: + delta = " n/a" if r["delta_net_per_added_worker"] is None else f"{r['delta_net_per_added_worker']:>13.4f}" + lines.append(f"{r['workers']:>2} {r['avg_active']:>9.2f} {r['win_pct']:>8.3f} " + f"{r['gross_ptm']:>11.2f} {r['net_ptm']:>11.2f} " + f"{r['worker_burn']:>12.2f} {r['window_burn']:>12.2f} {delta:>14} {r['gini']:>5.3f}") + lines.extend(["", "WARNUNGEN:"] + ["- " + w for w in scenarios[0]["warnings"]]) + lines.append("- Monte-Carlo-Rauschen: Δ Netto ist bei wenigen Seeds KEINE sichere Grenzkosten-Schwelle.") + t.write_text("\n".join(lines) + "\n", encoding="utf-8") + print("\n".join(lines)) + return paths + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--operators", type=int, default=20, help="one target + rest competing operators") + parser.add_argument("--workers", type=int, nargs="+", default=[0, 1, 2, 3, 5, 8, 10], + help="requested target Workers to compare, Hub is additionally free") + parser.add_argument("--others-workers", type=int, default=0) + parser.add_argument("--days", type=int, default=180) + parser.add_argument("--rounds-per-day", type=int, default=720) + parser.add_argument("--seed", type=int, default=20261025) + parser.add_argument("--replicates", type=int, default=1, help="number of seeds, e.g. 3") + parser.add_argument("--profile", choices=WINDOW_PROFILES, default="balanced") + parser.add_argument("--window-fee-model", choices=WINDOW_FEE_MULTIPLIERS, default="low") + parser.add_argument("--worker-fee-model", choices=["tier", "marginal"], default="marginal") + parser.add_argument("--worker-fee-scale", type=float, default=1.0) + parser.add_argument("--target-policy", choices=["free", "rational", "always"], default="rational") + parser.add_argument("--others-policy", choices=["free", "rational", "always"], default="free") + parser.add_argument("--newcomers", type=int, default=0) + parser.add_argument("--join-day", type=int, default=90) + parser.add_argument("--initial-balance", type=float, default=0.0) + parser.add_argument("--reward", type=float, default=1.0) + parser.add_argument("--out", type=Path, default=Path.home() / "potatium-simulation") + a = parser.parse_args() + if a.replicates < 1 or a.replicates > 100 or not a.workers or len(set(a.workers)) != len(a.workers): + parser.error("replicates must be 1..100 and --workers must contain unique values") + if a.operators < 2: + parser.error("require at least two competing operators") + try: + # Catch invalid arguments before running the potentially long sweep. + worker_fee(max(a.workers), a.worker_fee_model, a.worker_fee_scale) + if min(a.workers) < 0: + raise ValueError("worker count negative") + worker_fee(a.others_workers, a.worker_fee_model, a.worker_fee_scale) + if a.newcomers and a.join_day >= a.days: + raise ValueError("newcomers must join within simulation horizon") + if a.operators + a.newcomers > 300: + raise ValueError("max 300 operators in this research script") + configs = [] + for worker_count in sorted(a.workers): + for ix in range(a.replicates): + configs.append((worker_count, a.seed + ix)) + scenarios = [] + for index, (w, seed) in enumerate(configs, 1): + print(f"[{index}/{len(configs)}] Worker={w}, Seed={seed} ...", flush=True) + scenarios.append(simulate(operators=a.operators, target_workers=w, + others_workers=a.others_workers, days=a.days, + rounds_per_day=a.rounds_per_day, + profile_name=a.profile, + window_model=a.window_fee_model, + worker_model=a.worker_fee_model, + worker_scale=a.worker_fee_scale, + target_policy=a.target_policy, + others_policy=a.others_policy, + reward=a.reward, seed=seed, + newcomers=a.newcomers, join_day=a.join_day, + initial_balance=a.initial_balance)) + rows = compare(scenarios) + fingerprint_parameters = { + "operators": a.operators, "workers": sorted(a.workers), + "others_workers": a.others_workers, "days": a.days, + "rounds_per_day": a.rounds_per_day, "seed": a.seed, + "replicates": a.replicates, "profile": a.profile, + "window_fee_model": a.window_fee_model, + "worker_fee_model": a.worker_fee_model, "worker_fee_scale": a.worker_fee_scale, + "target_policy": a.target_policy, "others_policy": a.others_policy, + "newcomers": a.newcomers, "join_day": a.join_day, + "initial_balance": a.initial_balance, "reward": a.reward, + } + suffix = hashlib.sha256(json.dumps(fingerprint_parameters, sort_keys=True).encode()).hexdigest()[:10] + tag = (f"{a.operators}o_{a.days}d_{a.profile}_{a.window_fee_model}_" + f"{a.worker_fee_model}_{a.target_policy}_{a.seed}_{suffix}") + paths = write_results(a.out, scenarios, rows, tag) + except (ValueError, OverflowError) as error: + parser.error(str(error)) + print("\nErgebnisdateien:") + for path in paths: + print(" " + str(path)) + print("\nBitte TXT und Vergleichs-CSV hier hochladen; fuer Tiefenanalyse auch Tages-CSV.") + + +if __name__ == "__main__": + main() diff --git a/scripts/simulate_pop_dynamic.py b/scripts/simulate_pop_dynamic.py new file mode 100644 index 0000000..3cf6b7f --- /dev/null +++ b/scripts/simulate_pop_dynamic.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python3 +"""Potatium: dynamic worker activation, three PoP windows, treasury circulation. + +Pure Monte-Carlo RESEARCH. No on-chain code, cryptographic PoW, personal keys, +commercial price assumptions, or final protocol parameters. +Uses only Python standard library; floats are NOT acceptable for consensus. +""" +from __future__ import annotations + +import argparse +import csv +import hashlib +import json +import math +import random +import statistics +import zipfile +from dataclasses import dataclass, field +from pathlib import Path + +F1_F2_F3 = {"balanced": (.55, .30, .20), "cautious": (.50, .30, .20), "original": (.70, .70, .70)} +WINDOW_SCALES = {"low": .01, "mild": .1, "original": 1.} +MAX_WORKERS = 10 # Research range, NOT a protocol cap +HISTORY_DAYS = 90 +EPS = 1e-9 + + +def progressive_fee(rewards_90d: float) -> float: + if not math.isfinite(rewards_90d) or rewards_90d < 0: + raise ValueError("invalid rewards_90d") + return (min(rewards_90d, 10) * .00005 + + min(max(rewards_90d - 10, 0), 90) * .0002 + + max(rewards_90d - 100, 0) * .0005) + + +def window_fee(stage: int, rewards_90d: float, model: str = "low") -> float: + if stage not in (2, 3) or model not in WINDOW_SCALES: + raise ValueError("invalid PoP stage or fee model") + p = progressive_fee(rewards_90d) * WINDOW_SCALES[model] + return (.001 + p) if stage == 2 else (.003 + 2 * p) + + +def worker_fee(count: int, scale: float) -> float: + if not isinstance(count, int) or not 0 <= count <= MAX_WORKERS or not math.isfinite(scale) or scale < 0: + raise ValueError("invalid worker count or fee scale") + base = min(count, 3) * .001 + min(max(count - 3, 0), 4) * .003 + max(count - 7, 0) * .005 + return scale * base + + +def gini(balances: list[float]) -> float: + if any(not math.isfinite(x) or x < -EPS for x in balances): + raise ValueError("invalid balance") + sorted_bal = sorted(max(0., b) for b in balances) + total = sum(sorted_bal) + if total <= 0: + return 0. + return (2 * sum((i+1)*v for i,v in enumerate(sorted_bal)) / (len(sorted_bal)*total) + - (len(sorted_bal)+1)/len(sorted_bal)) + + +@dataclass +class Operator: + id: int + capacity: int + join_day: int + balance: float + recent: float = 0. + earned: float = 0. + worker_paid: float = 0. + window_paid: float = 0. + rebated: float = 0. + wins: int = 0 + tickets: int = 0 + activation_total: int = 0 + activation_rounds: int = 0 + paid_f2: int = 0 + paid_f3: int = 0 + refused_budget: int = 0 + history: list[float] = field(default_factory=lambda: [0.]*HISTORY_DAYS) + + def expire(self, day: int) -> None: + idx = day % HISTORY_DAYS + self.recent -= self.history[idx] + self.history[idx] = 0. + if abs(self.recent) < EPS: + self.recent = 0. + + def reward(self, day: int, amount: float) -> None: + self.earned += amount + self.balance += amount + self.wins += 1 + self.history[day % HISTORY_DAYS] += amount + self.recent += amount + + @property + def fees(self) -> float: + return self.worker_paid + self.window_paid + + +def marginal_ticket_gain(own: int, rival_est: float, reward: float) -> float: + if own < 0 or rival_est < 0: + raise ValueError("ticket forecast invalid") + if own == 0: + return reward / (1 + rival_est) + total = own + rival_est + return reward * rival_est / (total * (total + 1)) + + +def choose_worker_count(*, balance: float, capacity: int, mode: str, scale: float, + rivals_estimated_tickets: float, f1_chance: float, + reward: float, worker_choice_margin: float = 1.0) -> int: + """Heuristic worker choice BEFORE current mining proof/challenge. + + 'fixed' requests all capacity (budget may reduce it). + 'dynamic' maximizes [estimated ticket share * reward - worker fee] + against all affordable capacities. Only F1 chance informs forecast, hence + a deliberately imperfect/myopic policy; it cannot observe live rival proofs. + """ + if mode not in ("fixed", "dynamic") or capacity < 0 or capacity > MAX_WORKERS: + raise ValueError("invalid worker choice") + if balance < -EPS or not 0 < f1_chance <= 1 or reward <= 0 or worker_choice_margin <= 0: + raise ValueError("invalid worker choice parameter") + affordable = [n for n in range(capacity+1) if worker_fee(n, scale) <= balance+EPS] + if mode == "fixed": + return affordable[-1] + def net(n: int) -> float: + own = (n + 1) * f1_chance + return worker_choice_margin * reward * own / (own + max(rivals_estimated_tickets, EPS)) - worker_fee(n, scale) + # tie-break toward fewer workers + return max(affordable, key=lambda n: (net(n), -n)) + + +def should_buy_window(*, policy: str, op_balance: float, charge: float, stage: int, + own_tickets: int, rival_estimate: float, reward: float, + probabilities: tuple[float,float,float], future_charge: float = 0.) -> bool: + if op_balance + EPS < charge or policy == "free": + return False + if policy == "always": + return True + if policy != "rational": + raise ValueError("unknown window policy") + value = marginal_ticket_gain(own_tickets, rival_estimate, reward) + if stage == 2: + future = max(0., probabilities[2] * value - future_charge) if op_balance + EPS >= charge + future_charge else 0. + expected = probabilities[1] * value + (1 - probabilities[1]) * future + elif stage == 3: + expected = probabilities[2] * value + else: + raise ValueError("invalid stage") + return expected > charge + 1e-12 + + +def normalize_capacities(operator_count: int, distribution: list[int], newcomers: int) -> list[int]: + if not distribution or any(not isinstance(v, int) or v < 0 or v > MAX_WORKERS for v in distribution): + raise ValueError("worker distribution requires integers 0..10") + return [distribution[i % len(distribution)] for i in range(operator_count + newcomers)] + + +def simulate(*, operators: int = 20, newcomers: int = 5, join_day: int = 90, + capacities: tuple[int,...] = (0,1,3,5,8,10), + days: int = 180, rounds_per_day: int = 72, seed: int = 20261025, + worker_mode: str = "dynamic", worker_scale: float = 3., + fund_mode: str = "hold", payout_share: float = .25, payout_interval: int = 30, + window_policy: str = "rational", window_model: str = "low", + profile: str = "balanced", reward: float = 1., + initial_balance: float = 0.) -> dict: + """Run a full PoP lottery with conservation-checked on-chain-equivalent fund ledger. + + Genesis allocations initial_balance are allocated to *all* operators, + including future joiners, from day 0; no external minting upon joining. + Fund refunds use equal active-operator allocation, not an endorsed policy. + """ + if not (2 <= operators <= 300 and 0 <= newcomers <= 300 and operators + newcomers <= 300 + and days >= 1 and rounds_per_day >= 1 and 0 <= join_day and (newcomers == 0 or join_day < days) + and isinstance(seed, int) and math.isfinite(initial_balance) and initial_balance >= 0 + and math.isfinite(reward) and reward > 0 + and worker_mode in ("fixed", "dynamic") and fund_mode in ("hold", "redistribute") + and window_policy in ("free", "rational", "always") + and profile in F1_F2_F3 and window_model in WINDOW_SCALES + and 0 <= payout_share <= 1 and payout_interval >= 1): + raise ValueError("invalid simulation parameters") + # Check every capacity and scale early. + caps = normalize_capacities(operators, list(capacities), newcomers) + worker_fee(max(caps), worker_scale) + probabilities = F1_F2_F3[profile] + rng = random.Random(seed) + persons = [Operator(i, caps[i], 0 if i < operators else join_day, initial_balance) + for i in range(operators + newcomers)] + fund = 0. + fund_in = fund_out = minted = 0. + no_ticket_rounds = 0 + total_tickets = 0 + daily = [] + # Prior-round info only. Default forecast: all rivals' hubs qualify at F1. + last_rival_tickets = {op.id: (operators-1) * probabilities[0] for op in persons} + for day in range(days): + living = [op for op in persons if op.join_day <= day] + for op in living: + op.expire(day) + day_wins = 0 + day_activations = 0 + for _ in range(rounds_per_day): + nodes: list[Operator] = [] + current_counts: dict[int,int] = {} + # All operator decisions occur before F1 challenge, no peeking at outcomes. + for op in living: + chosen = choose_worker_count( + balance=op.balance, capacity=op.capacity, mode=worker_mode, + scale=worker_scale, rivals_estimated_tickets=last_rival_tickets[op.id], + f1_chance=probabilities[0], reward=reward) + if chosen < op.capacity: + op.refused_budget += int(worker_fee(chosen+1, worker_scale) > op.balance + EPS) + fee = worker_fee(chosen, worker_scale) + if fee > 0: + op.balance -= fee + op.worker_paid += fee + fund += fee + fund_in += fee + op.activation_total += chosen + op.activation_rounds += 1 + day_activations += chosen + current_counts[op.id] = chosen + 1 + nodes.extend([op]*(chosen + 1)) + # Each node ID is represented exactly once per round. + rng.shuffle(nodes) + qualified: list[Operator] = [] + failures: list[Operator] = [] + tickets_by_owner = {op.id: 0 for op in living} + for node_owner in nodes: + if rng.random() < probabilities[0]: + qualified.append(node_owner) + tickets_by_owner[node_owner.id] += 1 + else: + failures.append(node_owner) + for stage in (2,3): + if not failures: + break + rng.shuffle(failures) + again = [] + # Current rival outcomes remain unknown, only previous-round forecast used. + for op in failures: + cost = window_fee(stage, op.recent, window_model) + prospective_f3 = window_fee(3, op.recent, window_model) if stage == 2 else 0. + if not should_buy_window( + policy=window_policy, op_balance=op.balance, charge=cost, + stage=stage, own_tickets=tickets_by_owner[op.id], + rival_estimate=last_rival_tickets[op.id], reward=reward, + probabilities=probabilities, future_charge=prospective_f3): + continue + op.balance -= cost + op.window_paid += cost + fund += cost + fund_in += cost + if stage == 2: + op.paid_f2 += 1 + else: + op.paid_f3 += 1 + if rng.random() < probabilities[stage-1]: + qualified.append(op) + tickets_by_owner[op.id] += 1 + elif stage == 2: + again.append(op) + failures = again + for op in qualified: + op.tickets += 1 + total_tickets += len(qualified) + if qualified: + winner = rng.choice(qualified) + winner.reward(day, reward) + minted += reward + day_wins += 1 + else: + no_ticket_rounds += 1 + # Update forecasts solely from number of *actual tickets in past round*. + total = len(qualified) + for op in living: + rivals_now = total - tickets_by_owner[op.id] + last_rival_tickets[op.id] = .95*last_rival_tickets[op.id] + .05*rivals_now + payout_today = 0. + if fund_mode == "redistribute" and (day+1) % payout_interval == 0: + payout_today = fund * payout_share + payout_per_operator = payout_today / len(living) + for op in living: + op.balance += payout_per_operator + op.rebated += payout_per_operator + fund -= payout_today + fund_out += payout_today + balances = [op.balance for op in living] + sum_wallets = sum(op.balance for op in persons) + expected_supply = initial_balance*len(persons) + minted + if not math.isclose(sum_wallets + fund, expected_supply, rel_tol=1e-9, abs_tol=1e-5): + raise AssertionError("SUPPLY CONSERVATION FAILED") + if any(op.balance < -EPS for op in persons) or fund < -EPS: + raise AssertionError("negative wallet or fund") + daily.append({ + "day": day+1, "active_operators": len(living), + "minted": round(minted, 6), "fund": round(fund, 6), + "fund_in": round(fund_in, 6), "fund_out": round(fund_out, 6), + "fund_payout_today": round(payout_today, 6), + "wallet_total": round(sum_wallets, 6), + "fund_percent_of_supply": round(100*fund/expected_supply, 5) if expected_supply else 0., + "gini_active_wallets": round(gini(balances), 6), + "active_workers_per_round": round(day_activations / rounds_per_day, 4), + "wins_today": day_wins, + }) + nrounds = days*rounds_per_day + fund_paid_worker = sum(op.worker_paid for op in persons) + fund_paid_window = sum(op.window_paid for op in persons) + assert sum(op.wins for op in persons) + no_ticket_rounds == nrounds + assert math.isclose(fund_paid_worker+fund_paid_window, fund_in, rel_tol=1e-8, abs_tol=1e-5) + assert math.isclose(fund_in - fund_out, fund, rel_tol=1e-8, abs_tol=1e-5) + assert math.isclose(sum(op.rebated for op in persons), fund_out, rel_tol=1e-8, abs_tol=1e-5) + assert math.isclose(sum(op.earned for op in persons), minted, rel_tol=1e-8, abs_tol=1e-5) + supply = initial_balance*len(persons) + minted + rows = [] + for op in persons: + active_rounds = max(0, days-op.join_day) * rounds_per_day + rows.append({ + "id": op.id, "capacity": op.capacity, "join_day": op.join_day, + "active_rounds": active_rounds, + "avg_workers_active": round(op.activation_total/active_rounds, 5) if active_rounds else 0., + "wins": op.wins, "tickets": op.tickets, + "earned": round(op.earned, 5), + "paid_worker": round(op.worker_paid, 5), + "paid_window": round(op.window_paid, 5), + "fund_received": round(op.rebated, 5), + "mining_net": round(op.earned-op.fees, 5), + "wallet": round(op.balance, 5), + "paid_f2": op.paid_f2, "paid_f3": op.paid_f3, + "budget_restrictions": op.refused_budget, + "recent90": round(op.recent, 5) + }) + return { + "params": {"operators": operators, "newcomers": newcomers, "join_day": join_day, + "capacities": list(capacities), "days": days, "rounds_per_day": rounds_per_day, + "seed": seed, "worker_mode": worker_mode, "worker_scale": worker_scale, + "fund_mode": fund_mode, "payout_share": payout_share, + "payout_interval": payout_interval, + "window_policy": window_policy, "window_model": window_model, + "profile": profile, "probabilities": list(probabilities), + "reward": reward, "initial_balance": initial_balance}, + "summary": { + "rounds": nrounds, "minted": round(minted, 5), + "genesis_supply": initial_balance*len(persons), + "total_supply": round(supply, 5), "burned": 0., + "fund_balance": round(fund, 5), + "fund_received_fees": round(fund_in, 5), + "fund_distributed": round(fund_out, 5), + "fund_paid_worker": round(fund_paid_worker, 5), + "fund_paid_window": round(fund_paid_window, 5), + "fund_pct_supply": round(100*fund/supply, 6) if supply else 0., + "total_wallets": round(sum(op.balance for op in persons), 5), + "gini_operator_wallets": round(gini([op.balance for op in persons]), 6), + "no_ticket_rounds": no_ticket_rounds, + "ticket_count": total_tickets, + "total_avg_workers": round(sum(op.activation_total for op in persons)/nrounds, 4), + "total_wins_newcomers": sum(op.wins for op in persons if op.join_day > 0), + "total_fund_to_newcomers": round(sum(op.rebated for op in persons if op.join_day > 0), 5), + "total_fund_to_old": round(sum(op.rebated for op in persons if op.join_day == 0), 5), + }, "operators": rows, "daily": daily, + "limitations": [ + "RESEARCH ONLY: no PoW, signatures, block times, network outages, adversarial miners, exchange prices or actual hardware.", + "One operator per registered identity is assumed; no Sybil resistance or proof of physical hardware.", + "All operators have one free Hub; capacities are the number of additional paid Workers, not extra free Hubs.", + "Dynamic workers use an imperfect myopic F1-only expected-share estimate and a past-round EMA of rival tickets.", + "Window strategy is an approximation, not an optimal game-theoretic solution.", + "Redistribute allocates every recipient an EQUAL share, including the miner, and is NOT proposed governance or treasury policy.", + "Future joiners receive their configurable genesis balance at genesis; only begin competing and receiving payouts upon joining.", + "Fund is an internal ledger with conservation checks; no real coins or wallet keys, and no consensus code.", + "72 or 720 example mining rounds/day and 1 PTM per winning round are not decided protocol parameters.", + "Float amounts are valid for research only, never consensus.", + ]} + + +def aggregate(results: list[dict]) -> list[dict]: + grouped = {} + for r in results: + p = r["params"] + key = (p["worker_scale"], p["worker_mode"], p["fund_mode"]) + grouped.setdefault(key, []).append(r) + table = [] + for (scale, worker_mode, fund_mode), runs in sorted(grouped.items()): + def mean(key: str) -> float: + return statistics.mean(r["summary"][key] for r in runs) + rows = [op for r in runs for op in r["operators"]] + cap_stats = {} + for cap in sorted({op["capacity"] for op in rows}): + records = [op for op in rows if op["capacity"] == cap and op["join_day"] == 0] + if records: + cap_stats[f"capacity_{cap}_net"] = round(statistics.mean(op["mining_net"] for op in records),4) + cap_stats[f"capacity_{cap}_wallet"] = round(statistics.mean(op["wallet"] for op in records),4) + cap_stats[f"capacity_{cap}_gross"] = round(statistics.mean(op["earned"] for op in records),4) + cap_stats[f"capacity_{cap}_rebate"] = round(statistics.mean(op["fund_received"] for op in records),4) + cap_stats[f"capacity_{cap}_activated"] = round(statistics.mean(op["avg_workers_active"] for op in records),4) + newcomer_rows = [op for op in rows if op["join_day"] > 0] + incumbent_rows = [op for op in rows if op["join_day"] == 0] + def net_per_active_day(records: list[dict]) -> float: + return statistics.mean(op["mining_net"] / (op["active_rounds"] / runs[0]["params"]["rounds_per_day"]) + for op in records if op["active_rounds"] > 0) if records else 0. + summary = {"worker_scale": scale, "worker_mode": worker_mode, + "fund_mode": fund_mode, "seeds": len(runs), + "fund_balance": round(mean("fund_balance"), 4), + "fund_pct_supply": round(mean("fund_pct_supply"), 4), + "fund_distributed": round(mean("fund_distributed"), 4), + "fund_worker_in": round(mean("fund_paid_worker"), 4), + "fund_window_in": round(mean("fund_paid_window"), 4), + "minted": round(mean("minted"), 4), + "gini_wallets": round(mean("gini_operator_wallets"), 5), + "avg_workers_total": round(mean("total_avg_workers"), 4), + "newcomer_wins": round(mean("total_wins_newcomers"), 3), + "newcomer_net_daily": round(net_per_active_day(newcomer_rows), 4), + "incumbent_net_daily": round(net_per_active_day(incumbent_rows), 4)} + summary.update(cap_stats) + table.append(summary) + return table + + +def write_output(folder: Path, results: list[dict], table: list[dict], name: str) -> list[Path]: + folder.mkdir(parents=True, exist_ok=True) + files = [folder / (name + x) for x in ("_summary.txt", "_comparison.csv", "_operators.csv", "_daily.csv", "_results.json")] + txt, comp, ops, days, js = files + comp_cols = list(dict.fromkeys(key for r in table for key in r)) + with comp.open("w", newline="", encoding="utf-8") as fh: + wr = csv.DictWriter(fh, fieldnames=comp_cols) + wr.writeheader() + wr.writerows(table) + with ops.open("w", newline="", encoding="utf-8") as fh: + cols = ["worker_scale", "worker_mode", "fund_mode", "seed"] + list(results[0]["operators"][0]) + wr = csv.DictWriter(fh, fieldnames=cols) + wr.writeheader() + for r in results: + for op in r["operators"]: + wr.writerow({"worker_scale": r["params"]["worker_scale"], + "worker_mode": r["params"]["worker_mode"], + "fund_mode": r["params"]["fund_mode"], + "seed": r["params"]["seed"], **op}) + with days.open("w", newline="", encoding="utf-8") as fh: + cols = ["worker_scale", "worker_mode", "fund_mode", "seed"] + list(results[0]["daily"][0]) + wr = csv.DictWriter(fh, fieldnames=cols) + wr.writeheader() + for r in results: + for d in r["daily"]: + wr.writerow({"worker_scale": r["params"]["worker_scale"], + "worker_mode": r["params"]["worker_mode"], + "fund_mode": r["params"]["fund_mode"], + "seed": r["params"]["seed"], **d}) + js.write_text(json.dumps({"scenario_count": len(results), "comparisons": table, "runs": results}, + ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + header = ["POTATIUM | Dynamische Worker / Gebühren / Fonds | FORSCHUNG, KEINE PROTOKOLLREGEL", + f"Simulationen: {len(results)} | Seeds pro Konfiguration: {table[0]['seeds']}", + "Worker-Fee-Skala Modus Fonds Ø Fonds-PTM Ø Fonds-% Ø Ausgaben Gini Ø aktive Worker"] + for r in table: + header.append(f"{r['worker_scale']:>6}x {r['worker_mode']:<8} {r['fund_mode']:<12} " + f"{r['fund_balance']:>10.2f} {r['fund_pct_supply']:>7.2f}% " + f"{r['fund_distributed']:>11.2f} {r['gini_wallets']:>5.3f} " + f"{r['avg_workers_total']:>10.2f}") + header += ["", "WARNUNGEN:"] + ["- " + s for s in results[0]["limitations"]] + txt.write_text("\n".join(header)+"\n", encoding="utf-8") + print("\n".join(header)) + return files + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) + ap.add_argument("--operators", type=int, default=20) + ap.add_argument("--newcomers", type=int, default=5) + ap.add_argument("--join-day", type=int, default=90) + ap.add_argument("--capacities", nargs="+", type=int, default=[0,1,3,5,8,10], help="repeating operator worker-capacity distribution") + ap.add_argument("--days", type=int, default=180) + ap.add_argument("--rounds-per-day", type=int, default=72, help="720 for full earlier research cadence; 72 for quick study") + ap.add_argument("--seed", type=int, default=20261025) + ap.add_argument("--replicates", type=int, default=3) + ap.add_argument("--scales", nargs="+", type=float, default=[1.,3.,5.]) + ap.add_argument("--worker-modes", nargs="+", choices=["fixed", "dynamic"], default=["fixed", "dynamic"]) + ap.add_argument("--fund-modes", nargs="+", choices=["hold", "redistribute"], default=["hold", "redistribute"]) + ap.add_argument("--payout-share", type=float, default=.25) + ap.add_argument("--payout-interval", type=int, default=30) + ap.add_argument("--window-policy", choices=["free", "rational", "always"], default="rational") + ap.add_argument("--window-fee-model", choices=WINDOW_SCALES, default="low") + ap.add_argument("--profile", choices=F1_F2_F3, default="balanced") + ap.add_argument("--reward", type=float, default=1.) + ap.add_argument("--initial-balance", type=float, default=0.) + ap.add_argument("--out", type=Path, default=Path.home()/"potatium-dynamic-simulation") + args = ap.parse_args() + if not (1 <= args.replicates <= 30 and args.scales and len(set(args.scales)) == len(args.scales) + and 1 <= len(args.scales) <= 10 and len(args.worker_modes) == len(set(args.worker_modes)) + and len(args.fund_modes) == len(set(args.fund_modes))): + ap.error("replicates 1..30; unique nonempty scales/modes") + if any(not math.isfinite(s) or s < 0 for s in args.scales): + ap.error("scales must be finite and nonnegative") + if args.newcomers and args.join_day >= args.days: + ap.error("newcomers must arrive within simulation days") + if not 0 <= args.payout_share <= 1 or args.payout_interval <= 0: + ap.error("invalid payout configuration") + if any(c < 0 or c > MAX_WORKERS for c in args.capacities): + ap.error("capacities outside 0..10") + results = [] + configs = [(scale, mode, fund, args.seed+i) for scale in args.scales + for mode in args.worker_modes for fund in args.fund_modes + for i in range(args.replicates)] + for i, (scale, mode, fund, seed) in enumerate(configs, 1): + print(f"[{i}/{len(configs)}] workers={mode}, fee={scale}x, fund={fund}, seed={seed}", flush=True) + results.append(simulate( + operators=args.operators, newcomers=args.newcomers, join_day=args.join_day, + capacities=tuple(args.capacities), days=args.days, rounds_per_day=args.rounds_per_day, + seed=seed, worker_mode=mode, worker_scale=scale, fund_mode=fund, + payout_share=args.payout_share, payout_interval=args.payout_interval, + window_policy=args.window_policy, window_model=args.window_fee_model, + profile=args.profile, reward=args.reward, initial_balance=args.initial_balance)) + comparisons = aggregate(results) + tag_dict = {"operators": args.operators,"newcomers":args.newcomers,"join_day":args.join_day, + "capacities":args.capacities,"days":args.days,"rounds":args.rounds_per_day, + "seed":args.seed,"replicates":args.replicates,"scales":args.scales, + "worker_modes":args.worker_modes,"fund_modes":args.fund_modes, + "payout_share":args.payout_share,"payout_interval":args.payout_interval, + "policy":args.window_policy,"window_fee":args.window_fee_model, + "profile":args.profile,"initial_balance":args.initial_balance,"reward":args.reward} + tag = hashlib.sha256(json.dumps(tag_dict,sort_keys=True).encode()).hexdigest()[:10] + stem = f"potatium_dynamic_{args.operators}o_{args.days}d_{args.rounds_per_day}r_{tag}" + produced = write_output(args.out, results, comparisons, stem) + archive = args.out / (stem + "_ERGEBNISSE.zip") + with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED, compresslevel=7) as z: + for path in produced: + z.write(path, arcname=path.name) + with zipfile.ZipFile(archive) as z: + if z.testzip() is not None: + raise RuntimeError("Ergebnisarchiv ist beschaedigt") + print("\nDateien:\n" + "\n".join(str(x) for x in produced)) + print("\nZIP fuer Auswertung: " + str(archive)) + +if __name__ == "__main__": + main() diff --git a/scripts/simulate_pop_longterm.py b/scripts/simulate_pop_longterm.py new file mode 100644 index 0000000..323c10d --- /dev/null +++ b/scripts/simulate_pop_longterm.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +"""Potatium PoP Langzeitsimulation. Forschungsmodell, KEINE Konsensregeln. + +Ein Mineur = eine freigegebene Identitaet. Hier keine Sybil-Angriffe, +keine echten Hashzeiten oder Validator-Zensur. Float nur OFF-CHAIN. +""" +from __future__ import annotations + +import argparse +import csv +import json +import math +import random +import statistics +from dataclasses import dataclass, asdict +from pathlib import Path + + +PROFILES = { + "original": (0.70, 0.70, 0.70), + "balanced": (0.55, 0.30, 0.20), + "cautious": (0.50, 0.30, 0.20), +} +# Unveraenderte Beispieltarife aus v0.2; nur Progression skaliert. +FEE_MODELS = {"original": 1.0, "mild": 0.1, "low": 0.01} +HISTORY_DAYS = 90 + + +def progressive_fee(rewards_90d: float) -> float: + if rewards_90d < 0: + raise ValueError("90-Tage-Ertrag darf nicht negativ sein") + return (min(rewards_90d, 10.0) * 0.00005 + + min(max(rewards_90d - 10.0, 0.0), 90.0) * 0.0002 + + max(rewards_90d - 100.0, 0.0) * 0.0005) + + +def fee_for_window(window: int, rewards_90d: float, fee_model: str) -> float: + if fee_model not in FEE_MODELS: + raise ValueError("Unbekanntes Gebuehrenmodell") + if window == 1: + return 0.0 + p = progressive_fee(rewards_90d) * FEE_MODELS[fee_model] + if window == 2: + return 0.001 + p + if window == 3: + return 0.003 + 2 * p + raise ValueError("Nur Fenster 1 bis 3 erlaubt") + + +def gini(values: list[float]) -> float: + values = sorted(values) + if not values or sum(values) <= 0: + return 0.0 + n = len(values) + return (2.0 * sum((i + 1) * v for i, v in enumerate(values)) / (n * sum(values))) - (n + 1) / n + + +def policy_for(miner_id: int, strategy: str) -> str: + if strategy != "mixed": + return strategy + return ("free", "rational", "rational", "always")[miner_id % 4] + + +def should_buy(policy: str, window: int, balance: float, + last90: float, fee_model: str, p: tuple[float, float, float], + share_value: float) -> tuple[bool, str]: + fee = fee_for_window(window, last90, fee_model) + if balance + 1e-12 < fee: + return False, "budget" + if policy == "free": + return False, "strategy" + if policy == "always": + return True, "bought" + if policy == "rational": + if window == 3: + expected_gain = p[2] * share_value - fee + else: + # Schaetzung: Kann nach einem weiteren Fehlschlag ein drittes Fenster kaufen. + future_fee = fee_for_window(3, last90, fee_model) + future_value = 0.0 + if balance - fee + 1e-12 >= future_fee: + future_value = max(0.0, p[2] * share_value - future_fee) + expected_gain = p[1] * share_value + (1.0 - p[1]) * future_value - fee + return expected_gain > 0.0, "bought" if expected_gain > 0.0 else "strategy" + raise ValueError("Unbekannte Strategie") + + +@dataclass +class Miner: + ident: int + policy: str + balance: float = 0.0 + earned: float = 0.0 + paid: float = 0.0 + recent: float = 0.0 + wins: int = 0 + tickets: int = 0 + paid_windows2: int = 0 + paid_windows3: int = 0 + skipped_budget: int = 0 + skipped_strategy: int = 0 + + +def simulate(miners: int, days: int, seed: int, profile: str, + strategy: str, fee_model: str, reward: float, + rounds_per_day: int, initial_balance: float = 0.0) -> dict: + if miners < 1 or days < 1 or rounds_per_day < 1 or reward <= 0 or initial_balance < 0: + raise ValueError("Ungueltige Parameter") + if profile not in PROFILES or fee_model not in FEE_MODELS: + raise ValueError("Unbekanntes Profil oder Gebuehrenmodell") + if strategy not in ("free", "always", "rational", "mixed"): + raise ValueError("Unbekannte Strategie") + + p = PROFILES[profile] + rng = random.Random(seed) + persons = [Miner(i + 1, policy_for(i, strategy), balance=initial_balance) + for i in range(miners)] + history = [[0.0] * HISTORY_DAYS for _ in persons] + issued = burned = 0.0 + no_ticket_rounds = total_tickets = 0 + q_estimate = p[0] + daily = [] + + for day in range(days): + idx = day % HISTORY_DAYS + for i, person in enumerate(persons): + person.recent = max(0.0, person.recent - history[i][idx]) + history[i][idx] = 0.0 + daily_wins = daily_burn = daily_tickets = 0 + + for _ in range(rounds_per_day): + winners_candidates = [] + # Strategie nutzt nur vergangene, nicht zukuenftige Ticket-Daten. + share_value = reward / (1.0 + (miners - 1) * q_estimate) + for i, person in enumerate(persons): + qualified = rng.random() < p[0] + if not qualified: + for window in (2, 3): + buy, reason = should_buy(person.policy, window, person.balance, + person.recent, fee_model, p, share_value) + if not buy: + if reason == "budget": + person.skipped_budget += 1 + else: + person.skipped_strategy += 1 + break + fee = fee_for_window(window, person.recent, fee_model) + person.balance -= fee + person.paid += fee + burned += fee + daily_burn += fee + if window == 2: + person.paid_windows2 += 1 + else: + person.paid_windows3 += 1 + if rng.random() < p[window - 1]: + qualified = True + break + if qualified: + winners_candidates.append(i) + person.tickets += 1 + total_tickets += len(winners_candidates) + daily_tickets += len(winners_candidates) + q_estimate = 0.995 * q_estimate + 0.005 * (len(winners_candidates) / miners) + if winners_candidates: + chosen = persons[rng.choice(winners_candidates)] + chosen.wins += 1 + chosen.earned += reward + chosen.balance += reward + chosen.recent += reward + history[chosen.ident - 1][idx] += reward + issued += reward + daily_wins += 1 + else: + no_ticket_rounds += 1 + + daily.append({ + "day": day + 1, + "issued_total": round(issued, 8), + "burned_total": round(burned, 8), + "supply": round(miners * initial_balance + issued - burned, 8), + "winners": daily_wins, + "tickets": daily_tickets, + "fees_burned": round(daily_burn, 8), + "gini_wallet": round(gini([m.balance for m in persons]), 6), + "q_estimate": round(q_estimate, 6), + }) + + balances = [max(0.0, m.balance) for m in persons] + total = sum(balances) + richest_count = max(1, math.ceil(miners * 0.10)) + top_share = sum(sorted(balances, reverse=True)[:richest_count]) / total if total > 0 else 0 + rounds = days * rounds_per_day + groups = {} + for group in ("free", "always", "rational"): + members = [m for m in persons if m.policy == group] + if members: + groups[group] = { + "miners": len(members), + "avg_wins": round(statistics.mean(m.wins for m in members), 3), + "avg_earned": round(statistics.mean(m.earned for m in members), 6), + "avg_fees_paid": round(statistics.mean(m.paid for m in members), 6), + "avg_balance": round(statistics.mean(m.balance for m in members), 6), + "avg_90d_rewards": round(statistics.mean(m.recent for m in members), 6), + "window2_per_miner": round(statistics.mean(m.paid_windows2 for m in members), 3), + "window3_per_miner": round(statistics.mean(m.paid_windows3 for m in members), 3), + } + summary = { + "model": "RESEARCH ONLY, not blockchain consensus", + "parameters": { + "miners": miners, "days": days, "seed": seed, "profile": profile, + "probabilities": p, "strategy": strategy, "fee_model": fee_model, + "reward": reward, "rounds_per_day": rounds_per_day, + "initial_balance": initial_balance, + "90d_history": HISTORY_DAYS, + }, + "rounds_total": rounds, + "issued": round(issued, 8), + "burned": round(burned, 8), + "circulating": round(miners * initial_balance + issued - burned, 8), + "no_ticket_rounds": no_ticket_rounds, + "ticket_rate": round(total_tickets / (rounds * miners), 6), + "gini_wallet": round(gini(balances), 6), + "share_top10pct_wallet": round(top_share, 6), + "mean_wallet": round(statistics.mean(balances), 6), + "median_wallet": round(statistics.median(balances), 6), + "groups": groups, + "warnings": [ + "Rechenarbeit nur als unabhaengige Bernoulli-Zufallsaufgabe modelliert; keine Hashzeiten.", + "Eine Identitaet = ein Teilnehmer, keine Sybil-/Mehrgeraeteangriffe oder Wallet-Verschiebung.", + "Strategie 'rational' verwendet eine einfache Nutzen-Schaetzung, keine optimale Spieltheorie.", + "Gebuehren sind Forschungswerte. Float niemals fuer Konsensberechnungen verwenden.", + "Keine Validator-Zensur, Kosten fuer Strom, Preisentwicklung oder Netzwerk-Ausfaelle.", + ], + } + # Invariants fuer Simulation; tolerante Float-Pruefung (nie On-chain). + assert all(m.balance >= -1e-8 for m in persons) + assert math.isclose(sum(m.earned for m in persons), issued, abs_tol=1e-6) + assert math.isclose(sum(m.paid for m in persons), burned, abs_tol=1e-6) + assert math.isclose(sum(m.balance for m in persons), miners * initial_balance + issued - burned, + abs_tol=max(1e-6, (miners * initial_balance + issued) * 1e-9)) + return {"summary": summary, "miners": [asdict(m) for m in persons], "daily": daily} + + +def save_results(out_dir: Path, result: dict) -> list[Path]: + out_dir.mkdir(parents=True, exist_ok=True) + s = result["summary"] + p = s["parameters"] + prefix = f"pop_{p['miners']}m_{p['days']}d_{p['profile']}_{p['strategy']}_{p['fee_model']}_{p['seed']}" + json_path = out_dir / (prefix + ".json") + txt_path = out_dir / (prefix + ".txt") + miners_csv = out_dir / (prefix + "_miners.csv") + daily_csv = out_dir / (prefix + "_daily.csv") + json_path.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + with miners_csv.open("w", newline="", encoding="utf-8") as f: + w = csv.DictWriter(f, fieldnames=result["miners"][0].keys()) + w.writeheader() + w.writerows(result["miners"]) + with daily_csv.open("w", newline="", encoding="utf-8") as f: + w = csv.DictWriter(f, fieldnames=result["daily"][0].keys()) + w.writeheader() + w.writerows(result["daily"]) + lines = ["POTATIUM · Langzeitsimulation | FORSCHUNG, KEINE PROTOKOLLREGEL", + "", f"Miner: {p['miners']} | Tage: {p['days']} | Runden/Tag: {p['rounds_per_day']}", + f"Erfolg pro Fenster: {p['probabilities']} | Strategie: {p['strategy']} | Gebuehren: {p['fee_model']}", + f"Seed: {p['seed']} | Brutto-Belohnung: {p['reward']} PTM", "", + f"Runden: {s['rounds_total']:,} | ohne Tickets: {s['no_ticket_rounds']:,}", + f"Ticketquote: {100*s['ticket_rate']:.2f}% | Gini Guthaben: {s['gini_wallet']:.3f}", + f"Erzeugt: {s['issued']:.4f} PTM | Verbrannt: {s['burned']:.4f} PTM | Umlauf: {s['circulating']:.4f} PTM", + f"Top 10 % Guthabenanteil: {100*s['share_top10pct_wallet']:.2f}%", "", + "Strategie Miner Ø Gewinne Ø Gebühren Ø Wallet Ø 90d-Gewinn Ø F2 Ø F3"] + for name, g in s["groups"].items(): + lines.append(f"{name:<14} {g['miners']:>5} {g['avg_wins']:>10.2f} {g['avg_fees_paid']:>12.4f} " + f"{g['avg_balance']:>11.4f} {g['avg_90d_rewards']:>13.2f} " + f"{g['window2_per_miner']:>5.1f} {g['window3_per_miner']:>5.1f}") + lines += ["", "MODELLGRENZEN:"] + ["- " + w for w in s["warnings"]] + txt_path.write_text("\n".join(lines) + "\n", encoding="utf-8") + print("\n".join(lines)) + return [txt_path, json_path, miners_csv, daily_csv] + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--miners", type=int, default=20) + ap.add_argument("--days", type=int, default=180) + ap.add_argument("--rounds-per-day", type=int, default=720, + help="720 bei 2 Minuten je Runde; kleiner fuer schnelle Tests") + ap.add_argument("--seed", type=int, default=20261025) + ap.add_argument("--profile", choices=PROFILES, default="balanced") + ap.add_argument("--strategy", choices=["free", "always", "rational", "mixed"], default="mixed") + ap.add_argument("--fee-model", choices=FEE_MODELS, default="mild") + ap.add_argument("--reward", type=float, default=1.0) + ap.add_argument("--initial-balance", type=float, default=0.0) + ap.add_argument("--out", type=Path, default=Path.home() / "potatium-simulation") + args = ap.parse_args() + result = simulate(args.miners, args.days, args.seed, args.profile, args.strategy, + args.fee_model, args.reward, args.rounds_per_day, args.initial_balance) + paths = save_results(args.out, result) + print("\nErstellt:") + for path in paths: + print(path) + + +if __name__ == "__main__": + main() diff --git a/tests/test_pop_cluster.py b/tests/test_pop_cluster.py new file mode 100644 index 0000000..f12b2e4 --- /dev/null +++ b/tests/test_pop_cluster.py @@ -0,0 +1,120 @@ +"""Off-chain PoP cluster simulator tests; no chain implementation is tested.""" +from __future__ import annotations + +import importlib.util +import math +import sys +import unittest +from pathlib import Path + +SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "simulate_pop_cluster.py" +SPEC = importlib.util.spec_from_file_location("simulate_pop_cluster", SCRIPT) +mod = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = mod +SPEC.loader.exec_module(mod) + + +class PopClusterTests(unittest.TestCase): + def test_worker_fees_reproduce_v04_examples(self): + expected = {0: 0, 1: .001, 3: .003, 4: .006, 7: .015, 8: .020, 10: .030} + for count, fee in expected.items(): + self.assertAlmostEqual(mod.worker_fee(count, 'marginal'), fee) + self.assertAlmostEqual(mod.worker_fee(7, 'tier'), .003) + self.assertAlmostEqual(mod.worker_fee(10, 'tier'), .005) + self.assertAlmostEqual(mod.worker_fee(10, 'marginal', 2), .060) + + def test_window_fees_mirror_offline_v02(self): + self.assertAlmostEqual(mod.window_fee(1, 100, 'low'), 0) + self.assertAlmostEqual(mod.progressive_fee(100), .0185) + self.assertAlmostEqual(mod.window_fee(2, 100, 'low'), .001185) + self.assertAlmostEqual(mod.window_fee(3, 100, 'low'), .00337) + self.assertAlmostEqual(mod.window_fee(2, 0, 'mild'), .001) + + def test_marginal_ticket_value_declines_for_owner_with_existing_tickets(self): + self.assertAlmostEqual(mod.marginal_share_value(1, 0, 4), 0.2) + self.assertAlmostEqual(mod.marginal_share_value(1, 1, 4), 4 / 30) + self.assertAlmostEqual(mod.marginal_share_value(1, 3, 0), 0) + + def test_validations(self): + for count in (-1, 11): + with self.assertRaises(ValueError): + mod.worker_fee(count) + with self.assertRaises(ValueError): + mod.worker_fee(2, scale=float('nan')) + with self.assertRaises(ValueError): + mod.simulate(operators=0) + with self.assertRaises(ValueError): + mod.simulate(operators=3, target_workers=11) + with self.assertRaises(ValueError): + mod.simulate(operators=3, target_workers=0, worker_scale=-1) + + def test_worker_activation_is_upfront_and_budget_limited(self): + op = mod.Operator(0, True, 10, 'always', balance=.006) + activated = mod.activate_workers(op, 'marginal', 1) + self.assertEqual(activated, 4) + self.assertAlmostEqual(op.balance, 0) + self.assertAlmostEqual(op.burned_worker, .006) + self.assertEqual(op.skipped_worker_budget, 6) + op2 = mod.Operator(1, True, 10, 'always', balance=0) + self.assertEqual(mod.activate_workers(op2, 'marginal', 1), 0) + self.assertAlmostEqual(op2.burned_worker, 0) + + def test_one_payout_per_round_and_supply_invariant(self): + result = mod.simulate(operators=4, target_workers=3, days=3, + rounds_per_day=25, seed=123, target_policy='always', + others_policy='free', window_model='low', initial_balance=2) + summ = result['summary'] + self.assertEqual(summ['rounds'], 75) + self.assertEqual(sum(op['wins'] for op in result['operators']), + 75 - summ['no_ticket_rounds']) + self.assertAlmostEqual(summ['minted'] - summ['burned_total'] + 8, + summ['circulating'], places=5) + self.assertGreater(summ['burned_workers'], 0) + self.assertGreater(summ['burned_windows'], 0) + self.assertGreater(result['operators'][0]['window2'], 0) + self.assertGreater(result['operators'][0]['window3'], 0) + self.assertEqual(sum(op['tickets'] for op in result['operators']), + summ['total_tickets']) + self.assertEqual(result['operators'][0]['wallet'], summ['target_wallet']) + + def test_seed_is_deterministic(self): + args = dict(operators=5, days=2, rounds_per_day=30, + target_workers=5, others_workers=1, seed=567, initial_balance=5) + self.assertEqual(mod.simulate(**args), mod.simulate(**args)) + + def test_zero_fees_with_free_only_equals_minted(self): + data = mod.simulate(operators=5, days=2, rounds_per_day=120, + target_workers=0, target_policy='free', others_policy='free') + self.assertEqual(data['summary']['burned_total'], 0) + self.assertAlmostEqual(sum(x['wallet'] for x in data['operators']), + data['summary']['minted'], places=5) + + def test_individual_operator_90_day_history_expires(self): + op = mod.Operator(0, True, 0, 'free') + op.add_win(0, 1) + self.assertEqual(op.recent, 1) + for day in range(1, 90): + op.expire_day(day) + self.assertEqual(op.recent, 1) + op.expire_day(90) + self.assertEqual(op.recent, 0) + + def test_newcomers_join_at_configured_day(self): + data = mod.simulate(operators=3, newcomers=2, join_day=2, + days=4, rounds_per_day=8, + target_policy='free', others_policy='free') + self.assertEqual([d['active_operators'] for d in data['daily']], [3, 3, 5, 5]) + self.assertEqual(len(data['operators']), 5) + + def test_comparison_delta_normalization(self): + results = [mod.simulate(operators=3, target_workers=w, days=1, + rounds_per_day=50, seed=1, target_policy='free') + for w in (0, 2)] + rows = mod.compare(results) + self.assertIsNone(rows[0]['delta_net_per_added_worker']) + self.assertAlmostEqual(rows[1]['delta_net_per_added_worker'], + (rows[1]['net_ptm']-rows[0]['net_ptm'])/2) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_pop_dynamic.py b/tests/test_pop_dynamic.py new file mode 100644 index 0000000..b52eb7f --- /dev/null +++ b/tests/test_pop_dynamic.py @@ -0,0 +1,155 @@ +"""Research simulator tests, not blockchain consensus tests.""" +from __future__ import annotations + +import importlib.util +import json +import math +import tempfile +import unittest +from pathlib import Path + +SOURCE = Path(__file__).resolve().parents[1] / "scripts" / "simulate_pop_dynamic.py" +spec = importlib.util.spec_from_file_location("dynamic", SOURCE) +mod = importlib.util.module_from_spec(spec) +import sys +sys.modules[spec.name] = mod +spec.loader.exec_module(mod) + + +class FeeTests(unittest.TestCase): + def test_worker_fee_staffel(self): + assert mod.worker_fee(0, 5) == 0 + self.assertAlmostEqual(mod.worker_fee(3, 1), .003) + self.assertAlmostEqual(mod.worker_fee(7, 1), .015) + self.assertAlmostEqual(mod.worker_fee(8, 1), .020) + self.assertAlmostEqual(mod.worker_fee(10, 5), .150) + + def test_invalid_values(self): + for n in [-1, 11]: + with self.assertRaises(ValueError): + mod.worker_fee(n, 1) + for scale in [-2, math.nan, math.inf]: + with self.assertRaises(ValueError): + mod.worker_fee(2, scale) + with self.assertRaises(ValueError): + mod.simulate(operators=1) + with self.assertRaises(ValueError): + mod.simulate(capacities=(12,)) + with self.assertRaises(ValueError): + mod.simulate(fund_mode="burn") + + def test_window_fee_progressivity(self): + self.assertAlmostEqual(mod.progressive_fee(10), .0005) + self.assertAlmostEqual(mod.progressive_fee(100), .0185) + self.assertGreater(mod.window_fee(3, 100), mod.window_fee(2,100)) + with self.assertRaises(ValueError): + mod.window_fee(1, 0) + + def test_worker_decision_and_budget(self): + f = mod.choose_worker_count + kw = dict(capacity=10, scale=5, rivals_estimated_tickets=12, + f1_chance=.55, reward=1.) + self.assertEqual(f(balance=0., mode="fixed", **kw), 0) + self.assertEqual(f(balance=2., mode="fixed", **kw), 10) + choice = f(balance=2., mode="dynamic", **kw) + self.assertLessEqual(choice, 10) + self.assertGreaterEqual(choice, 0) + # A much stronger levy must make additional workers less worthwhile. + high = f(balance=20., mode="dynamic", scale=100, + capacity=10, rivals_estimated_tickets=12, f1_chance=.55, reward=1.) + self.assertEqual(high, 0) + + def test_ticket_marginal(self): + self.assertAlmostEqual(mod.marginal_ticket_gain(0,4,1), .2) + self.assertAlmostEqual(mod.marginal_ticket_gain(1,4,1), 4/30) + self.assertAlmostEqual(mod.marginal_ticket_gain(4,0,1), 0) + + +class SimulationTests(unittest.TestCase): + def short(self, **kwargs): + defaults = dict(operators=6, newcomers=2, join_day=3, + days=8, rounds_per_day=30, seed=123, capacities=(0,1,3,5,8,10)) + defaults.update(kwargs) + return mod.simulate(**defaults) + + def test_fund_holds_all_fee_income(self): + r = self.short(fund_mode="hold", worker_mode="fixed", worker_scale=3) + s = r["summary"] + self.assertEqual(s["burned"], 0) + self.assertAlmostEqual(s["fund_balance"], s["fund_received_fees"], 4) + self.assertEqual(s["fund_distributed"], 0) + self.assertAlmostEqual(s["fund_paid_window"]+s["fund_paid_worker"],s["fund_received_fees"],4) + self.assertAlmostEqual(s["total_wallets"] + s["fund_balance"], s["total_supply"],4) + + def test_redistribution_conserves_ptm_and_creates_no_new_coins(self): + r = self.short(fund_mode="redistribute", payout_interval=2, + payout_share=.5, worker_mode="fixed", worker_scale=5) + s = r["summary"] + self.assertGreater(s["fund_distributed"], 0) + self.assertGreater(s["fund_balance"], 0) + self.assertAlmostEqual(s["fund_balance"]+s["fund_distributed"], s["fund_received_fees"], 4) + self.assertAlmostEqual(s["total_wallets"]+s["fund_balance"], s["total_supply"], 4) + self.assertAlmostEqual(sum(o["fund_received"] for o in r["operators"]),s["fund_distributed"], 4) + + def test_free_hubs_no_fees_no_fund(self): + r = self.short(capacities=(0,), window_policy="free",worker_mode="dynamic") + self.assertEqual(r["summary"]["fund_received_fees"], 0) + self.assertEqual(r["summary"]["fund_balance"], 0) + self.assertAlmostEqual(r["summary"]["minted"],r["summary"]["total_wallets"],4) + + def test_one_winner_per_round_and_ticket_limit(self): + r = self.short(worker_mode="dynamic",fund_mode="redistribute",payout_interval=1) + s = r["summary"] + self.assertEqual(sum(o["wins"] for o in r["operators"])+s["no_ticket_rounds"],s["rounds"]) + for op in r["operators"]: + self.assertLessEqual(op["tickets"],op["active_rounds"]*(1+op["capacity"])) + self.assertLessEqual(sum(o["tickets"] for o in r["operators"]), + sum(op["active_rounds"]*(1+op["capacity"]) for op in r["operators"])) + + def test_newcomers_only_start_on_join_day(self): + r = self.short(join_day=4, days=8, rounds_per_day=7) + newcomers = [o for o in r["operators"] if o["join_day"] > 0] + self.assertEqual(len(newcomers),2) + self.assertTrue(all(o["active_rounds"]==4*7 for o in newcomers)) + self.assertEqual(r["daily"][2]["active_operators"],6) + self.assertEqual(r["daily"][4]["active_operators"],8) + + def test_fixed_vs_dynamic_different_for_high_cost(self): + args = dict(worker_scale=30, fund_mode="hold",initial_balance=20.) + a = self.short(worker_mode="fixed",**args) + b = self.short(worker_mode="dynamic",**args) + self.assertGreater(a["summary"]["total_avg_workers"],b["summary"]["total_avg_workers"]) + + def test_determinism(self): + settings = dict(worker_mode="dynamic", fund_mode="redistribute", payout_interval=2) + self.assertEqual(self.short(**settings), self.short(**settings)) + + def test_zero_payout_matches_hold_except_mode(self): + a = self.short(fund_mode="hold") + b = self.short(fund_mode="redistribute",payout_share=0,payout_interval=2) + self.assertEqual(a["summary"],b["summary"]) + self.assertEqual(a["daily"],b["daily"]) + self.assertEqual(a["operators"],b["operators"]) + + def test_rolling_90day_expiry(self): + op = mod.Operator(1,1,0,0.) + op.reward(0,2.) + self.assertEqual(op.recent,2.) + op.expire(90) + self.assertEqual(op.recent,0.) + self.assertEqual(op.history[0],0.) + + def test_export(self): + a = self.short() + b = self.short(worker_mode="fixed") + with tempfile.TemporaryDirectory() as temp: + paths = mod.write_output(Path(temp),[a,b],mod.aggregate([a,b]),"test") + self.assertEqual(len(paths),5) + self.assertTrue(all(p.is_file() and p.stat().st_size>0 for p in paths)) + payload = json.loads(paths[4].read_text()) + self.assertEqual(payload["scenario_count"],2) + self.assertEqual(len(payload["comparisons"]),2) + + +if __name__=="__main__": + unittest.main() diff --git a/tests/test_pop_fee_flows.py b/tests/test_pop_fee_flows.py new file mode 100644 index 0000000..cd18b7d --- /dev/null +++ b/tests/test_pop_fee_flows.py @@ -0,0 +1,93 @@ +"""Pure off-chain accounting and ZIP importer tests.""" +from __future__ import annotations +import importlib.util +import json +import sys +import tempfile +import unittest +import zipfile +from pathlib import Path + +S = Path(__file__).resolve().parents[1] / 'scripts' / 'analyze_pop_fee_flows.py' +spec = importlib.util.spec_from_file_location('analyze_pop_fee_flows', S) +mod = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = mod +spec.loader.exec_module(mod) + + +def sample(): + return {'parameters': {'target_workers': 1, 'seed': 2, 'operators': 2, 'days': 2, + 'other_workers': 0, 'worker_fee_scale': 1.0, + 'initial_balance': 1.0, 'others_workers': 0, 'newcomers': 0}, + 'summary': {'minted': 8.0, 'burned_workers': 2.0, 'burned_windows': 1.0, + 'burned_total': 3.0, 'target_net': 2.5, + 'target_win_percent': 25., 'gini_operator_wallets': .1}, + 'operators': [{'wallet': 3.0}, {'wallet': 4.0}]} + + +class FeeFlowTests(unittest.TestCase): + def test_all_policies_conserve_supply(self): + for p in mod.presets(): + r = mod.project(sample(), p, 4) + self.assertAlmostEqual(r['miner_wallets'] + r['paid_validators'] + r['paid_treasury'], + r['outstanding_supply']) + self.assertAlmostEqual(r['burned'] + r['paid_validators'] + r['paid_treasury'], 3) + + def test_burn_reduces_outstanding(self): + r = mod.project(sample(), mod.presets()[0], 4) + self.assertEqual((r['outstanding_supply'], r['burned'], r['paid_validators']), (7, 3, 0)) + + def test_no_burn_increases_outstanding_but_not_miner_net(self): + burn = mod.project(sample(), mod.presets()[0], 4) + hybrid = mod.project(sample(), mod.presets()[3], 4) + self.assertEqual((hybrid['burned'], hybrid['outstanding_supply']), (0, 10)) + self.assertAlmostEqual(hybrid['paid_validators'], 2.4) + self.assertAlmostEqual(hybrid['paid_treasury'], .6) + self.assertEqual(hybrid['target_miner_net'], burn['target_miner_net']) + + def test_different_destinations_worker_and_window(self): + d = mod.project(sample(), mod.presets()[-1], 2) + self.assertEqual((d['paid_validators'], d['paid_treasury'], d['burned']), (1, 2, 0)) + + def test_custom_triplets(self): + self.assertEqual(mod.parse_split('0,80,20'), mod.Split(0, .8, .2)) + for s in ('70,10,10', 'nan,10,90', '0,10', '1,2,foo'): + with self.assertRaises(ValueError): mod.parse_split(s) + + def test_reject_bad_accounting(self): + bad = sample() + bad['operators'][0]['wallet'] = 99 + with self.assertRaises(ValueError): mod.project(bad, mod.presets()[0], 4) + + def test_grouping_seeds(self): + a = sample() + b = sample() + b['parameters']['seed'] = 4 + runs = [mod.project(v, mod.presets()[0], 4) for v in (a, b)] + rows = mod.summarize(runs) + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0]['seeds'], 2) + + def test_read_zip_without_extracting(self): + with tempfile.TemporaryDirectory() as d: + path = Path(d) / 'results.zip' + with zipfile.ZipFile(path, 'w') as z: + z.writestr('potatium-simulation/pop_cluster_sample.json', json.dumps({'scenarios': [sample()]})) + z.writestr('../should_not_be_extracted.txt', 'nothing') + source, names = mod.load_sources(path) + self.assertEqual(len(source), 1) + self.assertEqual(len(names), 1) + self.assertFalse((Path(d).parent / 'should_not_be_extracted.txt').exists()) + + def test_output_package(self): + with tempfile.TemporaryDirectory() as d: + runs = [mod.project(sample(), x, 4) for x in mod.presets()] + paths = mod.output(Path(d), mod.summarize(runs), runs, ['a.json']) + self.assertEqual(len(paths), 5) + with zipfile.ZipFile(paths[-1]) as z: + self.assertIsNone(z.testzip()) + self.assertEqual(len(z.namelist()), 4) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_pop_longterm.py b/tests/test_pop_longterm.py new file mode 100644 index 0000000..557fce8 --- /dev/null +++ b/tests/test_pop_longterm.py @@ -0,0 +1,52 @@ +"""Unit tests for offline research simulation only (not consensus tests).""" +from __future__ import annotations + +import math +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) +from simulate_pop_longterm import ( + fee_for_window, progressive_fee, policy_for, should_buy, simulate, save_results, +) + + +class LongtermSimulationTests(unittest.TestCase): + def test_fee_models_and_free_entry(self): + self.assertEqual(fee_for_window(1, 1000, "original"), 0) + self.assertLess(fee_for_window(2, 1000, "low"), fee_for_window(2, 1000, "original")) + self.assertGreater(progressive_fee(1000), progressive_fee(100)) + self.assertGreater(fee_for_window(3, 100, "original"), fee_for_window(2, 100, "original")) + + def test_policy_and_budget(self): + self.assertEqual([policy_for(x, "mixed") for x in range(4)], + ["free", "rational", "rational", "always"]) + ok, reason = should_buy("always", 2, 0, 100, "original", (0.7,)*3, 1) + self.assertFalse(ok) + self.assertEqual(reason, "budget") + + def test_zero_capital_never_goes_negative(self): + res = simulate(12, 4, 7, "balanced", "mixed", "original", 1, 10) + self.assertTrue(all(m["balance"] >= -1e-8 for m in res["miners"])) + self.assertLessEqual(res["summary"]["issued"], 40) + self.assertGreaterEqual(res["summary"]["burned"], 0) + + def test_free_scenario_no_fees(self): + res = simulate(10, 2, 11, "original", "free", "original", 1, 16) + self.assertEqual(res["summary"]["burned"], 0) + self.assertTrue(all(m["paid_windows2"] == 0 for m in res["miners"])) + + def test_reproducible_and_report_files(self): + a = simulate(5, 3, 2026, "balanced", "mixed", "mild", 1, 20) + b = simulate(5, 3, 2026, "balanced", "mixed", "mild", 1, 20) + self.assertEqual(a, b) + with tempfile.TemporaryDirectory() as tmp: + files = save_results(Path(tmp), a) + self.assertEqual(len(files), 4) + self.assertTrue(all(p.is_file() for p in files)) + + +if __name__ == "__main__": + unittest.main()