ZYCORD Doku
Deutsch
ZycordDokuArchitektur

Architektur

Der technische Begleitband zum Whitepaper — wie der Referenz-Node den Entwurf umsetzt und welche Entscheidungen dahinterstehen. Erläuternd, nicht normativ.

Dieses Dokument ist nicht normativ

Das Protokoll sind die Parameterdateien und die Golden Vectors, dazu die benannten Regeln, wo immer sie definiert sind; die Wire-Spezifikation trägt die Anforderungen der Peer-Ebene. Diese Seite erläutert diese Oberfläche und hält die Entscheidungen dahinter fest. Wo sie der normativen Oberfläche widerspricht, gewinnt die normative Oberfläche, und dieser Text wird korrigiert. Der vollständige technische Begleitband ist docs/ARCHITECTURE.md.

Zwei Prädikate, zwei Engines#

Das Leben eines Zertifikats, von Anfang bis Ende:

 wallet or sequencer         network                        every full node
+-------------+   gossip   +--------------+               +---------------------+
| build cert  | ---------> | cert topic   | ------------> | STATELESS PIPELINE  |
| (reads,     |            | (TLS gossip) |               | V-rules, strict     |
| writes,     |            +--------------+               | Ed25519, program    |
| sigs, fee,  |                                           | re-execution        |
| deposit)    |                                           |  -> mark VALID      |
+-------------+                                           +----------+----------+
                                                                     | mempool
                                                                     v
                        miner (any node)                  +---------------------+
                      +----------------------+   block    | FOLD (sequential)   |
                      | order the hash list, | ---------> | per certificate:    |
                      | append forced certs, |   gossip   | APPLIED / SKIPPED / |
                      | RandomX solve        |            | FORCED_FAILED /     |
                      +----------------------+            | DROPPED -> state    |
                                                          +---------------------+

Gültigkeit ist zustandslos und parallel: sie läuft einmal pro Zertifikat pro Node, vor Blöcken und unabhängig von ihnen. Anwendbarkeit ist zustandsbehaftet und sequenziell: sie läuft innerhalb des Folds an der festgeschriebenen Position des Zertifikats. Der Miner führt nichts aus, was ihm gegeben wird: er ordnet Hashes, die er bereits validiert gesehen hat, und löst den Proof of Work, oder er signiert nach der Umstellung auf Proof of Stake in Ära 2 den Slot, für den er ausgelost wurde. Seine einzige Ausführung ist die des Forced Path, ab Ära 1: er darf Open Intents gegen den Zustand am Ende seines Blocks ausführen und die Ergebnisse anhängen, höchstens 16 pro Block, die jeder Node dann wie jedes andere Zertifikat prüft. Der Fold ist eine enge Schleife über einen Arbeitssatz im Speicher — vergleichen, addieren, schreiben — und führt überhaupt nichts aus.

Technische Grundsätze#

Grundsatz
P1Der Fold ist unantastbar. Die Zustandsübergangsfunktion lebt in einem reinen Paket ohne I/O, ohne Uhren, ohne Goroutinen, ohne Map-Iteration und ohne Gleitkomma. Sie ist der einzige Code, dessen Fehler sich nachträglich nicht beheben lassen. Alles andere im Node ist austauschbare Verrohrung.
P2Determinismus schlägt Performance. Jede Optimierung, die Nichtdeterminismus riskiert, wird in Konsenscode abgelehnt. Performance gehört in die zustandslose Pipeline, wo sie gefahrlos ist.
P3Keine Admin-Schlüssel, keine privilegierte RPC. Es gibt keinen Codepfad, über den irgendein Schlüssel pausieren, aktualisieren, prägen oder reorganisieren könnte. Was nicht in den Fold-Regeln steht, existiert nicht.
P4Kleine Konsensoberfläche. core/ importiert nichts außerhalb der Standardbibliothek. Der Rest des Nodes darf Ökosystembibliotheken verwenden; der Kern nicht.
P5Spezifikation zuerst. Die Golden Vectors sind das Protokoll. Der Go-Code ist eine Referenzimplementierung; eine unabhängige Implementierung, die die Vektoren besteht, ist ein Peer, kein Fork. Das ist es, was es dem Betreuer erlaubt, irgendwann niemand Bestimmtes zu sein.
P6Reproduzierbar ab v0.1. Fixierte Go-Toolchain, -trimpath, fixierte Abhängigkeiten. Vertrauen wandert von der Binärdatei zum Code, und das ist das einzige Vertrauen, das ein anonymer Autor anbieten kann.
P7Eine Binärdatei, per Höhe freigeschaltet. Die Maschine, die Sequencer-Mechanik und der Forced Path sind in jedes Release einkompiliert und werden unterhalb ihrer Aktivierungshöhe h1_vm (80.640 im Mainnet) abgelehnt, von der Validate verlangt, dass sie auf eine Epochengrenze fällt. Ära 1 kommt, weil die Chain eine Zahl erreicht hat, nie weil Betreiber zum Upgrade aufgefordert wurden. Die späteren Ären sind nicht in der Binärdatei: jede erscheint als eigenes Release, per Hard Fork.

