montana/Монтана-Протокол/Код/crates/montana-node/src/node_lifecycle.rs

176 lines
6.1 KiB
Rust
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.

use std::fs;
use std::path::{Path, PathBuf};
use mt_crypto::Hash32;
use mt_genesis::ProtocolParams;
use crate::clock::meta_dir;
use crate::identity::{Identity, NodeError};
pub const NODE_STATE_FILE: &str = "node_state.bin";
const MAGIC: &[u8; 4] = b"mtns";
const VERSION: u8 = 1;
const SIZE: usize = 4 + 1 + 1 + 2 + 32 + 32 + 8 + 8 + 8 + 8 + 32;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum NodePhase {
Bootstrap = 0,
CandidateVdf = 1,
Registered = 2,
Active = 3,
}
impl NodePhase {
fn from_u8(v: u8) -> Result<Self, NodeError> {
match v {
0 => Ok(NodePhase::Bootstrap),
1 => Ok(NodePhase::CandidateVdf),
2 => Ok(NodePhase::Registered),
3 => Ok(NodePhase::Active),
other => Err(NodeError::InvalidArguments(format!(
"неизвестный node_phase: {other}"
))),
}
}
}
pub struct NodeLifecycle {
pub phase: NodePhase,
pub candidate_seed: Hash32,
pub candidate_endpoint: Hash32,
pub candidate_progress: u64,
pub target_chain_length: u64,
pub w_start: u64,
pub registration_window: u64,
pub nodereg_hash: Hash32,
}
impl NodeLifecycle {
// Автоматическое определение genesis vs candidate per spec Genesis Decree:
// если identity.node_pk == params.bootstrap_node_pubkey → узел = bootstrap
// node сети, phase = Active immediately (без Candidate VDF, DEV-010);
// иначе → standard path: phase = Bootstrap → CandidateVdf на первом окне →
// Registered (через apply_noderegistrations_batch) → Active (через
// apply_selection_event на ближайшем W % selection_interval == 0).
//
// Pre-Genesis-ceremony: params.bootstrap_node_pubkey = [0u8; PUBLIC_KEY_SIZE]
// (placeholder zeros). Любой реальный узел с identity не совпадёт с zeros;
// в этой ветке узел запускается как singleton genesis (legacy local mode)
// — после Genesis ceremony эта ветка перестанет применяться (см.
// mt_genesis::is_genesis_bootstrap_finalized).
pub fn fresh_for(identity: &Identity, params: &ProtocolParams) -> Self {
if Self::is_bootstrap_node(identity, params) {
Self::fresh_genesis()
} else {
Self::fresh_candidate()
}
}
pub fn is_bootstrap_node(identity: &Identity, params: &ProtocolParams) -> bool {
let bootstrap_pubkey_zeroed = params.bootstrap_node_pubkey.iter().all(|&b| b == 0);
if bootstrap_pubkey_zeroed {
// Genesis ceremony pending — placeholder zeros pubkey не активирует
// production check. Любой узел трактуется как singleton genesis для
// local network of one (M5 development phase).
return true;
}
identity.node_pk.as_bytes() == &params.bootstrap_node_pubkey
}
fn fresh_genesis() -> Self {
Self {
phase: NodePhase::Active,
candidate_seed: [0u8; 32],
candidate_endpoint: [0u8; 32],
candidate_progress: 0,
target_chain_length: 0,
w_start: 0,
registration_window: 0,
nodereg_hash: [0u8; 32],
}
}
fn fresh_candidate() -> Self {
Self {
phase: NodePhase::Bootstrap,
candidate_seed: [0u8; 32],
candidate_endpoint: [0u8; 32],
candidate_progress: 0,
target_chain_length: 0,
w_start: 0,
registration_window: 0,
nodereg_hash: [0u8; 32],
}
}
}
pub fn lifecycle_path(data_dir: &Path) -> PathBuf {
meta_dir(data_dir).join(NODE_STATE_FILE)
}
pub fn load_or_init_lifecycle(
data_dir: &Path,
identity: &Identity,
params: &ProtocolParams,
) -> Result<NodeLifecycle, NodeError> {
let path = lifecycle_path(data_dir);
if !path.exists() {
return Ok(NodeLifecycle::fresh_for(identity, params));
}
let bytes = fs::read(&path)?;
if bytes.len() != SIZE {
return Err(NodeError::CorruptedSize {
expected: SIZE,
actual: bytes.len(),
});
}
if &bytes[0..4] != MAGIC.as_slice() {
return Err(NodeError::InvalidMagic);
}
if bytes[4] != VERSION {
return Err(NodeError::UnsupportedVersion(bytes[4]));
}
let phase = NodePhase::from_u8(bytes[5])?;
let mut candidate_seed = [0u8; 32];
candidate_seed.copy_from_slice(&bytes[8..40]);
let mut candidate_endpoint = [0u8; 32];
candidate_endpoint.copy_from_slice(&bytes[40..72]);
let candidate_progress = u64::from_le_bytes(bytes[72..80].try_into().unwrap());
let target_chain_length = u64::from_le_bytes(bytes[80..88].try_into().unwrap());
let w_start = u64::from_le_bytes(bytes[88..96].try_into().unwrap());
let registration_window = u64::from_le_bytes(bytes[96..104].try_into().unwrap());
let mut nodereg_hash = [0u8; 32];
nodereg_hash.copy_from_slice(&bytes[104..136]);
Ok(NodeLifecycle {
phase,
candidate_seed,
candidate_endpoint,
candidate_progress,
target_chain_length,
w_start,
registration_window,
nodereg_hash,
})
}
pub fn save_lifecycle(data_dir: &Path, state: &NodeLifecycle) -> Result<(), NodeError> {
fs::create_dir_all(meta_dir(data_dir))?;
let path = lifecycle_path(data_dir);
let mut buf = vec![0u8; SIZE];
buf[0..4].copy_from_slice(MAGIC);
buf[4] = VERSION;
buf[5] = state.phase as u8;
buf[8..40].copy_from_slice(&state.candidate_seed);
buf[40..72].copy_from_slice(&state.candidate_endpoint);
buf[72..80].copy_from_slice(&state.candidate_progress.to_le_bytes());
buf[80..88].copy_from_slice(&state.target_chain_length.to_le_bytes());
buf[88..96].copy_from_slice(&state.w_start.to_le_bytes());
buf[96..104].copy_from_slice(&state.registration_window.to_le_bytes());
buf[104..136].copy_from_slice(&state.nodereg_hash);
let tmp = path.with_extension("bin.tmp");
fs::write(&tmp, &buf)?;
fs::rename(&tmp, &path)?;
Ok(())
}