아키텍처
백서의 엔지니어링 동반 문서입니다 — 참조 노드가 설계를 어떻게 실현하는지, 그리고 그 뒤에 있는 결정들. 규범적이 아니라 설명적입니다.
프로토콜은 파라미터 파일과 골든 벡터에, 그리고 이름 붙은 규칙들이 정의된 곳 어디에나 있습니다. 와이어 명세는 피어 계층의 요구사항을 담고 있습니다. 이 페이지는 그 표면을 설명하고 그 뒤에 있는 결정들을 기록합니다. 이 페이지가 규범적 표면과 어긋날 때는 규범적 표면이 이기며 이 글이 수정됩니다. 전체 엔지니어링 동반 문서는 docs/ARCHITECTURE.md입니다.
두 개의 술어, 두 개의 엔진#
인증서(certificate)의 일생, 처음부터 끝까지.
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 |
+-------------------+ +---------------------+
유효성은 상태와 무관하고 병렬적입니다. 노드마다 인증서(certificate)마다 한 번씩, 블록보다 앞서 블록과 무관하게 실행됩니다. 적용 가능성은 상태에 의존하고 순차적입니다. 인증서가 커밋된 위치에서 폴드(fold) 안에서 실행됩니다. 채굴자는 아무 실행도 하지 않습니다. 이미 검증된 것을 본 해시들의 순서를 정하고 작업증명을 풀 뿐입니다. 폴드는 메모리에 있는 작업 집합 위를 도는 촘촘한 루프입니다 — 비교하고, 더하고, 씁니다.
엔지니어링 원칙#
| 원칙 | |
|---|---|
| P1 | 폴드(fold)는 신성합니다. 상태 전이 함수는 I/O도, 시계도, 고루틴도, 맵 순회도, 부동소수점도 없는 하나의 순수 패키지 안에 있습니다. 사후에 버그를 고칠 수 없는 유일한 코드입니다. 노드의 나머지는 전부 교체 가능한 배관입니다. |
| P2 | 결정론이 성능을 이깁니다. 비결정성을 초래할 위험이 있는 최적화는 합의 코드에서 기각됩니다. 성능은 안전한 곳, 즉 상태 없는 파이프라인의 몫입니다. |
| P3 | 관리자 키도, 특권 RPC도 없습니다. 어떤 키로도 일시 정지하거나, 업그레이드하거나, 발행하거나, 재구성할 수 있는 코드 경로가 없습니다. 폴드(fold) 규칙에 없는 것은 존재하지 않습니다. |
| P4 | 합의 표면은 작게. core/는 표준 라이브러리 밖의 어떤 것도 임포트하지 않습니다. 노드의 나머지는 생태계 라이브러리를 써도 되지만, 코어는 안 됩니다. |
| P5 | 명세가 먼저. 골든 벡터가 곧 프로토콜입니다. Go 코드는 하나의 참조 구현입니다. 이 벡터를 통과하는 독립 구현은 포크가 아니라 피어입니다. 이것이 언젠가 관리자가 특별한 누구도 아니게 되는 것을 가능하게 합니다. |
| P6 | v0.1부터 재현 가능. 고정된 Go 툴체인, -trimpath, 고정된 의존성. 신뢰가 바이너리에서 코드로 옮겨 가며, 그것이 익명의 저자가 제공할 수 있는 유일한 신뢰입니다. |
| P7 | 바이너리는 하나, 여는 것은 높이. 머신, 본드 연산, 시퀀서 등록은 모든 릴리스에 컴파일되어 들어가며 활성화 높이 아래에서는 거부됩니다 — Validate는 h1_vm ≥ h1_bond를 요구하고, h1_vm이 에포크 경계에 떨어질 것을 요구합니다. 에라(Era)는 체인이 어떤 숫자에 도달했기 때문에 오는 것이지, 운영자에게 업그레이드를 요청했기 때문에 오는 것이 결코 아닙니다. |
회의적인 독자가 가장 세게 눌러 봐야 할 것은 P3이며, 누를 자리는 트레저리(treasury)입니다. 제네시스 블록에는 키도, 지출 경로도 전혀 없으므로 쥐거나 위임하거나 훔칠 특권 자체가 없습니다. Era 2의 5개 중 3개 정족수는 미래의 하드 포크로 고정됩니다 — 다른 어떤 합의 변경과도 같은 사회적 메커니즘이고, 같은 거부의 대상이며, 그렇게 되더라도 정확히 셀(cell) 하나만 움직일 수 있습니다. 네트워크가 써 넣기로 합의해야만 나타나고, 나타난 뒤에도 셀 하나만 쥐는 정족수는 지출 규칙입니다. 관리자 키란 누가 동의하기 전부터 존재하며 모든 것에 손이 닿는 것입니다.
암호 기본 요소#
| 역할 | 선택 | 이유 |
|---|---|---|
| 서명 | Ed25519 | 일괄 검증(GPU로 가는 진입로), 가변성 없음, 작은 키. 제네시스 블록에서 고정된 엄격한 규칙이 있습니다. 공개 키와 R에 대한 정규 인코딩 요구, 공개 키가 비틀림이 없고 낮은 위수가 아닐 것, 그리고 검증은 여인수 없이 수행할 것. |
| 해싱 | BLAKE3 | 인증서(certificate)와 블록의 id, 주소, 상태 루트에 쓰입니다. 중계 회선 속도로 해싱할 만큼 빠르고, 에포크 상태 루트에 대해 병렬화하기 좋습니다. |
| 작업증명 | RandomX | CPU에 최적화되어 있습니다. 트리 안의 유일한 cgo이며, 빌드 태그 뒤에 있고, 태그 없이 빌드하면 아예 없습니다. pow_engine은 합의 루트 안에 있으므로, 잘못된 엔진을 담은 바이너리는 잘못된 증명을 받아들이는 대신 시작을 거부합니다. |
| 도메인 분리 | 필수 | 모든 해시는 blake3(tag ‖ payload)입니다. 서명은 체인 id 와 합의 루트에 대해 이루어지며, 이는 네트워크들 사이의 재생과 한 네트워크의 두 화신 사이의 재생을 모두 막습니다. |
일괄 경로를 안전하게 만들어 주는 것이 바로 비틀림 배제입니다. 혼합 위수 키는 낮은 위수가 아니므로 어떤 차단 목록에도 걸리지 않으며, 여인수를 쓰는 일괄 검증기와 여인수를 쓰지 않는 단건 검증기가 어긋나는 지점이 정확히 거기입니다. 키와 R이 소수 위수 부분군 안에 있으면 둘은 증명 가능하게 동등합니다 — 따라서 일괄 검증기는 일괄 처리 전에 동일한 인코딩 및 비틀림 규칙을 적용한다는 조건에서 여인수를 써도 됩니다. 그 의무가 이 선택의 대가이며, 일괄 검증기는 아직 존재하지 않습니다.
정규 인코딩과 식별자#
모든 합의 객체는 SSZ 컨테이너입니다. 정규 바이트 인코딩이 하나뿐이고, 맵 순서도 없고, 선택적 필드로 인한 모호함도 없으며, 값싼 부분 파싱을 위한 고정 오프셋과 기본 제공되는 머클화를 갖추고 있습니다.
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))
앞의 둘은 서로 다른 질문에 답하는 서로 다른 다이제스트이며, 한쪽이 있어야 할 자리에 다른 쪽을 쓰는 구현은 금전적으로 망가진 것입니다. id는 이 인가에 대해 청구가 이루어졌는가에 답하고, 실체(exemplar) 해시는 이 바이트들이 그것을 증명하는가에 답합니다. 둘은 결코 키를 공유하지 않습니다.
서명이 id의 원상 바깥에 있는 것은 서명이 무작위화된 시연이기 때문입니다. 서명자가 nonce를 고르고, 모든 nonce가 같은 본문에 대한 또 하나의 유효한, 완벽하게 정규적인 서명을 만들어 내며, 어떤 검증자도 어느 것이 쓰였는지 확인할 수 없습니다. 서명이 안에 있었다면 하나의 인가가 무한히 많은 id를 갖게 되고, 각각이 청구 가능하며, 각각을 필요한 서명자 중 어느 하나가 다른 서명자들의 권한을 빌려 만들어 낼 수 있게 됩니다.
id 바깥의 입찰가는 전송 중인 누구든 다시 쓸 수 있는 입찰가입니다 — 기본 수수료를 통해 서명자의 잔액을 태우려고 부풀릴 수도 있고, 인증서(certificate)를 어떤 블록에도 넣지 못하게 하려고 0으로 만들 수도 있습니다. 인증서가 얼마를 내는지는 그 서명자가 인가한 것의 일부이므로, 그것은 해싱되고 서명됩니다. 나중에 "중계 유연성을 위해 수수료를 서명된 본문 밖으로 옮기는" 인코딩 변경은 최적화처럼 보이겠지만 절도 경로가 될 것입니다.
여기서 따라 나오는 규칙은 파싱하되 두 번 검증하지 말라입니다. 디코딩이 정규 형식을 강제하므로, 디코딩된 객체는 구조적으로 유효한 것이 보장되고 규칙 엔진은 형태를 다시 검사하지 않습니다.
셀(cell) 모델#
셀(cell)은 슬롯에 담긴 값입니다. 셀의 값은 32바이트에 빅엔디언으로 저장되는 부호 없는 256비트 정수입니다. 없는 셀은 0으로 읽힙니다 — 0은 부재입니다. 이는 구현상의 편의가 아니라 합의 요구사항입니다. 상태 루트가 상태를 만들어 낸 이력의 함수가 아니라 상태 자체의 함수로 남게 해 주기 때문입니다.
이와 별개로, 프로토콜은 사용된 주소 레지스트리를 유지합니다. 서명 권한이 소각된 일회성 주소들의 영구적인 합의 집합입니다.
Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
| 버전 | 종류 | 차변 인가 |
|---|---|---|
0x01 | 일회성 사용자 | 소유자 서명. 차변을 발생시키는 인증서(certificate)는 명시적인 MARK_SPENT도 함께 지녀야 합니다. 그것이 적용되고 나면 그 주소 아래의 모든 읽기와 쓰기가 영원히 실패합니다. |
0x02 | 영구 사용자 | 소유자 서명. 영원히 재사용할 수 있습니다. 사용된 주소 레지스트리에 결코 들어갈 수 없습니다. |
0x03 | 자산 | 그 자산의 불변 권한 셀(cell)들이 다스립니다. |
0x00 | 프로토콜 | 폴드(fold) 전용입니다. 에포크 비컨, 기본 수수료 셀(cell), coinbase 성숙 기간 링, 트레저리(treasury) 셀. |
0x04 | 예약됨 — 숨겨진 값 셀(cell) (Era S) | Era 0에서는 도달할 수 없습니다. |
0x04는 나중에 할당하지 않고 지금 예약해 둡니다. 숨겨진 값은
그 주소로 평범한 값과 구별할 수 있어야 하기 때문입니다. Pedersen 커밋먼트와 256비트
잔액은 둘 다 32바이트이므로, 실수로든 악의로든 커밋먼트 슬롯을 겨눈 가드 델타는 폴드(fold)가
곡선 점 인코딩에 정수를 더하게 만듭니다 — 모든 검사를 통과하면서 아무도 영원히 지출할 수
없는 셀(cell)을 남기는 연산입니다. 이 표는 제네시스 블록에서 동결되므로, 이 바이트는 여기서
선점해 두고 도달할 수 없는 채로 남겨 둡니다.
레지스트리 항목은 결코 압축되지 않습니다. 사용된 주소 아래의 셀(cell) 값은 되돌리기 지평 이후에 정리될 수 있지만, 그 주소가 사용되었음을 기록하는 항목은 그 주소가 되살아나는 것을 막는 장치입니다. 이는 추가만 가능한 합의 상태로, 주소당 약 33바이트씩 영원히 남습니다 — 이 프로토콜이 정직하게 밝히는 미해결 문제이며, 모든 널리파이어 집합 설계가 구조적으로 함께 지고 있는 문제입니다.
상태 없는 유효성, 그리고 청구 법칙#
V-규칙은 모든 인증서(certificate)에 대해 병렬로, mempool 수용 전과 블록 검증 중에 실행되며, 상태를 전혀 필요로 하지 않습니다. 정규 형식과 체인 id, 서명 루트에 대해 검증되는 모든 서명, 인증서만으로 유도 가능한 인가, 프로그램이 유도한 것과 같은 선언된 reads, 그리고 인증서가 스스로 소각하는 것에 대조해 확인된 환불 목적지를 검사합니다.
이 시스템의 청구 법칙은 한 문장이며, 붙들어야 할 것이 바로 그것입니다.
하나의 서명에는 많아야 한 번의 청구, 그리고 결코 그 서명자가 피할 수 없었던 위치에서는 아닙니다.
이 명세는 백서의 어휘에 용어 하나를 더합니다. 청구되지 않는 비사건인 폐기(drop) 입니다. 예치금이 이미 소진된 채로 적용 단계에 도달한 인증서(certificate)는 폐기됩니다 — 청구되지 않고, 확인 집합(seen set)에 표시되지도 않으며, 새 예치금을 붙여 자유롭게 다시 제출할 수 있습니다 — 따라서 정직한 사용자는 자기 예치금 셀(cell)을 둘러싼 경합으로 잃는 것이 없습니다.
저장소 구조#
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
의존성 화살표는 안쪽으로만 향합니다 — node → core,
wallet → core이며 그 반대는 결코 없습니다 — 그리고 core/
안의 어떤 것도 core/와 표준 라이브러리 바깥에 손을 뻗지 않습니다. 명시되고 강제되는
예외가 하나 있습니다. core/pow/randomx는 트리 안의 유일한 cgo이며 빌드 태그 아래에서만
컴파일되므로, 태그 없는 모든 빌드는 여전히 표준 라이브러리만 쓰고 C 툴체인이 필요 없습니다. 이를
강제하는 검사는 CI가 실행합니다. 서드파티 검사는 모듈 경로를 grep하는데 cgo에는 모듈 경로가 없어서
— 이 검사가 추가되기 전까지 그 규칙은 아무도 강제하지 않고 있었습니다.
어떻게 테스트하는가#
- 골든 벡터. 모든 폴드(fold), 블록, 유효성 규칙에 대해
(pre-state, block) → (post-state | invalid, outcomes, fees)형태의 긍정 사례와 부정 사례가 있습니다. 이 모음이 독립 구현을 위한 호환성 계약입니다. - 괴롭힘 모음. 적용된 인증서(certificate)와 건너뜀(skip) 처리된 인증서의 재포함, 만료된 포함, 낮은 입찰가 포함, 제안자 셔플 아래의 의존 사슬, 소각-환불 순환, 제3자 대변 폭풍, 발행 상한 경계 경합.
- 속성 기반. 제안자 순서 치환 아래에서의 폴드(fold) 결정론, 델타 교환성, ABA 내성, 그리고 보존 — 진행 중인 예치금, 성숙 링, 트레저리(treasury) 셀(cell)까지 포함해서입니다. 트레저리를 빠뜨린 보존 검사는 모든 블록이 가치를 만들어 낸다고 보고하기 때문입니다.
- 차이 검사. 속도가 아니라 명료함을 위해 쓴, 일부러 순진하게 만든 두 번째 폴드(fold) 구현을 진짜 구현에 대해 퍼징합니다. 결과가 갈라지면 릴리스가 막힙니다.
- 적대적 시뮬레이션. 건너뜀(skip) 폭풍, 예치금 고갈 경합, 폐기(drop)로 채워 넣는 채굴자, 재구성 고문, 타임스탬프 조작, 경량 이클립스 중계 시나리오. 시나리오 설정은 커밋되어 있고, 실행은 시드로 재현할 수 있습니다.
- 동시성, 의도적으로. 고루틴 하나보다 많은 것이 건드리는 모든 구성 요소에는 프로세스가 실제로 쓰는 형태 그대로 실제로 동시적인 테스트가 있습니다. 고루틴 하나짜리 테스트 모음에
-race를 걸면 아무것도 측정하지 못한 채 성공을 보고합니다. - 카오스 침수 시험. 지연, 지터, 손실, 분할을 주입하는 프록시 뒤에서 실제 소켓 위로 실제 노드 프로세스를 돌리며, 노드를 무작위로
SIGKILL로 죽입니다. 이것이-race모음 전체가 놓친 데이터 경합을 찾아낸 표면입니다.
제네시스 블록은 의식이 아니라 산출물입니다#
zcd genesis는 제네시스 블록 — 빈 상태, 초기화된 비컨 셀(cell), 빈 사용된 주소
레지스트리, 어떤 종류의 배분도 없음 — 과 그 id를 내놓습니다. 공표된 출시는
몇 주 전에 미리 코드 태그, 파라미터 해시, 제네시스 블록 id, 출시 시각에 구속됩니다. 누구든 이 넷을
소스로부터, 어떤 컴퓨터에서든, 밀리초 단위로 다시 만들어 볼 수 있습니다. 그 밖에 신뢰할
것은 아무것도 없습니다.