Ejecutar un nodo
La guía del operador de zycordd. El protocolo que habla es la especificación de red; esta página trata de ejecutarlo.
La versión de una línea#
zycordd --devnet --dir ./devnet
Eso es un nodo completo: valida cada bloque desde la génesis, sirve a los pares y responde a un
RPC de solo lectura en 127.0.0.1:9420. Para unirte a la red que está funcionando de verdad, véase
Testnet pública — y ten en cuenta que necesita el
binario -randomx.
Lo que el nodo no tiene#
No hay material de clave en el proceso del nodo, jamás. No acepta semilla, ni
frase de paso, ni archivo de clave. La firma ocurre en zcd, y el único endpoint de escritura
del nodo — /submit — no concede autoridad alguna: un certificado enviado se
valida exactamente igual que uno que llegue de un desconocido.
Tampoco hay ningún endpoint privilegiado, porque no hay nada que privilegiar. Ninguna clave puede pausar, actualizar, censurar ni acuñar, así que no hay llamada que exponer.
El limitador se indexa por el par de transporte y nunca por X-Forwarded-For: un limitador
que confía en una cabecera que fija el cliente es un limitador que el cliente apaga. Detrás de un proxy, por tanto, cada
petición llega con la dirección del proxy, así que el límite por cliente deja de ser por cliente
y se convierte en un único cubo compartido de 600 por minuto para todos a la vez — y una sola persona que llame
puede vaciarlo para todas las demás. Eso es discutiblemente peor que no tener límite alguno, y nada en la
respuesta lo dice: quien llama recibe un 429 corriente tanto si lo causó él como si fue otro.
La superficie de solo lectura#
Cualquier cosa que observe la cadena desde fuera — un explorador, un monitor, un indexador — la lee aquí. El nodo entrega bytes y nunca interpretación.
| Punto de acceso | Responde |
|---|---|
/status, /head | Identidad de la punta, altura, raíz de estado |
/block?height=N o ?id=0x… | Un bloque en JSON, con canonical, orphaned y confirmations |
/block?…&format=ssz | El mismo bloque como bytes SSZ canónicos, application/octet-stream |
/params | El conjunto de parámetros activo y su raíz de consenso |
/cell, /balance | Estado en la punta |
/fees | Comisiones base, y los techos elásticos en vigor |
/mempool | Contadores de la reserva; ?limit=N añade hasta 1000 ids pendientes, de menor a mayor |
/network, /metrics | Forma de las conexiones y contadores |
/submit | La única escritura, y no concede nada |
El §8.1 del whitepaper hace que los techos de bytes, de gas y de certificados del bloque sean funciones de
T, el objetivo secuencial, y T es estado de consenso que mueve el controlador de época.
Por eso /params lleva solo los valores de la génesis —
block_byte_limit_genesis y compañía — que son donde T empieza y el
suelo al que puede decaer, nunca el límite en vigor. Esos números siguen siendo reales
para siempre, así que leer uno como "el límite de tamaño de bloque" es un error silencioso, y de hasta la distancia
entre el valor de génesis de 2,5 MB y el muro de capacidad de 8 MB. /fees sirve la T
en vigor y los cuatro techos derivados de ella, de modo que la derivación pueda comprobarse en vez de confiarse.
Por qué importan los bytes#
blake3("zcd/block/v1" ‖ header_bytes) es el id del bloque, así que un observador que
vuelve a derivar los ids de lo que se le sirvió o coincide con la red o se entera de inmediato. Es
también como se obtienen los desenlaces por certificado: si un certificado se aplicó, se omitió y se
cobró, o se descartó, se calcula dentro del fold y nunca se persiste, porque una
fila por certificado para siempre, en un almacén que vive en memoria, no es algo que se le pueda pedir a un nodo
que cargue. Un observador que quiera los desenlaces hace él mismo el fold del bloque.
Lo que no está aquí, y no estará#
- Sin iteración arbitraria del estado. Sin lista de direcciones, sin barrido por prefijo, sin lista de ricos.
- Sin estado histórico.
/celly/balanceleen la punta; "saldo a la altura H" implica una historia retenida que el nodo no guarda. - Sin llamada de administración, reindexado ni depuración, porque no hay nada que privilegiar.
La agregación pertenece a lo que sea que esté observando, calculada una sola vez en la ingesta hacia su propia base de datos.
Códigos de estado#
Las lecturas responden a GET y HEAD y rechazan cualquier otro verbo con
405. Una pregunta bien formada con respuesta negativa — una altura que la cadena no ha
alcanzado, un id desconocido, el cuerpo de un bloque que perdió una reorganización — es 404; solo una
petición malformada es 400. Un registro que el nodo escribió y ya no puede releer es
500, no 400: la culpa es del disco del nodo, y decirle a quien llama que su
petición estaba malformada le invita a dejar de reintentar una petición que siempre estuvo bien formada. Quien consulta
nunca debería tener que leer prosa para distinguir la ausencia del error, o su propio fallo del del nodo.
Reorganizaciones#
La búsqueda por altura responde solo por la cadena canónica. Un bloque que pierde una reorganización conserva su cabecera
y pierde su cuerpo, así que su id sigue resolviéndose, vuelve marcado como orphaned, y sigue
llevando el enlace al padre hasta el punto de bifurcación — lo que hace de la búsqueda por id la única
vía segura ante reorganizaciones.
Alcanzabilidad, y por qué importa más de lo que parece#
Zycord no hace travesía de NAT. Un nodo con --listen en un puerto alcanzable es
un sitio desde el que otros pueden arrancar, y la proporción de nodos así es la
condición falsable sobre la que descansa toda la decisión de no hacer travesía.
# 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 recurre a --listen, así que --listen
0.0.0.0:9421 sin --advertise publica 0.0.0.0:9421, que
nadie puede marcar. Una dirección equivocada se propaga por el intercambio de pares y le cuesta una llamada a cada nodo
que la intenta. Si no puedes abrir un puerto, deja --listen desactivado y sé periferia.
Comprobar si funcionó#
curl -s localhost:9420/network
{"enabled":true,"peers":8,"listening":true,"inbound":5,"outbound":3,"reachable":true}
listening: true con inbound: 0 después de que el nodo lleve
unos minutos levantado significa que el puerto no es realmente alcanzable: el proceso está enlazado y
esperando, y no llega nada. Eso parece sano en cualquier otra vista, que es por lo que este
endpoint existe.
Direcciones de arranque desde un archivo#
zycordd --testnet --dir ./testnet --peers-file peers.txt
Una dirección por línea; los comentarios con # y las líneas en blanco se ignoran, y la lista se
fusiona con las semillas incorporadas de la red. --peers toma lo mismo como argumento
separado por comas, y --no-seeds descarta las incorporadas conservando ambas.
La interfaz del monedero, sobre un túnel ssh#
Un nodo no guarda ninguna clave y nunca la guardará. El monedero es un proceso aparte, y en un servidor es
zcd ui:
zcd ui --key wallet.json --no-open
Eso imprime una URL y la sirve en 127.0.0.1:9430. Se enlaza a loopback y
rechaza cualquier otra cosa, sin flag para anularlo. El proceso tras ese escuchador guarda
una clave privada desbloqueada y se autentica con un token bearer en una URL, lo cual es adecuado para un
socket que solo la máquina local puede abrir y no es adecuado para nada más. Alcanzarlo desde
otro sitio es tarea de ssh, y ssh lo hace mejor de lo que lo haría esto:
# on your own machine
ssh -L 9430:127.0.0.1:9430 <host>
Luego abre la URL que imprimió el servidor. El token viaja en el fragmento de la URL,
así que nunca llega al servidor, ni a un registro, ni a una cabecera Referer — trata la URL como
el secreto que es. Es nueva en cada ejecución, y Ctrl-C borra la clave y deja de servir.
El extremo local del reenvío no tiene por qué ser el puerto 9430: la interfaz comprueba el
nombre de host en la cabecera Host y deliberadamente no el puerto. El nombre de host es
lo que importa — es lo que bloquea el rebinding de DNS, que es el ataque real contra un servidor en
loopback, ya que cualquier página de tu navegador puede hacer peticiones a 127.0.0.1 y lo único
que no puede falsificar es el nombre al que se navegó.
zcd uiEl consejo de arriba sobre el RPC del nodo trata de una superficie que no guarda ninguna clave y no concede ninguna autoridad. Esta guarda una clave. No hay ninguna versión de exponerla que sea buena idea, y el túnel cuesta un flag.
Otras dos formas que conviene conocer:
zcd ui --lockedarranca sin pedir una contraseña en la terminal; la pide el navegador en su lugar. Útil cuando la terminal es compartida o queda registrada.zcd ui --lock-after 5macorta el bloqueo por inactividad, que borra la clave en su sitio — la semilla se sobrescribe, no se limita a dejar de referenciarse.
Identidad de par y anonimato#
El nodo genera una clave de par Ed25519 nueva en cada arranque y nunca la escribe en disco. No se deriva de nada — ni de tu monedero, ni de una semilla, ni de ninguna otra cosa de la máquina. Reiniciar la rota.
Si estás ejecutando un nodo de una forma que te importa, lo que te desanonimiza no es la clave. Es la dirección. Un nodo de arranque que ejecutes en infraestructura rastreable hasta ti es un vector de desanonimización que ninguna higiene de claves arregla.
Directorio de datos#
data/
chain/ blocks, state, and the write-ahead log
peers.json the persisted peer store
El almacén de pares se persiste a propósito: un nodo que arranca en blanco tras cada reinicio le da al atacante una oportunidad nueva de llenarlo. Borrarlo es seguro pero desecha eso.
El nodo sobrevive a que lo maten en cualquier momento — para eso está el registro de escritura anticipada, y se prueba matando nodos al azar bajo caos de red. No sobrevive a que le editen el directorio de datos por debajo: al arrancar recalcula la raíz de estado y se niega a funcionar si la almacenada discrepa.
Elegir una red#
| Flag | Red | Chain id | Motor |
|---|---|---|---|
| (ninguno) | mainnet | 1 | randomx-v1 |
--testnet | testnet pública | 2 | randomx-v1 |
--devnet | devnet local | 1337 | motor de desarrollo |
Los parámetros están incrustados en el binario, no se leen de una ruta. Todo
participante de una red pública tiene que llevar los mismos bytes o no están en una misma red, y un
archivo pasado con --params es un archivo que se desvía. Una red distinta es una red
distinta: las dos se desconectan en el saludo, y un directorio de datos que contenga la cadena equivocada se
rechaza al arrancar en lugar de mezclarse en silencio.
Recuperar un directorio de datos dañado#
zycordd trae una vía de reparación para un directorio de datos contra el que un
nodo se niega a arrancar. Detén el nodo primero — zycordd repair --dir ./data toma el
cerrojo del directorio y se niega mientras lo tenga un nodo — y luego pregunta qué ha pasado con
--dry-run antes de cambiar nada. Solo una clase de daño se repara en el sitio; para las
demás el remedio es resincronizar, y la ejecución en seco es lo que te dice cuál tienes.