ZYCORD docs
Français
ZycordDocsArchitecture

Architecture

Le compagnon d'ingénierie du livre blanc — comment le nœud de référence réalise la conception, et les décisions qui la sous-tendent. Explicatif, non normatif.

Ce document n'est pas normatif

Le protocole, ce sont les fichiers de paramètres et les vecteurs de référence, plus les règles nommées là où elles sont définies ; la spécification réseau porte les exigences de la couche pair. Cette page explique cette surface et consigne les décisions qui la sous-tendent. Là où elle diverge de la surface normative, c'est la surface normative qui l'emporte et ce texte est corrigé. Le compagnon d'ingénierie complet est docs/ARCHITECTURE.md.

Deux prédicats, deux moteurs#

La vie d'un certificat, de bout en bout :

 wallet                      network                        every full node
+------------+   gossip   +--------------+               +---------------------+
| build cert | ---------> | cert topic   | ------------> | STATELESS PIPELINE  |
| (reads,    |            | (TLS gossip) |               | V1..V8, batch sigs, |
| writes,    |            +--------------+               | native re-exec      |
| sigs, seq, |                                           |  -> mark VALID      |
| deposit)   |                                           +----------+----------+
+------------+                                                      | mempool
                                                                    v
                            miner (any node)              +---------------------+
                          +-------------------+  block    | FOLD (sequential)   |
                          | assemble ordered  | --------> | F-rules per cert:   |
                          | hash list + bodies|  gossip   | APPLY / SKIP / DROP |
                          | + RandomX solve   |           |  -> new state       |
                          +-------------------+           +---------------------+

La validité est sans état et parallèle : elle s'exécute une fois par certificat et par nœud, avant les blocs et indépendamment d'eux. L'applicabilité est avec état et séquentielle : elle s'exécute à l'intérieur du fold, à la position sur laquelle le certificat s'est engagé. Le mineur n'exécute rien ; il ordonne des hachages qu'il a déjà vu valider et résout la preuve de travail. Le fold est une boucle serrée sur un ensemble de travail en mémoire — comparer, ajouter, écrire.

Principes d'ingénierie#

Principe
P1Le fold est sacré. La fonction de transition d'état vit dans un unique paquet pur, sans entrées-sorties, sans horloges, sans goroutines, sans itération sur des tables associatives et sans virgule flottante. C'est le seul code dont les bugs sont irréparables après coup. Tout le reste du nœud n'est que de la plomberie remplaçable.
P2Le déterminisme prime sur la performance. Toute optimisation qui risque d'introduire du non-déterminisme est rejetée dans le code de consensus. La performance a sa place dans le pipeline sans état, où elle est sans danger.
P3Aucune clé d'administration, aucun RPC privilégié. Il n'existe aucun chemin de code par lequel une clé pourrait suspendre, mettre à jour, émettre ou réorganiser. Si ce n'est pas dans les règles du fold, cela n'existe pas.
P4Surface de consensus réduite. core/ n'importe rien en dehors de la bibliothèque standard. Le reste du nœud peut utiliser des bibliothèques de l'écosystème ; le cœur ne le peut pas.
P5La spécification d'abord. Les vecteurs de référence sont le protocole. Le code Go est une implémentation de référence ; une implémentation indépendante qui passe les vecteurs est un pair, pas un fork. C'est ce qui permet au mainteneur de finir par n'être personne en particulier.
P6Reproductible depuis la v0.1. Chaîne d'outils Go fixée, -trimpath, dépendances fixées. La confiance passe du binaire au code, seule confiance qu'un auteur anonyme puisse offrir.
P7Un seul binaire, ouvert par la hauteur. La machine, les opérations de caution et l'enregistrement des séquenceurs sont compilés dans chaque version et refusés en dessous de leur hauteur d'activation — Validate exige h1_vm ≥ h1_bond et exige que h1_vm tombe sur une frontière d'époque. Une ère arrive parce que la chaîne a atteint un nombre, jamais parce qu'on a demandé aux opérateurs de mettre à jour.

P3 est celui sur lequel un lecteur sceptique devrait appuyer le plus fort, et la trésorerie est l'endroit où appuyer : la genèse ne contient aucune clé et aucun chemin de dépense, il n'y a donc aucun privilège à détenir, déléguer ou voler. Le quorum 3-sur-5 de l'Ère 2 est fixé par un hard fork futur — le même mécanisme social que n'importe quel autre changement de consensus, sujet au même refus, et capable de déplacer exactement une cellule même alors. Un quorum qui n'apparaît que si le réseau accepte de l'inscrire, et qui ne tient ensuite qu'une cellule, est une règle de dépense. Une clé d'administration, elle, existe avant que quiconque ait consenti et atteint tout.

Primitives cryptographiques#

