ZYCORD documentación
Español
ZycordDocumentaciónArquitectura

Arquitectura

El acompañante de ingeniería del whitepaper — cómo el nodo de referencia realiza el diseño, y las decisiones que hay detrás. Explicativo, no normativo.

Este documento no es normativo

El protocolo son los archivos de parámetros y los vectores de referencia, más las reglas nombradas allí donde estén definidas; la especificación de red lleva los requisitos de la capa entre pares. Esta página explica esa superficie y registra las decisiones que hay detrás. Donde discrepe con la superficie normativa, gana la superficie normativa y este texto se corrige. El compañero de ingeniería completo es docs/ARCHITECTURE.md.

Dos predicados, dos motores#

La vida de un certificado, de principio a fin:

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

La validez es sin estado y paralela: se ejecuta una vez por certificado y por nodo, antes de los bloques e independientemente de ellos. La aplicabilidad es con estado y secuencial: se ejecuta dentro del fold en la posición confirmada del certificado. El minero no ejecuta nada de lo que recibe: ordena hashes que ya ha visto validados y resuelve el proof of work o, tras la transición a proof of stake en la era 2, firma el slot para el que fue sorteado. Su única ejecución es la del camino forzado, desde la era 1: puede ejecutar intents abiertos contra el estado al final de su bloque y añadir los resultados, como máximo 16 por bloque, que luego cada nodo comprueba como cualquier otro certificado. El fold es un bucle estrecho sobre un conjunto de trabajo en memoria — comparar, sumar, escribir — y no ejecuta nada en absoluto.

Principios de ingeniería#

Principio
P1El fold es sagrado. La función de transición de estado vive en un único paquete puro sin E/S, sin relojes, sin gorutinas, sin iteración de mapas y sin coma flotante. Es el único código cuyos fallos no tienen arreglo a posteriori. Todo lo demás en el nodo es fontanería reemplazable.
P2El determinismo gana al rendimiento. Cualquier optimización que arriesgue indeterminismo se rechaza en el código de consenso. El rendimiento corresponde a la tubería sin estado, donde es seguro.
P3Sin claves de administración, sin RPC privilegiado. No existe ninguna ruta de código por la que una clave pueda pausar, actualizar, acuñar o reorganizar. Si no está en las reglas del fold, no existe.
P4Superficie de consenso pequeña. core/ no importa nada fuera de la biblioteca estándar. El resto del nodo puede usar bibliotecas del ecosistema; el núcleo no.
P5La especificación primero. Los vectores de referencia son el protocolo. El código Go es una implementación de referencia; una implementación independiente que pase los vectores es un par, no un fork. Esto es lo que permite que el mantenedor acabe siendo nadie en particular.
P6Reproducible desde la v0.1. Cadena de herramientas de Go fijada, -trimpath, dependencias fijadas. La confianza pasa del binario al código, que es la única confianza que un autor anónimo puede ofrecer.
P7Un binario, condicionado por altura. La máquina, la maquinaria de secuenciadores y el camino forzado están compilados en cada versión y se rechazan por debajo de su altura de activación, h1_vm (80.640 en mainnet), que Validate exige que caiga en un límite de época. La Era 1 llega porque la cadena alcanzó un número, nunca porque se pidiera a los operadores que actualizaran. Las eras posteriores no están en el binario: cada una se publica como su propia versión, mediante hard fork.

P3 es el que un lector escéptico debería apretar con más fuerza, y la tesorería es donde apretarlo: el génesis no contiene ninguna clave ni ninguna vía de gasto, así que no hay privilegio que tener, delegar o robar. La tesorería no puede gastarse antes de la altura 259.200, y la regla que la abra, sea cual sea, llega como un cambio de consenso — el mismo mecanismo social que cualquier otro, sujeto al mismo rechazo, y capaz de mover exactamente una celda incluso entonces. La parte del fundador es un abono, no una clave: el 3% de cada subsidio a una dirección fijada en el archivo de parámetros, hasta la altura 3.153.600, que la era 2 lee como la época 1.095. Esa dirección guarda monedas y no alcanza nada más. Una clave de administración es la que existe antes de que nadie haya dado su consentimiento y lo alcanza todo.

Primitivas criptográficas#