P3 ist diejenige, auf die ein skeptischer Leser am stärksten drängen sollte, und die Treasury ist die Stelle, an der er drängen sollte: die Genesis enthält überhaupt keinen Schlüssel und keinen Ausgabeweg, es gibt also kein Privileg, das man halten, delegieren oder stehlen könnte. Die Treasury kann nicht vor Höhe 259.200 ausgegeben werden, und welche Regel sie auch immer öffnet, kommt als Konsensänderung — derselbe soziale Mechanismus wie jede andere, derselben Ablehnung unterworfen und selbst dann nur in der Lage, genau eine Zelle zu bewegen. Der Gründeranteil ist eine Gutschrift, kein Schlüssel: 3 % jeder Subvention an eine in der Parameterdatei festgelegte Adresse, bis Höhe 3.153.600, die Ära 2 als Epoche 1.095 liest. Diese Adresse hält Coins und erreicht sonst nichts. Ein Admin-Schlüssel ist einer, der existiert, bevor irgendjemand zugestimmt hat, und alles erreicht.

Kryptografische Primitive#

RolleWahlWarum
SignaturenEd25519Batch-Verifikation (die Auffahrt zur GPU), keine Malleability, winzige Schlüssel. Strenge, bei Genesis festgelegte Regeln: kanonische Kodierungen für den öffentlichen Schlüssel und R sind Pflicht, der öffentliche Schlüssel muss torsionsfrei und nicht von kleiner Ordnung sein, und die Verifikation erfolgt kofaktorlos.
HashingBLAKE3Zertifikats- und Block-IDs, Adressen, State Root. Schnell genug, um mit der Leitungsgeschwindigkeit der Weiterleitung zu hashen; parallelfreundlich für den State Root der Epoche.
Proof of WorkRandomXCPU-optimiert. Das einzige cgo im Baum, hinter einem Build-Tag, und in einem Build ohne dieses Tag nicht vorhanden. pow_engine steht in der Konsenswurzel, eine Binärdatei mit der falschen Engine verweigert also den Start, statt den falschen Beweis anzunehmen.
DomänentrennungverpflichtendJeder Hash ist blake3(tag ‖ payload). Signaturen signieren über die Chain-ID und die Konsenswurzel, was Replay sowohl zwischen Netzwerken als auch über zwei Inkarnationen eines Netzwerks hinweg ausschließt.

Die Torsionsablehnung ist es, was den Batch-Pfad sicher macht. Ein Schlüssel gemischter Ordnung ist nicht von kleiner Ordnung, keine Sperrliste erreicht ihn also, und er ist genau die Stelle, an der ein kofaktorbehafteter Batch-Verifizierer und ein kofaktorloser Einzelverifizierer uneins sind. Liegen der Schlüssel und R in der Untergruppe primer Ordnung, sind die beiden beweisbar äquivalent — ein Batch-Verifizierer darf also kofaktorbehaftet sein, sofern er dieselben Kodierungs- und Torsionsregeln vor dem Batching anwendet. Diese Pflicht ist der Preis der Wahl, und den Batch-Verifizierer gibt es noch nicht.

Kanonische Kodierung und Bezeichner#

