montana/Монтана-Протокол/Код/docs/SPEC_DEVIATIONS.md

222 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Spec Deviations
Single source of truth для всех известных отклонений реализации от спеки Montana. Введён v1.13.0 роли архитектора кода ([C-10] Mandatory deviation tracker).
Каждый `// SPEC DEVIATION DEV-NNN: ...` в коде ссылается на конкретный entry ниже. Pre-commit hook (`scripts/pre-commit.sh`) сверяет количество.
Закрытые `DEV-N` оставляются в файле как историческая запись со `Status: закрыто (commit <sha>)`.
---
## DEV-001: NodeRegistration с vdf_chain_length=0
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/registration.rs:8-22` (build_node_registration)
**Spec section:** «NodeRegistration» / «Adaptive VDF» / «Шаг 1: incremental apply»
**Spec quote:** «`if NR.vdf_chain_length >= required: apply; N += 1; else: reject`», `required_vdf_length(pending=0, active=0, τ₂)``tau2_windows = 20160`
**Что делает код:** `vdf_chain_length=0` (либо user-provided), без проверки `≥ τ₂`, обходит `apply_noderegistrations_batch` через ручной `CandidatePool::insert`
**Severity:** блокер mainnet ([I-9] / [C-7] violation, обход canonical apply pipeline)
**Closure path:** реализовать candidate VDF phase в `start.rs` — узел тикает VDF до `vdf_chain_length ≥ τ₂_windows`, после чего автоматически формирует NodeRegistration с правильным `vdf_chain_length` и вызывает `apply_noderegistrations_batch` через canonical pipeline
**Closure cost:** ~14 дней wall-clock на M-class Mac (физика VDF, не код) + ~4 часа кода
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-002: proof_endpoint = candidate_vdf_init(zeros, zeros, node_id)
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/registration.rs:11`
**Spec section:** «Шаг 2: Кандидатура» / «[I-8] compliance»
**Spec quote:** «`candidate_vdf_init = SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || cemented_bundle_aggregate(W_start - 2) || node_id)`»
**Что делает код:** `candidate_vdf_init(&[0u8; 32], &[0u8; 32], &node_id)` — timechain_value и cba как zeros (placeholder)
**Severity:** блокер mainnet ([I-8] violation — нет canonical unpredictable-offline binding)
**Closure path:** на момент формирования NodeRegistration использовать **реальные** `timechain.t_r` и `cemented_bundle_aggregate(W_start - 2, &cemented_node_ids_at_W_start_minus_2)` из локального state узла
**Closure cost:** ~1 час кода после DEV-001 closure
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-003: Лотерея отсутствует — winner = первый node по lex
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/commands/start.rs:104-120`, `commands/advance.rs` аналогично
**Spec section:** «Лотерея» / «Победитель τ₁»
**Spec quote:** «winner = `argmin(weighted_ticket_node)` среди cemented `VDF_Reveal` узлов-кандидатов; `weighted_ticket_node = ln_q64(endpoint) / lottery_weight`»
**Что делает код:** `state.nodes.iter().next()` — первый узел по `node_id` lex order, **без формирования VDF_Reveal, без endpoint, без weighted_ticket**
**Severity:** блокер mainnet (consensus-critical логика игнорирована, [I-8] violation)
**Closure path:** реализовать per окно: формирование `VDF_Reveal` (`mt_lottery::VdfReveal`) с `endpoint = SHA-256("mt-lottery" || T_r || cba || node_id || W LE)`, подпись `node_sk`; вычисление `weighted_ticket_node` через `mt_lottery::weighted_ticket_node`; для singleton — единственный кандидат — argmin тривиален и corretct **через каноничный API**
**Closure cost:** ~6 часов кода
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-004: BundledConfirmation никогда не формируется
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/commands/start.rs:113-117`
**Spec section:** «Confirmer threshold» / «BundledConfirmation» / «apply_proposal Step 3.5»
**Spec quote:** «chain_length инкрементируется при cemented `BundledConfirmation`», quorum = `(67 × X + 99) / 100` от active_chain_length
**Что делает код:** `chain_length += 1` напрямую без формирования BC, без подписи `op_hashes/reveal_hashes`, без quorum cementing
**Severity:** блокер mainnet (chain_length грубо инкрементируется на основании несуществующего правила)
**Closure path:** формирование `mt_lottery::BundledConfirmation` с `op_hashes[]` (от Account Table cemented operations) + `reveal_hashes[]` (от cemented VDF_Reveal предыдущего окна) + подпись `node_sk`; cementing через quorum (для singleton — сам себе 100%, проверка через `mt_lottery::is_cemented`)
**Closure cost:** ~8 часов кода
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-005: ProposalHeader не формируется, Step 4 apply_proposal обойдён
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/commands/start.rs:102-128`
**Spec section:** «Proposal header» / «Canonical acceptance» / «apply_proposal Step 4»
**Spec quote:** «winner формирует `ProposalHeader` (1080 байт) с `included_bundles + included_reveals + state_root`, подписывает, archive-ит. Validator пересчитывает state_root и сверяет.»
**Что делает код:** напрямую `account.balance += 13_000_000_000` минуя `apply_emission`; ProposalHeader не формируется, `archive_proposal` не вызывается
**Severity:** блокер mainnet (full Step 4 apply_proposal обойдён)
**Closure path:** реализовать формирование `mt_consensus::ProposalHeader` с правильными полями (`canonical_proposer`, `included_bundles`, `included_reveals`, `state_root`), `validate_acceptance`, эмиссия через `mt_account::apply_proposal`, `mt_store::archive_proposal`
**Closure cost:** ~12 часов кода
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-006: state_root не cross-check между proposer и validator
**Crate:** `montana-node`
**File:line:** N/A (отсутствие кода)
**Spec section:** «Верификация» / «Финальность proposal»
**Spec quote:** «Финальность proposal — подпись `proposer_node_id` на proposal header. Верификация — независимый пересчёт state_root.»
**Что делает код:** state_root recompute не существует. Singleton mode — узел сам себе proposer и validator, но cross-check всё равно необходим для регулярного self-verification (защита от corruption диска / памяти)
**Severity:** средний (singleton не имеет 2 узлов для cross-check, но self-verification обязателен)
**Closure path:** после формирования `ProposalHeader.state_root`, повторно вычислить `compute_state_root(account_root, node_root, candidate_root)` независимо и сверить byte-exact; mismatch → panic (corruption detected)
**Closure cost:** ~1 час кода
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-007: next_d не вызывается на τ₂ boundary
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/commands/start.rs:95-160`
**Spec section:** «Адаптация D через participation-ratio feedback»
**Spec quote:** «D адаптируется на границе τ₂ через каноническое chain observation»
**Что делает код:** `timechain.current_d` фиксируется на `D₀=252M`, `next_d` не вызывается
**Severity:** блокер mainnet для long-running узла (>14 дней)
**Closure path:** хранить `participation_history: Vec<u32>` (permille per окно) в timechain state; на каждой τ₂ boundary вычислить median + `next_d(current_d, median, params)`; обновить `timechain.current_d`; для singleton: participation_ratio = всегда 1000 → median=1000 → каждые τ₂ D × 1.03
**Closure cost:** ~3 часа кода
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-008: selection_event с zeros в advance.rs vs реальный T_r в start.rs
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/commands/advance.rs:55-72`
**Spec section:** «Selection event sort_key»
**Spec quote:** «`sort_key(c) = SHA-256("mt-selection" || timechain_value(W) || cemented_bundle_aggregate(W-2) || c.node_id)`»
**Что делает код:** `let placeholder = [0u8; 32]` для обоих `t_r` и `cba`. Silent divergence между моими же командами — `start.rs` использует реальный `timechain.t_r`, `advance.rs` использует zeros. Один и тот же state, разные seeds → разные ranking → разные winners для multi-candidate.
**Severity:** блокер mainnet (silent divergence между путями исполнения)
**Closure path:** удалить `advance.rs` целиком — для byte-exact spec нет «быстрой симуляции», есть только реальное исполнение
**Closure cost:** ~10 минут (удаление файла + dispatch update)
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-009: apply_proposal целиком обойдён — все steps реализованы ручным insert/update
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/commands/start.rs:95-160`, `advance.rs:45-95`
**Spec section:** «State transition → apply_proposal»
**Spec quote:** «Steps 1, 2, 3a, 3b, 4 в каноническом порядке»
**Что делает код:** напрямую модифицирует `AccountTable`/`NodeTable`/`CandidatePool` вне любого `apply_proposal`. Каждое окно — это ad-hoc набор shortcut'ов, не каноническая state transition.
**Severity:** блокер mainnet (silent divergence реализации от спеки на per-окно basis)
**Closure path:** заменить ad-hoc на canonical `apply_proposal` pipeline через `mt_account::apply_proposal(&mut account_table, &mut node_table, &mut candidate_pool, &proposal_input, params)`. Singleton mode формирует валидный `ProposalInput` для каждого окна и вызывает canonical pipeline.
**Closure cost:** ~16 часов кода (зависит от DEV-001..DEV-006)
**Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal)
---
## DEV-011: hardware calibration initial D под target window time
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/commands/start.rs` функция
`calibrate_d_for_target_window` + первый запуск start
**Spec section:** «Двигатели → TimeChain VDF — осциллятор», «Калибровка D₀»
**Spec quote:** «Mainnet calibration `D₀` нацелена на τ₁ ≈ 60 секунд wall-clock
на median commodity hardware (engineering target, не protocol
invariant)»
**Что делает код:** при первом запуске узла (timechain.bin не существовал)
запускает benchmark vdf_step(zeros, 10M) → измеряет
hardware SHA-256 rate → calibrate `current_d` так чтобы
окно ≈ 60 сек wall-clock на этой машине.
**Что в спеке:** spec `D₀ = 252M` — engineering calibration target для
median commodity. Per-узел actual wall-clock varies ×20
(Apple Silicon ~53s, idle x86_64 VPS ~68s, loaded ~1145s).
Adaptive D feedback на τ₂ boundary автоматически
подстраивает D под median network rate.
**Severity:** косметический — D в genesis узле = local state, не shared
consensus invariant с другими узлами (других нет).
При появлении новых узлов сети их D будет calibrated
самостоятельно либо synchronized через canonical params.d0.
**Closure path:** при finalize multi-node M6+ — узлы будут sync через
canonical D из Genesis Decree либо negotiate через
network consensus. Hardware calibration остаётся для
genesis узла как initial value.
**Closure cost:** acknowledged как permanent feature для genesis узла,
не требует closure
**Status:** acknowledged (genesis-узел local hardware calibration —
explicit operator choice, не silent shortcut)
**Acknowledged:** автор 2026-04-28 «сделай так чтобы мой узел генерил
примерно в 60 секунд окно»
---
## DEV-010: genesis bootstrap mode без Candidate VDF (auto-detected)
**Crate:** `montana-node`
**File:line:** `crates/montana-node/src/state.rs:32-66` (LocalState::bootstrap),
`crates/montana-node/src/node_lifecycle.rs:48-92` (NodeLifecycle::fresh_for + is_bootstrap_node),
`crates/montana-node/src/commands/start.rs:74-93` (Bootstrap → CandidateVdf transition)
**Spec section:** «Genesis Decree» / «bootstrap_node_pubkey» / «Активация узла»
**Spec quote:** «`bootstrap_node_pubkey: [u8; PUBLIC_KEY_SIZE]` в `protocol_params`
первый узел сети активируется через genesis state, не через
Candidate VDF + selection event цикл»
**Что делает код:** автоматическое определение genesis vs candidate per spec:
- `NodeLifecycle::is_bootstrap_node(identity, params)` сравнивает
`identity.node_pk` с `params.bootstrap_node_pubkey` byte-by-byte
- Если `bootstrap_node_pubkey == [0u8; PUBLIC_KEY_SIZE]` (placeholder
pre-Genesis-ceremony) — **любой** узел трактуется как genesis (singleton
legacy mode для M5 development phase). Эта ветка перестаёт применяться
после Genesis ceremony когда `bootstrap_node_pubkey` финализирован
конкретным значением
- Если `bootstrap_node_pubkey` finalized + `identity.node_pk` совпадает —
genesis path: phase=Active immediately, NodeRecord для self в NodeTable
- Если `bootstrap_node_pubkey` finalized + `identity.node_pk` НЕ совпадает —
standard candidate path: phase=Bootstrap → CandidateVdf на первом окне
→ Registered (через apply_noderegistrations_batch когда vdf_chain_length
≥ τ₂) → Active (через apply_selection_event на ближайшем W % selection_interval == 0).
Узел НЕ появляется в NodeTable bootstrap state — добавляется только
через canonical apply_selection_event.
**Severity:** acknowledged feature pre-Genesis-ceremony; production-ready
после ceremony (auto-detection через canonical apply_proposal pipeline
для не-bootstrap узлов работает byte-exact spec).
**Closure path:** Genesis ceremony — установить `params.bootstrap_node_pubkey`
в реальное значение. После этого DEV-010 закрывается
автоматически: проверка `is_bootstrap_node` определит ровно
один genesis узел, остальные пройдут стандартный candidate path.
**Closure cost:** 0 после Genesis ceremony (код уже implements auto-detection)
**Status:** acknowledged (auto-detection в коде, pre-ceremony placeholder
активирует singleton legacy ветку для M5; post-ceremony — production
spec compliance)
**Acknowledged:** автор 2026-04-28 — «у нас автоматически определяется
genesis узел с условиями и остальные?» → fix v1.15.0 [C-13]
enforcement: правильный путь немедленно, без вопроса автору
---
## История
| Версия роли | Дата | Действие |
|---|---|---|
| v1.13.0 | 2026-04-28 | Файл создан. Открыты DEV-001..DEV-009 для `montana-node` Этапы 1-5. Решение автора: byte-exact rewrite. |
| v1.13.0 | 2026-04-28 | DEV-001..DEV-009 закрыты через canonical apply_proposal pipeline rewrite. |
| v1.13.0 | 2026-04-28 | DEV-010 added: genesis bootstrap mode (узел стартует Active без Candidate VDF) — explicit acknowledged deviation, decision автора. |