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.
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.
| 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 |
/network, /metrics | Verbindungsform und Zähler |
/submit | Der einzige Schreibvorgang, und er gewährt nichts |
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.
/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.
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-v1 |
--testnet | öffentliches Testnet | 2 | randomx-v1 |
--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.