Alle Konsensobjekte sind SSZ-Container: eine einzige kanonische Byte-Kodierung, keine Map-Reihenfolge, keine Mehrdeutigkeit optionaler Felder, feste Offsets für billiges teilweises Parsen und native Merkleisierung.

cert_id       = blake3("zcd/certid/v1" || ssz(certificate with an empty signature list))
cert_exemplar = blake3("zcd/cert/v1"   || ssz(certificate))
block_id      = blake3("zcd/block/v1"  || ssz(header))

Die ersten beiden sind verschiedene Digests, die verschiedene Fragen beantworten, und eine Implementierung, die den einen dort verwendet, wo der andere hingehört, ist monetär kaputt. Die ID beantwortet ist diese Autorisierung abgerechnet worden; der Exemplar-Hash beantwortet belegen diese Bytes das. Die beiden teilen sich nie einen Schlüssel.

Signaturen liegen außerhalb des Urbildes der ID, weil eine Signatur ein randomisierter Nachweis ist: der Unterzeichner wählt die Nonce, jede Nonce ergibt eine weitere gültige und vollkommen kanonische Signatur über denselben Body, und kein Verifizierer kann prüfen, welche verwendet wurde. Lägen sie innerhalb, hätte eine Autorisierung unbegrenzt viele IDs, jede abrechenbar, jede von irgendeinem einzelnen ihrer erforderlichen Unterzeichner aus der Befugnis der anderen heraus erzeugbar.

Das Gebührengebot liegt innerhalb der ID, und das ist eine monetäre Entscheidung

Ein Gebot außerhalb der ID wäre ein Gebot, das jeder unterwegs umschreiben könnte — aufgebläht, um das Guthaben des Unterzeichners über die Grundgebühr zu verbrennen, oder auf null gesetzt, um das Zertifikat aus jedem Block herauszuhalten. Was ein Zertifikat zahlt, ist Teil dessen, was sein Unterzeichner autorisiert hat, also wird es gehasht und signiert. Eine spätere Kodierungsänderung, die “

Die Regel, die daraus folgt: parsen, nicht zweimal validieren. Das Dekodieren erzwingt die kanonische Form, ein dekodiertes Objekt ist also konstruktionsbedingt strukturell gültig, und die Regel-Engines prüfen die Form nie erneut.

Das Zellenmodell#

Eine Zelle ist der Wert an einem Slot. Zellwerte sind vorzeichenlose 256-Bit-Ganzzahlen, in 32 Bytes big-endian gespeichert. Fehlende Zellen lesen sich als null — null ist Abwesenheit, was eine Konsensanforderung ist und keine Bequemlichkeit der Implementierung: es hält den State Root zu einer Funktion des Zustands statt der Historie, die ihn hervorgebracht hat.

Getrennt davon führt das Protokoll eine Spent-Address-Registry: eine dauerhafte Konsensmenge von One-Shot-Adressen, deren Signaturbefugnis verbrannt wurde.

Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
VersionArtBelastungsautorisierung
0x01One-Shot, NutzerSignatur des Eigentümers; jedes belastende Zertifikat muss zusätzlich ein ausdrückliches MARK_SPENT tragen. Nachdem es angewendet wurde, schlagen alle Lese- und Schreibvorgänge unter der Adresse für immer fehl.
0x02persistent, NutzerSignatur des Eigentümers; für immer wiederverwendbar. Kann nie in die Spent-Registry gelangen.
0x03AssetGeregelt durch die unveränderlichen Befugniszellen des Assets.
0x00ProtokollNur im Fold: Epoch Beacon, Grundgebührzellen, Coinbase-Maturity-Ring, Treasury-Zelle, Sequencer-Zähler.
0x05ContractNiemand per Schlüssel: kein Schlüssel hasht auf eine solche Adresse. Der Code des Contracts und die Regeln des Folds entscheiden.
0x04nicht definiertDarunter existiert keine Zelle; ein Zertifikat, das eine solche Adresse benennt, ist ungültig.

