soft3/cybernode/rs/src/node/genesis.rs

use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::path::Path;

use serde::{Deserialize, Serialize};

pub(super) const MAX_LEGACY_LOG: usize = 64 * 1024 * 1024;
const MAX_GENESIS: usize = 16 * 1024;

#[derive(Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct Genesis {
    chain_id: String,
    genesis_time: u64,
    engine: String,
    protocol: String,
}

pub(super) struct Loaded {
    pub canonical: Vec<u8>,
    pub time: u64,
}

pub(super) fn read_bounded(path: &Path, limit: usize) -> io::Result<Vec<u8>> {
    let file = File::open(path)?;
    if !file.metadata()?.is_file() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "expected a regular file",
        ));
    }
    let mut bytes = Vec::new();
    file.take(limit as u64 + 1).read_to_end(&mut bytes)?;
    if bytes.len() > limit {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("{} exceeds {limit} bytes", path.display()),
        ));
    }
    Ok(bytes)
}

pub(super) fn load(home: &Path, create: bool) -> io::Result<Loaded> {
    let path = home.join("genesis.json");
    if create && !path.try_exists()? {
        let value = Genesis {
            chain_id: "spacepussy-test".into(),
            genesis_time: super::now_unix(),
            engine: "cybergraph+bbg".into(),
            protocol: "soft3/spacepussy-test/v1".into(),
        };
        let mut bytes = serde_json::to_vec_pretty(&value)?;
        bytes.push(b'\n');
        let mut file = OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(&path)?;
        file.write_all(&bytes)?;
        file.sync_all()?;
        File::open(home)?.sync_all()?;
    }
    let value: Genesis = serde_json::from_slice(&read_bounded(&path, MAX_GENESIS)?)?;
    if value.chain_id != "spacepussy-test"
        || value.engine != "cybergraph+bbg"
        || !matches!(
            value.protocol.as_str(),
            "soft3/spacepussy-test/v1" | "soft3/spacepussy-test/v2"
        )
    {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "unsupported genesis chain, engine or protocol",
        ));
    }
    Ok(Loaded {
        canonical: serde_json::to_vec(&value)?,
        time: value.genesis_time,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    fn load_err(home: &Path) -> io::Error {
        match load(home, false) {
            Err(e) => e,
            Ok(_) => panic!("expected load() to fail"),
        }
    }

    fn tempdir() -> std::path::PathBuf {
        let dir = std::env::temp_dir().join(format!(
            "soft3-genesis-test-{}-{}",
            std::process::id(),
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_nanos()
        ));
        std::fs::create_dir_all(&dir).unwrap();
        dir
    }

    #[test]
    fn load_creates_default_genesis_on_fresh_home() {
        let home = tempdir();
        let loaded = load(&home, true).expect("default genesis should load");
        assert!(home.join("genesis.json").try_exists().unwrap());
        let value: Genesis = serde_json::from_slice(&loaded.canonical).unwrap();
        assert_eq!(value.chain_id, "spacepussy-test");
        assert_eq!(value.engine, "cybergraph+bbg");
        assert_eq!(value.protocol, "soft3/spacepussy-test/v1");
        assert_eq!(loaded.time, value.genesis_time);
    }

    #[test]
    fn load_without_create_fails_on_missing_genesis() {
        let home = tempdir();
        let err = load_err(&home);
        assert_eq!(err.kind(), io::ErrorKind::NotFound);
    }

    #[test]
    fn load_rejects_wrong_chain_id() {
        let home = tempdir();
        let genesis = serde_json::json!({
            "chain_id": "not-spacepussy",
            "genesis_time": 0,
            "engine": "cybergraph+bbg",
            "protocol": "soft3/spacepussy-test/v1",
        });
        std::fs::write(
            home.join("genesis.json"),
            serde_json::to_vec(&genesis).unwrap(),
        )
        .unwrap();
        let err = load_err(&home);
        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
        assert!(err.to_string().contains("unsupported"));
    }

    #[test]
    fn load_rejects_wrong_engine() {
        let home = tempdir();
        let genesis = serde_json::json!({
            "chain_id": "spacepussy-test",
            "genesis_time": 0,
            "engine": "not-the-engine",
            "protocol": "soft3/spacepussy-test/v1",
        });
        std::fs::write(
            home.join("genesis.json"),
            serde_json::to_vec(&genesis).unwrap(),
        )
        .unwrap();
        let err = load_err(&home);
        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
    }

    #[test]
    fn load_accepts_v2_protocol() {
        let home = tempdir();
        let genesis = serde_json::json!({
            "chain_id": "spacepussy-test",
            "genesis_time": 42,
            "engine": "cybergraph+bbg",
            "protocol": "soft3/spacepussy-test/v2",
        });
        std::fs::write(
            home.join("genesis.json"),
            serde_json::to_vec(&genesis).unwrap(),
        )
        .unwrap();
        let loaded = load(&home, false).expect("v2 protocol should be accepted");
        assert_eq!(loaded.time, 42);
    }

    #[test]
    fn load_rejects_unsupported_protocol() {
        let home = tempdir();
        let genesis = serde_json::json!({
            "chain_id": "spacepussy-test",
            "genesis_time": 0,
            "engine": "cybergraph+bbg",
            "protocol": "soft3/spacepussy-test/v99",
        });
        std::fs::write(
            home.join("genesis.json"),
            serde_json::to_vec(&genesis).unwrap(),
        )
        .unwrap();
        let err = load_err(&home);
        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
    }

    #[test]
    fn load_rejects_unknown_fields() {
        let home = tempdir();
        let genesis = serde_json::json!({
            "chain_id": "spacepussy-test",
            "genesis_time": 0,
            "engine": "cybergraph+bbg",
            "protocol": "soft3/spacepussy-test/v1",
            "extra_field": "not allowed",
        });
        std::fs::write(
            home.join("genesis.json"),
            serde_json::to_vec(&genesis).unwrap(),
        )
        .unwrap();
        let err = load_err(&home);
        assert!(err.to_string().contains("unknown field") || err.kind() == io::ErrorKind::InvalidData);
    }

    #[test]
    fn read_bounded_rejects_oversized_file() {
        let home = tempdir();
        let path = home.join("big.json");
        std::fs::write(&path, vec![b'a'; 100]).unwrap();
        let err = read_bounded(&path, 50).unwrap_err();
        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
        assert!(err.to_string().contains("exceeds"));
    }

    #[test]
    fn read_bounded_accepts_file_at_exact_limit() {
        let home = tempdir();
        let path = home.join("exact.json");
        std::fs::write(&path, vec![b'a'; 50]).unwrap();
        let bytes = read_bounded(&path, 50).expect("exact-limit file should be accepted");
        assert_eq!(bytes.len(), 50);
    }

    #[test]
    fn read_bounded_rejects_directory() {
        let home = tempdir();
        let err = read_bounded(&home, MAX_GENESIS).unwrap_err();
        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
        assert!(err.to_string().contains("regular file"));
    }
}

Homonyms

soft3/crate/src/chain/genesis.rs
soft3/crate/src/node/genesis.rs
soft3/cybernode/rs/src/chain/genesis.rs

Graph