RolElecciónPor qué
FirmasEd25519Verificación por lotes (la rampa hacia la GPU), sin maleabilidad, claves diminutas. Reglas estrictas fijadas en el génesis: codificaciones canónicas obligatorias para la clave pública y R, la clave pública libre de torsión y no de orden bajo, y verificación sin cofactor.
HashingBLAKE3Ids de certificado y de bloque, direcciones, raíz de estado. Lo bastante rápido para hacer hash a la velocidad de la retransmisión; amigable con el paralelismo para la raíz de estado de época.
Proof of workRandomXOptimizado para CPU. El único cgo del árbol, tras una etiqueta de compilación, y ausente de una compilación sin ella. pow_engine está en la raíz de consenso, así que un binario con el motor equivocado se niega a arrancar en lugar de aceptar la prueba equivocada.
Separación de dominiosobligatoriaTodo hash es blake3(tag ‖ payload). Las firmas firman sobre el chain id y la raíz de consenso, lo que mata la repetición tanto entre redes como entre dos encarnaciones de una misma red.

El rechazo de torsión es lo que hace segura la vía por lotes. Una clave de orden mixto no es de orden pequeño, así que ninguna lista de bloqueo la alcanza, y es exactamente donde un verificador por lotes con cofactor y un verificador individual sin cofactor discrepan. Con la clave y R en el subgrupo de orden primo, los dos son demostrablemente equivalentes — así que un verificador por lotes puede usar cofactor siempre que aplique las mismas reglas de codificación y torsión antes de agrupar. Esa obligación es el precio de la elección, y el verificador por lotes aún no existe.

Codificación canónica e identificadores#

Todos los objetos de consenso son contenedores SSZ: una única codificación canónica en bytes, sin orden de mapas, sin ambigüedad de campos opcionales, desplazamientos fijos para un análisis parcial barato, y merkleización nativa.

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

Los dos primeros son resúmenes distintos que responden a preguntas distintas, y una implementación que use uno donde corresponde el otro está rota en términos monetarios. El id responde a si esta autorización ya ha sido cobrada; el hash del ejemplar responde a si estos bytes lo demuestran. Los dos nunca comparten clave.

Las firmas quedan fuera de la preimagen del id porque una firma es una demostración aleatorizada: el firmante elige el nonce, cada nonce produce otra firma válida y perfectamente canónica sobre el mismo cuerpo, y ningún verificador puede comprobar cuál se usó. Si estuvieran dentro, una autorización tendría ilimitados ids, cada uno cobrable, cada uno producible por cualquiera uno de sus firmantes requeridos al margen de la autoridad de los demás.

La puja de comisión está dentro del id, y eso es una decisión monetaria

Una puja fuera del id sería una puja que cualquiera en tránsito podría reescribir — inflada para quemar el saldo del firmante a través de la comisión base, o puesta a cero para dejar el certificado fuera de todo bloque. Lo que paga un certificado es parte de lo que su firmante autorizó, así que se resume y se firma. Un cambio de codificación posterior que "saque la comisión del cuerpo firmado por flexibilidad de retransmisión" parecería una optimización y sería un vector de robo.

La regla que se sigue: analiza, no valides dos veces. La decodificación impone la forma canónica, así que un objeto decodificado es estructuralmente válido por construcción y los motores de reglas nunca vuelven a comprobar la forma.

El modelo de celdas#

Una celda es el valor en un slot. Los valores de celda son enteros sin signo de 256 bits almacenados en big-endian en 32 bytes. Las celdas ausentes se leen como cero — cero es ausencia, lo cual es un requisito de consenso y no una comodidad de implementación: mantiene la raíz de estado como función del estado y no de la historia que lo produjo.

Por separado, el protocolo mantiene un registro de direcciones gastadas: un conjunto de consenso permanente de direcciones de un solo uso cuya autoridad de firma se ha quemado.

Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
VersiónTipoAutorización de débito
0x01usuario de un solo usoFirma del propietario; cualquier certificado que debite debe llevar además un MARK_SPENT explícito. Después de que se aplique, toda lectura y escritura bajo la dirección falla para siempre.
0x02usuario persistenteFirma del propietario; reutilizable para siempre. Nunca puede entrar en el registro de gastadas.
0x03activoGobernado por las celdas de autoridad inmutables del activo.
0x00protocoloSolo el fold: baliza de época, celdas de comisión base, anillo de madurez del coinbase, celda de tesorería, contadores de secuenciador.
0x05contratoNadie por clave: ningún hash de clave da una dirección así. Deciden el código del contrato y las reglas del fold.
0x04no definidoNo existe ninguna celda bajo él; un certificado que nombra una dirección así es inválido.

El acceso tipado es de donde viene el paralelismo. Una lectura declara cómo restringe su celda — EXACT, o una de las guardas GUARD_GE, GUARD_LE, GUARD_EQ — y una escritura declara lo que hace: SET, DELTA_ADD, DELTA_SUB o MARK_SPENT. Solo EXACT alimenta el cálculo, y solo EXACT queda obsoleta cuando la celda cambia; una guarda no devuelve nada y un delta no lee nada, así que la reejecución sigue siendo una función pura de las lecturas exactas, y cualquier número de débitos y créditos con guarda sobre un mismo slot conmutan. Una disciplina por slot y por certificado: un set y un delta nunca comparten slot. Escribir cero borra la celda, así que un slot nunca escrito y un slot escrito a cero son el mismo slot.