Aus dem typisierten Zugriff stammt die Parallelität. Ein Read deklariert, wie er seine Zelle einschränkt — EXACT oder einer der Guards GUARD_GE, GUARD_LE, GUARD_EQ —, und ein Write deklariert, was er tut: SET, DELTA_ADD, DELTA_SUB oder MARK_SPENT. Nur EXACT speist die Berechnung, und nur EXACT veraltet, wenn sich die Zelle bewegt; ein Guard gibt nichts zurück und ein Delta liest nichts, sodass die erneute Ausführung eine reine Funktion der Exact Reads bleibt und beliebig viele geschützte Belastungen und Gutschriften auf einen Slot kommutieren. Eine Disziplin pro Slot pro Zertifikat: ein Set und ein Delta teilen sich nie einen Slot. Null zu schreiben löscht die Zelle, sodass ein nie beschriebener Slot und ein mit null beschriebener Slot derselbe Slot sind.

One-Shot gegen persistent ist eine Wallet-Richtlinie, die als Adressbit sichtbar wird, nicht zwei Ledger. Wallets verwenden standardmäßig für jede eingehende Zahlung eine frische One-Shot-Adresse, den Pfad ohne Konkurrenz; wer ein Konto will, verwendet die persistente Variante. Mining-Belohnungen gehören in eine solche: der Konsens akzeptiert beide Arten als Auszahlung, aber eine One-Shot-Adresse verbrennt jede Belohnung, die sie hält, in dem Moment, in dem sie ausgegeben wird, deshalb weist der Node eine solche als Auszahlungsadresse ab.

Der Registry-Eintrag wird nie verdichtet. Zellwerte unter einer ausgegebenen Adresse dürfen nach dem Undo Horizon beschnitten werden, aber der Eintrag, der die Adresse als ausgegeben festhält, ist das, was ihre Wiederauferstehung verhindert. Er ist reiner Anhänge-Konsenszustand, etwa 33 Bytes pro Adresse, für immer — das ehrliche offene Problem des Protokolls, strukturell geteilt mit jedem Nullifier-Set-Entwurf.

Zustandslose Gültigkeit und das Abrechnungsgesetz#

Die V-Regeln laufen auf jedem Zertifikat, parallel, vor der Aufnahme in den Mempool und während der Blockverifikation, und brauchen keinerlei Zustand: kanonische Form und Chain-ID, wobei jede Liste innerhalb ihrer Grenze liegt; jede Signatur, die unter striktem Ed25519 über die Signaturwurzel aufgeht, wobei die Signaturmenge sowohl minimal als auch ausreichend ist; die erneute Ausführung des Programms gegen die deklarierten Reads, die genau die deklarierten Writes ergibt; Autorisierung für jeden Write, der sie braucht, und bedingungslos eine Signatur auf der Einlagezelle; eine Einlage, die das native Guthaben einer Nutzeradresse ist und den ungünstigsten Fall des Zertifikats deckt; Selbstkonsistenz, sodass kein Write einem Read desselben Slots widerspricht und keine belastete One-Shot-Adresse der Markierung als ausgegeben entgeht; kein Write auf eine Protokollzelle; ein definiertes Versionsbyte auf jeder genannten Adresse; und, in einem Contract, dessen Sequencer-Schlüssel nicht null ist, die Signatur dieses Schlüssels auf dem exakten Pfad.

Das Abrechnungsgesetz des Systems ist ein Satz, und es ist das, woran man sich halten sollte:

Eine Signatur, höchstens eine Abrechnung, nie an einer Position, der ihr Unterzeichner nicht ausweichen konnte.

Der Fold ordnet einen Block in drei Klassen und lässt dem Producer keinen Ermessensspielraum: zuerst die Fills, in der Reihenfolge der Sequencer-Pipelines, zu denen sie gehören; dann jedes andere Zertifikat nach (deposit address, Seq, id); zuletzt die erzwungenen Zertifikate, nach id. Jedes Zertifikat hat dann genau eines von vier Ergebnissen, und die Golden Vectors benennen sie:

