Arquitetura
O companheiro de engenharia do whitepaper — como o nó de referência realiza o projeto, e as decisões por trás dele. Explicativo, não normativo.
O protocolo são os arquivos de parâmetros e os vetores dourados, mais as regras nomeadas onde quer que estejam definidas; a especificação de fio carrega os requisitos da camada de pares. Esta página explica essa superfície e registra as decisões por trás dela. Onde ela divergir da superfície normativa, a superfície normativa vence e este texto é corrigido. O companheiro de engenharia completo é docs/ARCHITECTURE.md.
Dois predicados, dois motores#
A vida de um certificado, de ponta a ponta:
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 |
+---------------------+
A validade é sem estado e paralela: roda uma vez por certificado por nó, antes dos blocos e independentemente deles. A aplicabilidade é com estado e sequencial: roda dentro do fold na posição comprometida do certificado. O minerador não executa nada do que recebe: ele ordena hashes que já viu validados e resolve o proof of work ou, depois da transição para proof of stake na era 2, assina o slot para o qual foi sorteado. Sua única parcela de execução é a do caminho forçado, a partir da era 1: ele pode executar intents abertos contra o estado do fim do seu bloco e anexar os resultados, no máximo 16 por bloco, que todo nó então verifica como qualquer outro certificado. O fold é um laço apertado sobre um conjunto de trabalho em memória — comparar, somar, escrever — e não executa absolutamente nada.
Princípios de engenharia#
| Princípio | |
|---|---|
| P1 | O fold é sagrado. A função de transição de estado vive num único pacote puro, sem E/S, sem relógios, sem goroutines, sem iteração de mapas e sem ponto flutuante. É o único código cujos bugs são impossíveis de corrigir depois do fato. Todo o resto no nó é encanamento substituível. |
| P2 | Determinismo vence desempenho. Qualquer otimização que arrisque não determinismo é rejeitada em código de consenso. O desempenho pertence ao pipeline sem estado, onde é seguro. |
| P3 | Sem chaves de administração, sem RPC privilegiada. Não existe caminho de código pelo qual qualquer chave possa pausar, atualizar, emitir ou reorganizar. Se não está nas regras do fold, não existe. |
| P4 | Superfície de consenso pequena. core/ não importa nada fora da biblioteca padrão. O resto do nó pode usar bibliotecas do ecossistema; o núcleo não pode. |
| P5 | Especificação primeiro. Os vetores de referência são o protocolo. O código Go é uma implementação de referência; uma implementação independente que passa nos vetores é um par, não um fork. É isso que permite que o mantenedor venha a ser ninguém em particular. |
| P6 | Reproduzível desde a v0.1. Toolchain do Go fixado, -trimpath, dependências fixadas. A confiança se desloca do binário para o código, que é a única confiança que um autor anônimo pode oferecer. |
| P7 | Um binário só, liberado por altura. A máquina, o maquinário de sequenciadores e o caminho forçado são compilados em cada release e recusados abaixo da sua altura de ativação, h1_vm (80.640 na mainnet), que Validate exige que caia num limite de época. A Era 1 chega porque a cadeia alcançou um número, nunca porque pediram aos operadores que atualizassem. As eras seguintes não estão no binário: cada uma é publicada como sua própria release, por hard fork. |
O P3 é o que um leitor cético deveria pressionar mais, e a tesouraria é onde pressioná-lo: a gênese não contém nenhuma chave e nenhum caminho de gasto, então não há privilégio para deter, delegar ou roubar. A tesouraria não pode ser gasta antes da altura 259.200, e qualquer regra que a abra chega como uma mudança de consenso — o mesmo mecanismo social de qualquer outra, sujeito à mesma recusa, e capaz de mover exatamente uma célula mesmo assim. A parte do fundador é um crédito, não uma chave: 3% de cada subsídio para um endereço fixado no arquivo de parâmetros, até a altura 3.153.600, que a era 2 lê como a época 1.095. Esse endereço guarda moedas e não alcança mais nada. Uma chave de administrador é uma que existe antes de alguém consentir e alcança tudo.
Primitivas criptográficas#
| Papel | Escolha | Por quê |
|---|---|---|
| Assinaturas | Ed25519 | Verificação em lote (a rampa para GPU), sem maleabilidade, chaves minúsculas. Regras estritas fixadas na gênese: codificações canônicas exigidas para a chave pública e R, a chave pública livre de torção e não de ordem baixa, e verificação sem cofator. |
| Hashing | BLAKE3 | Ids de certificado e de bloco, endereços, raiz de estado. Rápido o bastante para fazer hash na velocidade de linha da retransmissão; amigável a paralelismo para a raiz de estado da época. |
| Proof of work | RandomX | Otimizado para CPU. O único cgo na árvore, atrás de uma build tag, e ausente de um build sem ela. pow_engine está na raiz de consenso, então um binário com a engine errada se recusa a iniciar em vez de aceitar a prova errada. |
| Separação de domínio | obrigatória | Todo hash é blake3(tag ‖ payload). As assinaturas assinam sobre o chain id e a raiz de consenso, o que mata o replay tanto entre redes quanto entre duas encarnações de uma mesma rede. |
A rejeição de torção é o que torna o caminho em lote seguro. Uma chave de ordem mista não é de ordem pequena, então nenhuma lista de bloqueio a alcança, e é exatamente onde um verificador em lote com cofator e um verificador único sem cofator divergem. Com a chave e o R no subgrupo de ordem prima os dois são comprovadamente equivalentes — então um verificador em lote pode usar cofator desde que aplique as mesmas regras de codificação e de torção antes de agrupar em lote. Essa obrigação é o preço da escolha, e o verificador em lote ainda não existe.
Codificação canônica e identificadores#
Todos os objetos de consenso são contêineres SSZ: uma única codificação canônica de bytes, sem ordenação de mapas, sem ambiguidade de campos opcionais, deslocamentos fixos para parsing parcial barato, e merkleização 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))
Os dois primeiros são digests diferentes respondendo a perguntas diferentes, e uma implementação que use um onde o outro cabe está monetariamente quebrada. O id responde esta autorização já foi cobrada; o hash do exemplar responde estes bytes provam isso. Os dois nunca compartilham uma chave.
As assinaturas ficam fora da preimagem do id porque uma assinatura é uma demonstração aleatorizada: o assinante escolhe o nonce, cada nonce produz outra assinatura válida e perfeitamente canônica sobre o mesmo corpo, e nenhum verificador pode checar qual foi usado. Se estivessem dentro, uma autorização teria ilimitadamente muitos ids, cada um cobrável, cada um produzível por qualquer um dos seus assinantes exigidos à revelia da autoridade dos demais.
Um lance fora do id seria um lance que qualquer um em trânsito poderia reescrever — inflado para queimar o saldo do assinante pela taxa base, ou zerado para manter o certificado fora de todo bloco. O que um certificado paga faz parte do que seu assinante autorizou, então isso entra no hash e é assinado. Uma mudança de codificação posterior que "tire a taxa do corpo assinado por flexibilidade de retransmissão" pareceria uma otimização e seria um vetor de roubo.
A regra que se segue: faça o parse, não valide duas vezes. A decodificação impõe a forma canônica, então um objeto decodificado é estruturalmente válido por construção e os motores de regras nunca rechecam a forma.
O modelo de células#
Uma célula é o valor em um slot. Os valores de célula são inteiros de 256 bits sem sinal armazenados em big-endian em 32 bytes. Células ausentes leem como zero — zero é ausência, o que é um requisito de consenso e não uma conveniência de implementação: isso mantém a raiz de estado uma função do estado em vez do histórico que o produziu.
Separadamente, o protocolo mantém um registro de endereços gastos: um conjunto de consenso permanente de endereços de uso único cuja autoridade de assinatura foi queimada.
Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
| Versão | Tipo | Autorização de débito |
|---|---|---|
0x01 | usuário de uso único | Assinatura do dono; todo certificado que debite precisa carregar também um MARK_SPENT explícito. Depois que ele se aplica, toda leitura e escrita sob o endereço falha para sempre. |
0x02 | usuário persistente | Assinatura do dono; reutilizável para sempre. Nunca pode entrar no registro de gastos. |
0x03 | ativo | Governado pelas células de autoridade imutáveis do ativo. |
0x00 | protocolo | Somente pelo fold: beacon de época, células de taxa base, anel de maturidade do coinbase, célula da tesouraria, contadores de sequenciador. |
0x05 | contrato | Ninguém por chave: nenhuma chave gera um hash igual a um deles. Decidem o código do contrato e as regras do fold. |
0x04 | não definido | Não existe nenhuma célula sob ele; um certificado que nomeie um endereço assim é inválido. |
O acesso tipado é de onde vem o paralelismo. Uma leitura declara como ela
restringe sua célula — EXACT, ou uma das guardas GUARD_GE,
GUARD_LE, GUARD_EQ — e uma escrita declara o que faz:
SET, DELTA_ADD, DELTA_SUB ou MARK_SPENT. Só
EXACT alimenta a computação, e só EXACT fica desatualizada quando a célula muda;
uma guarda não retorna nada e um delta não lê nada, então a reexecução continua sendo uma função pura das
leituras exatas, e qualquer número de débitos e créditos com guarda sobre um mesmo slot comuta. Uma disciplina por
slot por certificado: um set e um delta nunca compartilham um slot. Escrever zero apaga a célula, então um
slot nunca escrito e um slot escrito com zero são o mesmo slot.
Uso único versus persistente é uma política de carteira exposta como um bit do endereço, não dois livros-razão. As carteiras usam por padrão um endereço de uso único novo por pagamento recebido, o caminho sem contenção; quem quer uma conta usa a versão persistente. As recompensas de mineração devem ir para uma conta assim: o consenso aceita qualquer um dos dois tipos como pagamento, mas um endereço de uso único queima toda recompensa que detém no momento em que é gasto, então o nó o recusa como endereço de pagamento.
A entrada do registro nunca é compactada. Os valores de célula sob um endereço gasto podem ser podados após o horizonte de desfazer, mas a entrada que registra o endereço como gasto é o que impede que ele seja ressuscitado. É estado de consenso somente-anexação, cerca de 33 bytes por endereço, para sempre — o problema em aberto que o protocolo assume honestamente, compartilhado estruturalmente com todo design de conjunto de nullifiers.
Validade sem estado, e a lei de cobrança#
As regras V rodam sobre cada certificado, em paralelo, antes da admissão no mempool e durante a verificação de blocos, e não exigem estado algum: forma canônica e chain id, com cada lista dentro do seu limite; cada assinatura verificando sob Ed25519 estrito sobre a raiz de assinatura, com o conjunto de assinaturas mínimo além de suficiente; a reexecução do programa contra as leituras declaradas produzindo exatamente as escritas declaradas; autorização para cada escrita que a exija, e uma assinatura sobre a célula de depósito incondicionalmente; um depósito que seja o saldo nativo de um endereço de usuário e cubra o pior caso do certificado; autoconsistência, de modo que nenhuma escrita contradiga uma leitura do mesmo slot e nenhum endereço de uso único debitado escape de ser marcado como gasto; nenhuma escrita em uma célula de protocolo; um byte de versão definido em cada endereço nomeado; e, em um contrato cuja chave de sequenciador não é zero, a assinatura dessa chave no caminho exato.
A lei de cobrança do sistema é uma frase, e é a coisa a que se agarrar:
Uma assinatura, no máximo uma cobrança, nunca em uma posição que seu assinante não pudesse evitar.
O fold ordena um bloco em três classes e não deixa ao produtor nenhuma discricionariedade: primeiro os
fills, na ordem dos pipelines de sequenciador a que pertencem; depois todos os outros
certificados por (deposit address, Seq, id); por último os certificados
forçados, por id. Cada certificado tem então exatamente um de quatro resultados, e os
vetores de referência os nomeiam:
| Resultado | Quando | Quem paga |
|---|---|---|
APPLIED | Toda leitura declarada se mantém. | O depositante, pelo custo real: taxa base queimada, taxa de prioridade para o produtor. |
SKIPPED_STALE, SKIPPED_OVERFLOW | Uma leitura não se mantém mais, ou um delta sairia de [0, 2²⁵⁶). Nada é escrito. | O depositante: uma taxa de omissão fixa, queimada. Para um fill, um sequenciador — nunca o usuário. |
FORCED_FAILED | Um certificado forçado cuja chamada reverteu ou violou seus limites. | Seus pagadores; nada mais é escrito. |
DROPPED | O depósito não pode ser reservado: a célula está sem saldo ou gasta. | Ninguém. Não é marcado como visto, então pode ser reenviado contra um depósito com saldo. |
Certificados aplicados, omitidos e com falha forçada são todos marcados como vistos, e incluir um certificado visto, expirado ou com lance abaixo do mínimo torna o bloco inválido, de modo que nenhum produtor pode reincluir ou atrasar o certificado de outra pessoa para colher seu depósito. Falhas que não têm ninguém a quem cobrar — um fill cujo contador de pipeline está fora de ordem ou cujo contador fence avançou, um certificado forçado cujas leituras não se mantêm — tornam o bloco inválido em vez disso: o produtor, que construiu o bloco a partir do estado que mantém, é a parte que poderia ter sabido.
Portões de era#
As alturas de ativação ficam em spec/params.json. h1_vm = 80.640 abre
a era 1: a cEVM e a biblioteca pré-implantada, registros de contrato com uma chave de sequenciador, um admin e um
contador fence, intents e fills, o diretório e o caminho forçado. As constantes próprias da era
— a janela de intent de 8 blocos, 16 certificados forçados por bloco a 4.000 unidades de derivação cada,
a vida útil de uma entrada de diretório de 259.200 blocos além do TTL do certificado que a anunciou — são compiladas em vez de carregadas pelo arquivo de
parâmetros, porque acrescentar um parâmetro mudaria o id de gênese de toda rede ativa; o bloco do portão
as escreve em células de protocolo, de modo que a raiz de estado se compromete com elas a partir de então.
h1_bond é igual a h1_vm na mainnet e na testnet pública, então as
operações de bonding nunca abrem ali: não há bonding de stake na era 1. A árvore ainda carrega as
regras de aval (underwriting), de arrendamento (lease) e de fila forçada da v1.0, e elas só são válidas na rede de
desenvolvimento, numa janela de 256 blocos acima do seu h1_vm e abaixo do seu próprio portão v1, que existe
para que os vetores que registram esse maquinário mantenham uma rede na qual ele é válido. h2_pos = 1.576.800 é reservado para
proof of stake, congelado na gênese e não é lido por nenhuma regra. A Era 2, proof of stake, sai como uma release própria
por hard fork, com suas constantes compiladas como as da era 1: ela abre o bonding na época 182 por
altura, inicia sua rampa quando o stake ativo mantém 10% da oferta por 30 épocas, ou na época 273 no
mais tardar, e passa a slots de 5 segundos quando a rampa se completa. A partir da transição, ela lê toda
constante da gênese que na verdade é uma duração na unidade que ela mede, de modo que o arquivo de parâmetros não
muda.
Organização do repositório#
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
As setas de dependência apontam apenas para dentro — node → core,
wallet → core, nunca o inverso — e nada dentro de core/
alcança fora de core/ e da biblioteca padrão. Uma exceção, nomeada e imposta:
core/pow/randomx é o único cgo na árvore, e compila apenas sob a build
tag, então toda build sem a tag continua sendo apenas biblioteca padrão e não precisa de toolchain C. A CI
roda a verificação que impõe isso, porque a verificação de terceiros faz grep em caminhos de módulo e cgo não
tem nenhum — o que deixou a regra imposta por ninguém até que ela fosse acrescentada.
Como é testado#
- Vetores de referência. Toda regra de fold, de bloco e de validade tem casos positivos e negativos na forma
(pre-state, block) → (post-state | invalid, outcomes, fees). A suíte é o contrato de compatibilidade para implementações independentes. - A suíte de assédio. Reinclusão de certificados aplicados e omitidos, inclusão expirada, inclusão com lance abaixo, cadeias dependentes sob embaralhamento do proponente, ciclos de queima e reembolso, tempestades de crédito de terceiros, disputas na fronteira do teto de emissão.
- Baseados em propriedades. Determinismo do fold sob permutação da ordem do proponente, comutatividade de deltas, tolerância a ABA, e conservação — incluindo depósitos em trânsito, o anel de maturidade e a célula da tesouraria, já que uma verificação de conservação que omita a tesouraria reporta todo bloco como criador de valor.
- Diferenciais. Uma segunda implementação do fold, deliberadamente ingênua, escrita para ser óbvia em vez de rápida, submetida a fuzzing contra a real. Divergência é bloqueadora de release.
- Simulação adversarial. Tempestades de omissão, disputas por drenagem de depósito, mineradores que entupem com descartes, tortura por reorganização, manipulação de timestamp, cenários de retransmissão eclipse-lite. As configurações de cenário são versionadas; as execuções são reproduzíveis por seed.
- Concorrência, deliberadamente. Todo componente que mais de uma goroutine toca tem um teste que é concorrente, no formato que o processo realmente usa.
-racenuma suíte de uma única goroutine não mede nada e reporta sucesso. - O teste de imersão sob caos. Processos de nó reais sobre sockets reais atrás de um proxy injetando latência, jitter, perda e partições, com nós mortos por
SIGKILLao acaso. Esta é a superfície que encontrou a corrida de dados que toda a suíte-racedeixou passar.
A gênese é um artefato, não uma cerimônia#
O zcd genesis emite o bloco de gênese — estado vazio, células de beacon inicializadas,
registro de gastos vazio, sem alocações de qualquer tipo — e seu id. O lançamento
se comprometeu antecipadamente com a tag do código, o hash dos parâmetros, o id de gênese e o horário de lançamento, e
o conjunto de parâmetros carregava um compromisso com uma mensagem de lançamento cujo digest o bloco 1 tinha de revelar, de modo que
ninguém pudesse minerar a cadeia antes do início anunciado. Qualquer um pode reconstruir os quatro primeiros em
milissegundos, a partir do código-fonte, em qualquer máquina. Não há mais nada em que confiar.