ZYCORD Doku
Deutsch
ZycordDokuEinen Node betreiben

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. Um dem Netzwerk beizutreten, das tatsächlich läuft, siehe Öffentliches Testnet — und beachten Sie, dass es die -randomx-Binärdatei braucht.

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 der einzige Schreib-Endpunkt des Nodes — /submit — gewährt keine Befugnis: ein eingereichtes Zertifikat wird genauso validiert wie eines, das von einem Fremden eintrifft.

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.

Wenn Sie die RPC hinter einen Proxy stellen, begrenzen Sie die Rate am Proxy

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.

EndpunktAntwortet
/status, /headIdentität der Spitze, Höhe, State Root
/block?height=N oder ?id=0x…Ein Block als JSON, mit canonical, orphaned und confirmations
/block?…&format=sszDerselbe Block als kanonische SSZ-Bytes, application/octet-stream
/paramsDer aktive Parametersatz und seine Konsenswurzel
/cell, /balanceZustand an der Spitze
/feesGrundgebühren und die geltenden elastischen Obergrenzen
/mempoolPool-Zähler; ?limit=N ergänzt bis zu 1000 anstehende IDs, niedrigste zuerst
/network, /metricsVerbindungsform und Zähler
/submitDer einzige Schreibvorgang, und er gewährt nichts
Die Obergrenzen sind keine Parameter

Whitepaper §8.1 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. /cell und /balance lesen 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
Kündigen Sie eine Adresse an, die antwortet, oder gar keine

--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.

Stellen Sie keinen Reverse Proxy vor zcd ui

Der 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 --locked startet, 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 5m verkürzt die Leerlaufsperre, die den Schlüssel an Ort und Stelle löscht — der Seed wird überschrieben, nicht bloß dereferenziert.

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#

FlagNetzwerkChain-IDEngine
(keines)Mainnet1randomx-v1
--testnetöffentliches Testnet2randomx-v1
--devnetlokales Devnet1337Entwicklungs-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.