ErgebnisWannWer zahlt
APPLIEDJeder deklarierte Read gilt.Der Einleger, zu den tatsächlichen Kosten: Basisgebühr verbrannt, Prioritätsgebühr an den Producer.
SKIPPED_STALE, SKIPPED_OVERFLOWEin Read gilt nicht mehr, oder ein Delta würde [0, 2²⁵⁶) verlassen. Nichts wird geschrieben.Der Einleger: eine feste Skip-Gebühr, verbrannt. Bei einem Fill ein Sequencer — nie der Nutzer.
FORCED_FAILEDEin erzwungenes Zertifikat, dessen Aufruf revertiert ist oder seine Schranken verletzt hat.Seine Zahler; sonst wird nichts geschrieben.
DROPPEDDie Einlage kann nicht reserviert werden: die Zelle ist ungedeckt oder ausgegeben.Niemand. Nicht als gesehen markiert, sodass es gegen eine gedeckte Einlage erneut eingereicht werden kann.

Angewendete, übersprungene und erzwungen gescheiterte Zertifikate werden alle als gesehen markiert, und die Aufnahme eines gesehenen, abgelaufenen oder unterbotenen Zertifikats macht den Block ungültig, sodass kein Producer das Zertifikat eines anderen erneut aufnehmen oder verzögern kann, um dessen Einlage abzuschöpfen. Fehlschläge, für die es niemanden zu belasten gibt — ein Fill, dessen Pipeline-Zähler außer der Reihe ist oder dessen Fence-Zähler sich bewegt hat, ein erzwungenes Zertifikat, dessen Reads nicht gelten —, machen stattdessen den Block ungültig: der Producer, der den Block aus Zustand gebaut hat, den er hält, ist die Partei, die es hätte wissen können.

Ära-Schranken#

Die Aktivierungshöhen stehen in spec/params.json. h1_vm = 80.640 öffnet Ära 1: die cEVM und die vorab bereitgestellte Bibliothek, Contract-Einträge mit einem Sequencer-Schlüssel, einem Admin und einem Fence-Zähler, Intents und Fills, das Verzeichnis und den Forced Path. Die eigenen Konstanten der Ära — das Intent-Fenster von 8 Blöcken, 16 erzwungene Zertifikate pro Block zu je 4.000 Ableitungseinheiten, eine Lebensdauer eines Verzeichniseintrags von 259.200 Blöcken über die TTL seines ankündigenden Zertifikats hinaus — sind einkompiliert, statt von der Parameterdatei getragen zu werden, weil ein zusätzlicher Parameter die Genesis-ID jedes laufenden Netzwerks verschieben würde; der Schrankenblock schreibt sie in Protokollzellen, sodass der State Root sich von da an auf sie festlegt.

h1_bond ist im Mainnet und im öffentlichen Testnet gleich h1_vm, sodass sich die Bonding-Operationen dort nie öffnen: in Ära 1 gibt es kein Binden von Stake. Der Baum trägt weiterhin die Underwriting-, Lease- und Forced-Queue-Regeln von v1.0, und sie gelten nur im Entwicklungsnetzwerk, in einem Fenster von 256 Blöcken oberhalb seiner h1_vm und unterhalb seiner eigenen v1-Schranke, das existiert, damit die Vektoren, die diese Mechanik festhalten, ein Netzwerk behalten, in dem sie gültig ist. h2_pos = 1.576.800 ist für Proof of Stake reserviert, bei Genesis eingefroren und von keiner Regel gelesen. Ära 2, Proof of Stake, wird als eigenes Release per Hard Fork ausgeliefert, mit Konstanten, die wie die von Ära 1 einkompiliert sind: sie öffnet das Bonding in Epoche 182 nach Höhe, startet ihre Rampe, wenn der aktive Stake 30 Epochen lang 10 % der Supply hält, oder spätestens in Epoche 273, und stellt auf 5-Sekunden-Slots um, wenn die Rampe abgeschlossen ist. Ab der Umstellung liest sie jede Genesis-Konstante, die eigentlich eine Dauer ist, in der Einheit, die sie misst, sodass sich die Parameterdatei nicht ändert.

Aufbau des Repositorys#

