Einen Node betreiben
Die Betreiberanleitung zu zycordd. Das Protokoll, das er spricht, ist die Wire-Spezifikation; auf dieser Seite geht es um den Betrieb.
Die Fassung in einer Zeile#
zycordd --devnet --dir ./devnet
Das ist ein Full Node: er validiert jeden Block ab der Genesis, bedient Peers und beantwortet eine
schreibgeschützte RPC auf 127.0.0.1:9420. Die öffentlichen Netzwerke brauchen die -randomx-
Binärdatei: zycordd-randomx --dir ./data tritt über seine eingebauten Seeds dem Mainnet bei, das seit
2026-10-07 läuft, und Öffentliches Testnet behandelt das
andere.
Was der Node nicht hat#
Es gibt zu keinem Zeitpunkt Schlüsselmaterial im Node-Prozess. Er nimmt keinen Seed, keine
Passphrase und keine Schlüsseldatei entgegen. Das Signieren geschieht in zcd, und die Schreib-Endpunkte
des Nodes gewähren keine Befugnis: /submit nimmt ein signiertes Zertifikat,
/openintent einen signierten Open Intent für den Forced Path und /servicecard eine
signierte Dienstankündigung entgegen, und jedes wird genauso geprüft wie eines, das von einem Fremden eintrifft,
und dann per Gossip weitergegeben. Die RPC bindet standardmäßig an 127.0.0.1:9420.
Es gibt zudem keinen privilegierten Endpunkt, weil es nichts zu privilegieren gibt. Kein Schlüssel kann pausieren, aktualisieren, zensieren oder prägen, es gibt also keinen Aufruf offenzulegen.
Der Begrenzer schlüsselt nach dem Transport-Peer und nie nach X-Forwarded-For: ein Begrenzer,
der einem vom Client gesetzten Header vertraut, ist ein Begrenzer, den der Client abschaltet. Hinter einem Proxy trifft daher jede
Anfrage mit der Adresse des Proxys ein, das Limit je Client ist also keines mehr je Client,
sondern wird zu einem gemeinsamen Eimer von 600 pro Minute für alle zugleich — und ein einzelner Aufrufer
kann ihn für alle leeren. Das ist wohl schlimmer als gar kein Limit, und nichts in der
Antwort sagt das: Aufrufer erhalten ein schlichtes 429, ob sie es nun verursacht haben oder jemand anderes.
Die schreibgeschützte Oberfläche#
Alles, was die Chain von außen beobachtet — ein Explorer, ein Monitor, ein Indexer —, liest sie hier. Der Node reicht Bytes heraus und nie Deutung.
Jede Route mit ihren Parametern und den Feldern, die sie zurückgibt, steht in der RPC-API-Referenz.
| Endpunkt | Antwortet |
|---|---|
/status, /head | Identität der Spitze, Höhe, State Root |
/block?height=N oder ?id=0x… | Ein Block als JSON, mit canonical, orphaned und confirmations |
/block?…&format=ssz | Derselbe Block als kanonische SSZ-Bytes, application/octet-stream |
/params | Der aktive Parametersatz und seine Konsenswurzel |
/cell, /balance | Zustand an der Spitze |
/fees | Grundgebühren und die geltenden elastischen Obergrenzen |
/mempool | Pool-Zähler; ?limit=N ergänzt bis zu 1000 anstehende IDs, niedrigste zuerst |
/cert | Was aus einem Zertifikat geworden ist: ausstehend, verworfen, aufgenommen oder unbekannt |
/sequencer, /directory | Welchen Schlüssel jeder Contract als seinen Sequencer registriert hat und die URL, die jeder Schlüssel im Verzeichnis angekündigt hat — die Aussage dieses Nodes über seinen eigenen Zustand, nicht authentifiziert |
/code, /storage, /call | Code und Storage eines Contracts sowie ein Probelauf eines Aufrufs (POST, ändert nichts) |
/network, /metrics | Verbindungsform und Zähler |
/submit, /openintent, /servicecard | Die Schreibvorgänge: ein Zertifikat, ein Open Intent, eine Service Card. Keiner gewährt etwas |
Whitepaper §9 macht die Byte-, Gas- und Zertifikatsobergrenzen des Blocks zu Funktionen von
T, dem sequenziellen Ziel, und T ist Konsenszustand, den der Epochen-Controller bewegt.
/params trägt daher nur die Genesis-Werte —
block_byte_limit_genesis und Verwandte —, die den Startpunkt von T und den
Boden bilden, auf den es zurückfallen kann, nie die geltende Grenze. Diese Zahlen bleiben für immer
echt, ein Wert davon als “das Blockgrößenlimit” gelesen ist also stillschweigend falsch, und zwar um bis zu den Abstand
zwischen dem Genesis-Wert von 2,5 MB und der Kapazitätswand bei 8 MB. /fees liefert das aktuelle
T und die vier daraus abgeleiteten Obergrenzen, sodass die Ableitung geprüft statt geglaubt werden kann.
Warum die Bytes zählen#
blake3("zcd/block/v1" ‖ header_bytes) ist die Block-ID, ein Beobachter, der IDs
aus dem Ausgelieferten neu herleitet, stimmt also entweder mit dem Netzwerk überein oder merkt es sofort. So
kommt man auch an die Ergebnisse je Zertifikat: ob ein Zertifikat angewendet, übersprungen und abgerechnet
oder gedroppt wurde, wird innerhalb des Folds berechnet und nie dauerhaft gespeichert, denn eine
Zeile je Zertifikat für immer, in einem Speicher, der im Arbeitsspeicher liegt, kann man einem Node nicht
zumuten. Ein Beobachter, der die Ergebnisse will, foldet den Block selbst.
Was hier nicht ist und nicht sein wird#
- Keine beliebige Zustandsiteration. Keine Adressliste, kein Präfix-Scan, keine Rich List.
- Kein historischer Zustand.
/cellund/balancelesen die Spitze; “ - Kein Admin-, Reindex- oder Debug-Aufruf, weil es nichts zu privilegieren gibt.
Die Aggregation gehört dem, was zusieht, einmal beim Einlesen in dessen eigene Datenbank berechnet.
Statuscodes#
Lesezugriffe beantworten GET und HEAD und weisen jedes andere Verb mit
405 ab. Eine wohlgeformte Frage mit negativer Antwort — eine Höhe, die die Chain noch nicht
erreicht hat, eine unbekannte ID, der Body eines Blocks, der eine Reorg verloren hat — ist 404; nur eine
fehlerhafte Anfrage ist 400. Ein Datensatz, den der Node geschrieben hat und nicht mehr zurücklesen kann, ist
500, nicht 400: der Fehler liegt an der Platte des Nodes, und einem Aufrufer zu sagen, seine
Anfrage sei fehlerhaft, lädt ihn ein, eine stets wohlgeformte Anfrage nicht mehr zu wiederholen. Ein Poller
sollte nie Fließtext lesen müssen, um Abwesenheit von Fehler oder den eigenen Fehler von dem des Nodes zu unterscheiden.
Reorgs#
Die Suche nach Höhe antwortet nur für die kanonische Chain. Ein Block, der eine Reorg verliert, behält seinen Header
und verliert seinen Body, seine ID lässt sich also weiterhin auflösen, kommt als orphaned markiert zurück und trägt weiterhin
den Elternverweis zurück zum Fork-Punkt — was die Suche nach ID zum einzigen
reorg-sicheren Weg macht.
Erreichbarkeit, und warum sie mehr zählt, als es aussieht#
Zycord betreibt keine NAT-Traversierung. Ein Node mit --listen auf einem erreichbaren Port ist
ein Ort, von dem aus andere bootstrappen können, und der Anteil solcher Nodes ist die falsifizierbare
Bedingung, auf der die gesamte Entscheidung gegen Traversierung ruht.
# periphery: nothing to configure
zycordd --testnet --dir ./testnet
# core: bind a port, and make sure it is actually reachable
zycordd --testnet --dir ./testnet \
--listen 0.0.0.0:9421 --advertise <public-address>:9421
--advertise fällt auf --listen zurück, --listen
0.0.0.0:9421 ohne --advertise veröffentlicht also 0.0.0.0:9421, was
niemand anwählen kann. Eine falsche Adresse verbreitet sich über den Peer-Austausch und kostet jeden Node, der sie
probiert, einen Wählversuch. Wenn Sie keinen Port weiterleiten können, lassen Sie --listen weg und bleiben Sie Peripherie.
Prüfen, ob es geklappt hat#
curl -s localhost:9420/network
{"enabled":true,"peers":8,"listening":true,"inbound":5,"outbound":3,"reachable":true}
listening: true mit inbound: 0, nachdem der Node ein paar
Minuten läuft, heißt, dass der Port tatsächlich nicht erreichbar ist: der Prozess ist gebunden und
wartet, und es kommt nichts an. Das sieht in jeder anderen Ansicht gesund aus, und genau deshalb gibt es diesen
Endpunkt.
Bootstrap-Adressen aus einer Datei#
zycordd --testnet --dir ./testnet --peers-file peers.txt
Eine Adresse pro Zeile; #-Kommentare und Leerzeilen werden ignoriert, und die Liste wird
mit den eingebauten Seeds des Netzwerks zusammengeführt. --peers nimmt dasselbe als
kommagetrenntes Argument, und --no-seeds lässt die eingebauten weg, ohne die beiden anderen zu berühren.
Die Wallet-Oberfläche über einen SSH-Tunnel#
Ein Node hält keinen Schlüssel und wird nie einen halten. Die Wallet ist ein eigener Prozess, und auf einem Server ist sie
zcd ui:
zcd ui --key wallet.json --no-open
Das gibt eine URL aus und liefert sie auf 127.0.0.1:9430 aus. Sie bindet an Loopback und
weist alles andere ab, ohne Flag zum Übersteuern. Der Prozess hinter diesem Listener hält
einen entsperrten privaten Schlüssel und authentifiziert sich mit einem Bearer-Token in einer URL, was für einen
Socket ausreicht, den nur der lokale Rechner öffnen kann, und für nichts sonst. Ihn von anderswo
zu erreichen, ist Aufgabe von ssh, und ssh kann das besser, als dies es könnte:
# on your own machine
ssh -L 9430:127.0.0.1:9430 <host>
Öffnen Sie dann die URL, die der Server ausgegeben hat. Das Token reitet im Fragment der URL mit,
es erreicht also nie den Server, ein Log oder einen Referer-Header — behandeln Sie die URL als
das Geheimnis, das sie ist. Sie ist bei jedem Lauf neu, und Strg-C löscht den Schlüssel und beendet die Auslieferung.
Das lokale Ende der Weiterleitung muss nicht Port 9430 sein: die Oberfläche prüft den
Hostnamen im Host-Header und bewusst nicht den Port. Der Hostname ist das,
worauf es ankommt — er ist es, der DNS-Rebinding verhindert, den eigentlichen Angriff auf einen Server auf
Loopback, denn jede Seite in Ihrem Browser kann Anfragen an 127.0.0.1 stellen, und das eine,
was sie nicht fälschen kann, ist der Name, über den sie angesteuert wurde.
zcd uiDer obige Rat zur RPC des Nodes betrifft eine Oberfläche, die keinen Schlüssel hält und keine Befugnis gewährt. Diese hier hält einen Schlüssel. Es gibt keine Variante, sie offenzulegen, die eine gute Idee wäre, und der Tunnel kostet ein Flag.
Zwei weitere wissenswerte Formen:
zcd ui --lockedstartet, ohne im Terminal nach einer Passphrase zu fragen; stattdessen fragt der Browser. Nützlich, wenn das Terminal geteilt oder protokolliert wird.zcd ui --lock-after 5mverkürzt die Leerlaufsperre, die den Schlüssel an Ort und Stelle löscht — der Seed wird überschrieben, nicht bloß dereferenziert.
Der Forced Path und der Betrieb eines Sequencers#
Ab Ära 1 ist ein minender Node auch der Ausführende des Forced Path. Er sammelt die Open Intents, die er empfängt, und führt in den Blöcken, die er baut, diejenigen, die er wählt, gegen den Zustand am Blockende aus und hängt sie als erzwungene Zertifikate an, höchstens 16 pro Block. Das ist standardmäßig eingeschaltet und wird bezahlt: die Zahlung jedes Open Intents deckt seine Gebühren, die Basisgebühr wird verbrannt, und der Rest geht an den Producer.
zycordd ... --mine --forced-min-margin DROPS # execute only open intents that pay this much beyond their fees
zycordd ... --mine --no-forced # execute none
Es auszuschalten kostet diesen Node die Zahlungen und kostet die Nutzer nichts, was sie nicht anderswo bekommen können: jeder andere Producer darf dieselben Open Intents mitnehmen.
Ein Node kann auch als Sequencer für die Contracts auftreten, die seinen Schlüssel registriert haben: er füllt ihre Intents aus der persistenten Adresse dieses Schlüssels und antwortet Wallets über die Sequencer-Schnittstelle, auf einem eigenen Listener, der nicht die RPC ist.
zcd wallet new --out seq.key # its first persistent key is the sequencer key
zycordd --dir ./data --sequencer --sequencer-key seq.key \
--sequencer-listen 0.0.0.0:PORT \
--sequencer-tls-cert cert.pem --sequencer-tls-key key.pem \
--sequencer-endpoint https://HOST:PORT --sequencer-announce
--sequencer-announce hält den Endpunkt im On-Chain-Verzeichnis und erneuert ihn,
bevor er verfällt; Wallets verlangen https, außer bei Loopback und .onion. Die Preis-Flags (--sequencer-flat, --sequencer-seq-rate,
--sequencer-par-rate) legen fest, was ein Intent zahlen muss. Die Schlüsseldatei bleibt verschlüsselt, und ihre
Passphrase kommt über die Standardeingabe oder die Umgebung, nie über die Kommandozeile. Die Einlage des Fills
und seine Skips gehen zulasten dieser persistenten Adresse, halten Sie sie also gedeckt: ein Sequencer, der
einen Fill auf veraltetem Zustand veröffentlicht, zahlt die Skip-Gebühr, und seine Nutzer zahlen nichts. Ein Contract wird bedient, sobald sein Eintrag diesen Schlüssel nennt, gesetzt von seinem Admin mit zcd wallet register-sequencer oder von seinem eigenen Code. Das vollständige Verfahren steht in docs/OPERATING.md.
Peer-Identität und Anonymität#
Der Node erzeugt bei jedem Start einen frischen Ed25519-Peer-Schlüssel und schreibt ihn nie auf die Platte. Er ist aus nichts abgeleitet — nicht aus Ihrer Wallet, nicht aus einem Seed, nicht aus irgendetwas anderem auf dem Rechner. Ein Neustart tauscht ihn aus.
Wenn Sie einen Node auf eine Weise betreiben, die Ihnen wichtig ist, ist das, was Sie deanonymisiert, nicht der Schlüssel. Es ist die Adresse. Ein Bootstrap-Node, den Sie auf zu Ihnen zurückverfolgbarer Infrastruktur betreiben, ist ein Deanonymisierungsvektor, den keine noch so gute Schlüsselhygiene behebt.
Datenverzeichnis#
data/
chain/ blocks, state, and the write-ahead log
peers.json the persisted peer store
Der Peer-Speicher wird absichtlich dauerhaft gehalten: ein Node, der nach jedem Neustart bei null anfängt, gibt einem Angreifer eine frische Gelegenheit, ihn zu füllen. Ihn zu löschen ist gefahrlos, wirft das aber weg.
Der Node übersteht es, jederzeit abgeschossen zu werden — dafür ist das Write-Ahead-Log da, und es wird getestet, indem Nodes unter Netzwerkchaos zufällig abgeschossen werden. Er überlebt es nicht, wenn sein Datenverzeichnis unter ihm verändert wird: beim Start berechnet er den State Root neu und verweigert den Betrieb, wenn der gespeicherte abweicht.
Ein Netzwerk wählen#
| Flag | Netzwerk | Chain-ID | Engine |
|---|---|---|---|
| (keines) | Mainnet | 1 | randomx-v2 |
--testnet | öffentliches Testnet | 2 | randomx-v2 |
--devnet | lokales Devnet | 1337 | Entwicklungs-Engine |
Die Parameter sind in die Binärdatei eingebettet und werden nicht aus einem Pfad gelesen. Jeder
Teilnehmer eines öffentlichen Netzwerks muss dieselben Bytes tragen, sonst sind sie nicht in einem Netzwerk, und eine
über --params übergebene Datei ist eine Datei, die auseinanderdriftet. Ein anderes Netzwerk ist ein anderes
Netzwerk: die beiden trennen sich beim Handshake, und ein Datenverzeichnis mit der falschen Chain wird beim Start
abgewiesen statt stillschweigend vermischt.
Ein beschädigtes Datenverzeichnis wiederherstellen#
zycordd bringt einen Reparaturpfad für ein Datenverzeichnis mit, gegen das ein
Node den Start verweigert. Halten Sie den Node zuerst an — zycordd repair --dir ./data
nimmt die Verzeichnissperre und verweigert sich, solange ein Node sie hält — und fragen Sie dann
mit --dry-run, was passiert ist, bevor Sie etwas ändern. Nur eine Schadensklasse lässt sich
an Ort und Stelle reparieren; für die anderen ist das Mittel ein Resync, und der Trockenlauf ist das,
was Ihnen sagt, welche Sie haben.