RôleChoixPourquoi
SignaturesEd25519Vérification par lots (la voie d'accès aux GPU), pas de malléabilité, clés minuscules. Règles strictes fixées à la genèse : encodages canoniques exigés pour la clé publique et R, clé publique sans torsion et pas d'ordre faible, et vérification sans cofacteur.
HachageBLAKE3Identifiants de certificats et de blocs, adresses, racine d'état. Assez rapide pour hacher au débit du relais ; adapté au parallélisme pour la racine d'état d'époque.
Preuve de travailRandomXOptimisée pour les processeurs. Le seul cgo de l'arborescence, derrière un tag de build, et absent d'un build qui ne l'a pas. pow_engine est dans la racine de consensus, si bien qu'un binaire portant le mauvais moteur refuse de démarrer plutôt que d'accepter la mauvaise preuve.
Séparation des domainesobligatoireChaque empreinte est blake3(tag ‖ payload). Les signatures signent sur l'identifiant de chaîne et la racine de consensus, ce qui tue le rejeu à la fois entre réseaux et entre deux incarnations d'un même réseau.

Le rejet de la torsion est ce qui rend sûr le chemin par lots. Une clé d'ordre mixte n'est pas d'ordre petit, aucune liste de blocage ne l'atteint donc, et c'est exactement là qu'un vérificateur par lots avec cofacteur et un vérificateur unitaire sans cofacteur divergent. Avec la clé et R dans le sous-groupe d'ordre premier, les deux sont prouvablement équivalents — un vérificateur par lots peut donc utiliser le cofacteur à condition d'appliquer les mêmes règles d'encodage et de torsion avant de constituer le lot. Cette obligation est le prix de ce choix, et le vérificateur par lots n'existe pas encore.

Encodage canonique et identifiants#

Tous les objets de consensus sont des conteneurs SSZ : un unique encodage canonique en octets, pas d'ordre de table de hachage, pas d'ambiguïté de champ optionnel, des décalages fixes pour un décodage partiel bon marché, et une merkleisation native.

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

Les deux premiers sont des empreintes différentes répondant à des questions différentes, et une implémentation qui emploie l'une là où l'autre a sa place est monétairement cassée. L'identifiant répond à cette autorisation a-t-elle été facturée ; le hachage d'exemplaire répond à ces octets le prouvent-ils. Les deux ne partagent jamais une clé.

Les signatures sont hors de la préimage de l'identifiant parce qu'une signature est une démonstration randomisée : le signataire choisit le nonce, chaque nonce donne une autre signature valide et parfaitement canonique sur le même corps, et aucun vérificateur ne peut savoir lequel a servi. Si elles étaient dedans, une autorisation aurait une infinité d'identifiants, chacun facturable, chacun produisible par n'importe lequel de ses signataires requis seul, aux dépens de l'autorité des autres.

L'offre de frais est à l'intérieur de l'identifiant, et c'est une décision monétaire

Une enchère hors de l'identifiant serait une enchère que n'importe qui en transit pourrait réécrire — gonflée pour brûler le solde du signataire par les frais de base, ou mise à zéro pour tenir le certificat hors de tout bloc. Ce qu'un certificat paie fait partie de ce que son signataire a autorisé : c'est donc haché et c'est signé. Un changement d'encodage ultérieur qui « sortirait les frais du corps signé pour souplesse de relais » aurait l'air d'une optimisation et serait un vecteur de vol.

La règle qui en découle : décoder, ne pas valider deux fois. Le décodage impose la forme canonique, si bien qu'un objet décodé est structurellement valide par construction et que les moteurs de règles ne revérifient jamais la forme.

Le modèle de cellules#

Une cellule est la valeur à un slot. Les valeurs de cellules sont des entiers non signés de 256 bits stockés en gros-boutien sur 32 octets. Les cellules absentes se lisent comme zéro — zéro est absence, ce qui est une exigence de consensus plutôt qu'une commodité d'implémentation : cela garde la racine d'état fonction de l'état plutôt que de l'historique qui l'a produit.

Séparément, le protocole tient un registre des adresses dépensées : un ensemble de consensus permanent d'adresses à usage unique dont l'autorité de signature a été brûlée.

Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
VersionTypeAutorisation de débit
0x01utilisateur à usage uniqueSignature du propriétaire ; tout certificat qui débite doit aussi porter un MARK_SPENT explicite. Une fois appliqué, toute lecture et toute écriture sous cette adresse échouent pour toujours.
0x02utilisateur persistantSignature du propriétaire ; réutilisable pour toujours. Ne peut jamais entrer dans le registre des adresses dépensées.
0x03actifRégie par les cellules d'autorité immuables de l'actif.
0x00protocoleRéservée au fold : balise d'époque, cellules de frais de base, anneau de maturité du coinbase, cellule de trésorerie.
0x04réservé — cellule à valeur cachée (Ère S)Inatteignable à l'Ère 0.

0x04 est réservé maintenant plutôt qu'attribué plus tard, parce qu'une valeur cachée doit être distinguable d'une valeur ordinaire par son adresse : un engagement de Pedersen et un solde de 256 bits font tous deux 32 octets, si bien qu'un delta gardé dirigé par erreur ou par malveillance vers un slot d'engagement ferait ajouter par le fold un entier à un encodage de point de courbe — une arithmétique qui passe toutes les vérifications et laisse une cellule que personne ne pourra jamais dépenser. Cette table est gelée à la genèse, l'octet est donc revendiqué ici et laissé inatteignable.

L'entrée du registre n'est jamais compactée. Les valeurs de cellules sous une adresse dépensée peuvent être élaguées après l'horizon d'annulation, mais l'entrée qui consigne l'adresse comme dépensée est ce qui l'empêche d'être ressuscitée. C'est un état de consensus en ajout seul, environ 33 octets par adresse, pour toujours — le problème ouvert que le protocole assume, partagé structurellement avec toute conception à ensemble de nullificateurs.

Validité sans état, et la loi de facturation#

Les règles V s'exécutent sur chaque certificat, en parallèle, avant l'admission au mempool et pendant la vérification des blocs, et n'exigent aucun état : forme canonique et identifiant de chaîne ; chaque signature vérifiée sur la racine de signature ; autorisation dérivable du seul certificat ; lectures déclarées égales à ce que le programme dérive ; et destination de remboursement vérifiée contre ce que le certificat lui-même brûle.

La loi de facturation du système tient en une phrase, et c'est ce à quoi il faut se tenir :

Une signature, au plus une facture, jamais à une position que son signataire ne pouvait pas éviter.

Cette spécification ajoute un terme au vocabulaire du livre blanc : le rejet, un non-événement non facturé. Un certificat qui atteint l'application avec son dépôt déjà consommé est rejeté — non facturé, non marqué comme vu, libre d'être resoumis contre un dépôt frais — si bien que les utilisateurs honnêtes ne perdent rien aux courses sur leur propre cellule de dépôt.

Organisation du dépôt#

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

Les flèches de dépendance ne pointent que vers l'intérieur — node → core, wallet → core, jamais l'inverse — et rien à l'intérieur de core/ n'atteint quoi que ce soit hors de core/ et de la bibliothèque standard. Une exception, nommée et imposée : core/pow/randomx est le seul cgo de l'arbre, et il ne compile que sous le tag de compilation, si bien que toute compilation sans le tag reste limitée à la bibliothèque standard et n'a besoin d'aucune chaîne d'outils C. La CI exécute la vérification qui l'impose, car la vérification des dépendances tierces filtre des chemins de modules et cgo n'en a pas — ce qui a laissé la règle imposée par personne jusqu'à ce qu'elle soit ajoutée.

Comment c'est testé#

  • Vecteurs de référence. Chaque règle de fold, de bloc et de validité a des cas positifs et négatifs sous la forme (pre-state, block) → (post-state | invalid, outcomes, fees). La suite est le contrat de compatibilité pour les implémentations indépendantes.
  • La suite de nuisance. Réinclusion de certificats appliqués et omis, inclusion expirée, inclusion sous-enchérie, chaînes dépendantes sous brassage du proposeur, cycles brûlage-remboursement, tempêtes de crédits par des tiers, courses aux bornes du plafond d'émission.
  • Tests par propriétés. Déterminisme du fold sous permutation de l'ordre du proposeur, commutativité des deltas, tolérance à l'ABA, et conservation — y compris les dépôts en vol, l'anneau de maturité et la cellule de trésorerie, puisqu'une vérification de conservation qui omet la trésorerie signale chaque bloc comme créant de la valeur.
  • Tests différentiels. Une seconde implémentation du fold délibérément naïve, écrite pour l'évidence plutôt que pour la vitesse, soumise au fuzzing contre la vraie. Une divergence bloque une publication.
  • Simulation adverse. Tempêtes d'omissions, courses au drainage de dépôts, mineurs bourrant de rejets, torture par réorganisation, manipulation d'horodatages, scénarios de relais quasi éclipse. Les configurations de scénarios sont versionnées ; les exécutions sont reproductibles à partir de leur graine.
  • Concurrence, délibérément. Chaque composant touché par plus d'une goroutine a un test qui est concurrent, dans la forme que le processus utilise réellement. -race sur une suite mono-goroutine ne mesure rien et annonce un succès.
  • Le test d'endurance sous chaos. De vrais processus de nœuds sur de vrais sockets derrière un proxy injectant latence, gigue, pertes et partitions, avec des nœuds tués par SIGKILL au hasard. C'est cette surface qui a trouvé la course de données que toute la suite -race avait manquée.

La genèse est un artefact, pas une cérémonie#

zcd genesis émet le bloc de genèse — état vide, cellules de balise initialisées, registre des dépensées vide, aucune allocation d'aucune sorte — et son identifiant. Le lancement annoncé s'engage, des semaines à l'avance, sur le tag de code, le hachage des paramètres, l'identifiant de genèse et l'heure de lancement. N'importe qui peut reconstruire les quatre en quelques millisecondes, depuis les sources, sur n'importe quelle machine. Il n'y a rien d'autre à croire sur parole.