zycord/
  spec/       parameter sets, golden vectors, library images   <- THE PROTOCOL
  core/       consensus-critical; standard library only (P4)
    types/  crypto/  ssz/  u256/  state/  validity/  fold/  params/  genesis/
    cevm/          the certificate-adapted EVM; vendored interpreter, pure Go
    stdlib/        the pre-deployed library, its addresses and code hashes
    pow/randomx/   the mainnet engine: vendored C++, cgo, behind a build tag
  node/       storage/  chain/  verify/  mempool/  miner/  p2p/  sync/  rpc/  stratum/
  wallet/     key management, certificate builders (reference; not consensus)
  contracts/  the reference contracts, in Solidity
  sim/        simulator, fuzz harnesses, differential refold, chaos soak
  cmd/        zycordd, zcd
  desktop/    the wallet in a native window — a separate Go module
  docs/       architecture, protocol, operating guide, whitepaper

Abhängigkeitspfeile zeigen ausschließlich nach innen — node → core, wallet → core, nie umgekehrt —, und nichts innerhalb von core/ greift über core/ und die Standardbibliothek hinaus. Eine Ausnahme, benannt und durchgesetzt: core/pow/randomx ist das einzige cgo im Baum und übersetzt nur unter dem Build-Tag, jeder Build ohne das Tag kommt also weiterhin mit der Standardbibliothek allein aus und braucht keine C-Toolchain. Die CI führt die Prüfung aus, die das durchsetzt, denn die Drittanbieterprüfung greppt Modulpfade, und cgo hat keine — wodurch die Regel von niemandem durchgesetzt wurde, bis sie hinzugefügt wurde.

Wie es getestet wird#

  • Golden Vectors. Jede Fold-, Block- und Gültigkeitsregel hat positive und negative Fälle als (pre-state, block) → (post-state | invalid, outcomes, fees). Die Suite ist der Kompatibilitätsvertrag für unabhängige Implementierungen.
  • Die Griefing-Suite. Erneute Aufnahme angewendeter und übersprungener Zertifikate, abgelaufene Aufnahme, unterbotene Aufnahme, abhängige Ketten unter Proposer-Umsortierung, Burn-and-Refund-Zyklen, Kreditstürme Dritter, Wettläufe an der Mint-Cap-Grenze.
  • Eigenschaftsbasiert. Fold-Determinismus unter Permutation der Proposer-Reihenfolge, Delta-Kommutativität, ABA-Toleranz und Erhaltung — einschließlich Einlagen im Umlauf, dem Maturity-Ring und der Treasury-Zelle, denn eine Erhaltungsprüfung, die die Treasury auslässt, meldet jeden Block als wertschöpfend.
  • Differenziell. Eine absichtlich naive zweite Fold-Implementierung, auf Offensichtlichkeit statt Geschwindigkeit geschrieben, wird gegen die echte gefuzzt. Divergenz blockiert ein Release.
  • Adversarielle Simulation. Skip-Stürme, Wettläufe zur Einlagenleerung, Drop-stopfende Miner, Reorg-Folter, Zeitstempelmanipulation, Eclipse-Lite-Weiterleitungsszenarien. Szenariokonfigurationen sind eingecheckt; Läufe sind über den Seed reproduzierbar.
  • Nebenläufigkeit, mit Absicht. Jede Komponente, die mehr als eine Goroutine berührt, hat einen Test, der nebenläufig ist, in der Form, die der Prozess tatsächlich verwendet. -race auf einer Suite mit einer einzigen Goroutine misst nichts und meldet Erfolg.
  • Der Chaos-Dauertest. Echte Node-Prozesse über echte Sockets hinter einem Proxy, der Latenz, Jitter, Verluste und Partitionen einspeist, wobei Nodes zufällig per SIGKILL abgeschossen werden. Das ist die Oberfläche, die das Data Race fand, das die gesamte -race-Suite übersah.

Genesis ist ein Artefakt, keine Zeremonie#

zcd genesis gibt den Genesis-Block aus — leerer Zustand, initialisierte Beacon-Zellen, leere Spent-Registry, keinerlei Zuteilungen — und seine ID. Der Launch legte sich im Voraus auf das Code-Tag, den Parameter-Hash, die Genesis-ID und den Startzeitpunkt fest, und der Parametersatz trug ein Commitment auf eine Launch-Nachricht, deren Digest Block 1 offenlegen musste, sodass niemand die Chain vor ihrem angekündigten Start minen konnte. Jeder kann die ersten vier in Millisekunden aus dem Quellcode auf jedem Rechner nachbauen. Es gibt sonst nichts, dem zu vertrauen wäre.