Un solo uso frente a persistente es una política del monedero expuesta como un bit de la dirección, no dos libros contables. Los monederos usan por defecto una dirección de un solo uso nueva por cada pago entrante, el camino sin contención; quien quiera una cuenta usa la versión persistente. Las recompensas de minería deben ir a una persistente: el consenso acepta cualquiera de los dos tipos como pago, pero una dirección de un solo uso quema todas las recompensas que guarda en el momento en que se gasta, así que el nodo rechaza una como dirección de pago.

La entrada del registro nunca se compacta. Los valores de celda bajo una dirección gastada pueden podarse tras el horizonte de deshacer, pero la entrada que registra la dirección como gastada es lo que impide que se resucite. Es estado de consenso de solo anexión, unos 33 bytes por dirección, para siempre — el problema abierto que el protocolo reconoce, compartido estructuralmente con todo diseño de conjunto de anuladores.

Validez sin estado, y la ley de facturación#

Las reglas V se ejecutan sobre cada certificado, en paralelo, antes de la admisión al mempool y durante la verificación del bloque, y no requieren ningún estado: forma canónica y chain id, con cada lista dentro de su límite; cada firma verificando bajo Ed25519 estricto sobre la raíz de firma, con el conjunto de firmas mínimo además de suficiente; la reejecución del programa contra las lecturas declaradas produciendo exactamente las escrituras declaradas; autorización para cada escritura que la necesite, y una firma sobre la celda de depósito sin condiciones; un depósito que sea el saldo nativo de una dirección de usuario y cubra el peor caso del certificado; autoconsistencia, de modo que ninguna escritura contradiga una lectura del mismo slot y ninguna dirección de un solo uso debitada escape a ser marcada como gastada; ninguna escritura en una celda de protocolo; un byte de versión definido en cada dirección nombrada; y, en un contrato cuya clave de secuenciador no es cero, la firma de esa clave sobre la ruta exacta.

La ley de facturación del sistema es una sola frase, y es lo que hay que retener:

Una firma, como mucho un cobro, nunca en una posición que su firmante no pudiera evitar.

El fold ordena un bloque en tres clases y no deja ninguna discreción al productor: primero los fills, en el orden de los pipelines de secuenciador a los que pertenecen; después todos los demás certificados por (deposit address, Seq, id); por último los certificados forzados, por id. Cada certificado tiene entonces exactamente uno de cuatro resultados, y los vectores de referencia los nombran:

ResultadoCuándoQuién paga
APPLIEDToda lectura declarada se cumple.El depositante, al coste real: comisión base quemada, comisión de prioridad al productor.
SKIPPED_STALE, SKIPPED_OVERFLOWUna lectura ya no se cumple, o un delta saldría de [0, 2²⁵⁶). No se escribe nada.El depositante: una comisión de omisión fija, quemada. En un fill, un secuenciador — nunca el usuario.
FORCED_FAILEDUn certificado forzado cuya llamada revirtió o rompió sus límites.Sus pagadores; no se escribe nada más.
DROPPEDEl depósito no puede reservarse: la celda no tiene fondos o está gastada.Nadie. No se marca como visto, así que puede reenviarse contra un depósito con fondos.

Los certificados aplicados, omitidos y fallidos forzados se marcan todos como vistos, e incluir un certificado visto, caducado o con puja insuficiente hace inválido el bloque, de modo que ningún productor puede volver a incluir ni retrasar el certificado de otro para cosechar su depósito. Los fallos a los que no hay nadie a quien cargar — un fill cuyo contador de pipeline está fuera de orden o cuyo contador fence se ha movido, un certificado forzado cuyas lecturas no se cumplen — hacen inválido el bloque en su lugar: el productor, que construyó el bloque a partir del estado que tiene, es la parte que podía haberlo sabido.

Puertas de era#

Las alturas de activación están en spec/params.json. h1_vm = 80.640 abre la era 1: la cEVM y la biblioteca predesplegada, registros de contrato con una clave de secuenciador, un administrador y un contador fence, intents y fills, el directorio y el camino forzado. Las constantes propias de la era — la ventana de intent de 8 bloques, 16 certificados forzados por bloque a 4.000 unidades de derivación cada uno, una vida de una entrada del directorio de 259.200 bloques más allá del TTL de su certificado de anuncio — están compiladas en lugar de ir en el archivo de parámetros, porque añadir un parámetro movería el id de la génesis de cada red en funcionamiento; el bloque de la puerta las escribe en celdas de protocolo, así que la raíz de estado se compromete con ellas a partir de entonces.

