222 lines
18 KiB
Markdown
222 lines
18 KiB
Markdown
|
|
# 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 автора. |
|