h1_bond es igual a h1_vm en mainnet y en la testnet pública, así que las operaciones de fianza nunca se abren allí: no hay fianzas de stake en la era 1. El árbol aún contiene las reglas de aval, arrendamiento y cola forzada de la v1.0, y solo son válidas en la red de desarrollo, en una ventana de 256 bloques por encima de su h1_vm y por debajo de su propia puerta v1, que existe para que los vectores que registran esa maquinaria conserven una red en la que sea válida. h2_pos = 1.576.800 está reservado para el proof of stake, congelado en el génesis, y ninguna regla lo lee. La era 2, proof of stake, se publica como su propia versión mediante hard fork, con sus constantes compiladas como las de la era 1: abre las fianzas en la época 182 por altura, inicia su rampa cuando el stake activo mantiene el 10% del suministro durante 30 épocas o, a más tardar, en la época 273, y pasa a slots de 5 segundos cuando la rampa se completa. A partir de la transición lee cada constante de génesis que en realidad es una duración en la unidad que esa constante mide, de modo que el archivo de parámetros no cambia.

Estructura del repositorio#

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

Las flechas de dependencia apuntan solo hacia dentro — node → core, wallet → core, nunca al revés — y nada dentro de core/ alcanza fuera de core/ y la biblioteca estándar. Una excepción, nombrada e impuesta: core/pow/randomx es el único cgo del árbol, y solo compila bajo su etiqueta de compilación, así que toda compilación sin la etiqueta sigue siendo solo de biblioteca estándar y no necesita cadena de herramientas de C. CI ejecuta la comprobación que lo impone, porque la comprobación de terceros busca rutas de módulo con grep y cgo no tiene ninguna — lo que dejó la regla sin que nadie la impusiera hasta que se añadió.

Cómo se prueba#

  • Vectores de referencia. Cada regla de fold, de bloque y de validez tiene casos positivos y negativos como (pre-state, block) → (post-state | invalid, outcomes, fees). La batería es el contrato de compatibilidad para implementaciones independientes.
  • La batería de perjuicio. Reinclusión de certificados aplicados y omitidos, inclusión caducada, inclusión con puja insuficiente, cadenas dependientes bajo barajado del proponente, ciclos de quema y reembolso, tormentas de crédito de terceros, carreras en el límite del tope de acuñación.
  • Basadas en propiedades. Determinismo del fold bajo permutación del orden del proponente, conmutatividad de los deltas, tolerancia a ABA, y conservación — incluidos los depósitos en vuelo, el anillo de madurez y la celda de tesorería, ya que una comprobación de conservación que omita la tesorería informa de que cada bloque crea valor.
  • Diferencial. Una segunda implementación del fold deliberadamente ingenua, escrita buscando la obviedad y no la velocidad, sometida a fuzzing contra la real. Una divergencia bloquea la publicación.
  • Simulación adversaria. Tormentas de omisiones, carreras por vaciar depósitos, mineros que rellenan con descartes, tortura de reorganizaciones, manipulación de marcas de tiempo, escenarios de retransmisión con eclipse ligero. Las configuraciones de escenario se versionan; las ejecuciones son reproducibles por semilla.
  • Concurrencia, deliberadamente. Todo componente que toque más de una gorutina tiene una prueba que es concurrente, con la forma que el proceso usa de verdad. -race sobre una batería de una sola gorutina no mide nada e informa de éxito.
  • La prueba de remojo caótica. Procesos de nodo reales sobre sockets reales tras un proxy que inyecta latencia, jitter, pérdidas y particiones, con nodos matados por SIGKILL al azar. Esta es la superficie que encontró la carrera de datos que toda la batería de -race se perdió.

La génesis es un artefacto, no una ceremonia#

zcd genesis emite el bloque génesis — estado vacío, celdas de la baliza inicializadas, registro de gastadas vacío, ninguna asignación de ningún tipo — y su id. El lanzamiento se comprometió de antemano con la etiqueta de código, el hash de parámetros, el id de la génesis y la hora de lanzamiento, y el conjunto de parámetros llevaba un compromiso con un mensaje de lanzamiento cuyo digest debía revelar el bloque 1, de modo que nadie pudiera minar la cadena antes de su inicio anunciado. Cualquiera puede reconstruir los cuatro primeros en milisegundos, desde el código fuente, en cualquier máquina. No hay nada más en lo que confiar.