diff --git a/AGENTS.md b/AGENTS.md index 196b0636..1cd7b7dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ Term Challenge is a WASM evaluation module for AI agents on the Bittensor networ ``` term-challenge/ -├── Cargo.toml # workspace with members = [".", "wasm", "cli", "server", "storage"] +├── Cargo.toml # workspace with members = [".", "wasm", "cli", "lib", "server", "storage"] ├── src/ │ ├── lib.rs # Root library crate entry point │ └── dataset/ @@ -29,6 +29,20 @@ term-challenge/ │ ├── llm_review.rs # LLM-based code review, reviewer selection, aggregation │ ├── submission.rs # Named submission registry and version tracking │ └── timeout_handler.rs # Review assignment timeout tracking and replacement +├── lib/ +│ ├── Cargo.toml # native library, depends on platform-core & platform-challenge-sdk +│ └── src/ +│ ├── lib.rs # Module declarations, re-exports dataset/ChallengeId/Hotkey +│ ├── dataset.rs # Validator-side dataset management types +│ ├── synthetic/mod.rs # Synthetic task generation +│ ├── validation/mod.rs # Validation result types +│ ├── admin/mod.rs # Admin action types +│ ├── util/mod.rs # Utility module +│ ├── util/hotkey.rs # Hotkey parsing helpers (wraps platform_core::Hotkey) +│ ├── cache/mod.rs # Score caching layer +│ ├── chain/mod.rs # Chain submission types +│ ├── worker/mod.rs # Worker job processing types +│ └── bin/term-sudo.rs # Admin CLI binary ├── server/ │ ├── Cargo.toml # lib + bin, depends on platform-challenge-sdk (server mode) │ └── src/ @@ -170,6 +184,9 @@ The `term-cli` crate is a **native binary** (NOT `no_std`) that provides a termi ## Build Commands ```bash +# Build library (native) +cargo build --release -p term-challenge-lib + # Build CLI (native) cargo build --release -p term-cli @@ -205,7 +222,7 @@ Git hooks live in `.githooks/` and are activated with `git config core.hooksPath 5. **Host functions are the ONLY external interface.** No direct HTTP, no filesystem, no std::net. 6. **Do NOT add `#[allow(dead_code)]` broadly.** Fix unused code or remove it. -> **Note:** The `cli/`, `server/`, and `storage/` crates are exempt from the `no_std` rule (rule 1) and the host-functions-only rule (rule 5) since they are native code that runs outside the WASM sandbox. Rules 2, 3, 4, and 6 still apply to all. +> **Note:** The `cli/`, `core/`, `lib/`, `server/`, and `storage/` crates are exempt from the `no_std` rule (rule 1) and the host-functions-only rule (rule 5) since they are native code that runs outside the WASM sandbox. Rules 2, 3, 4, and 6 still apply to all crates. ## DO / DO NOT diff --git a/Cargo.lock b/Cargo.lock index 2cb24ce6..480e3d82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5002,6 +5002,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "term-challenge-lib" +version = "0.1.0" +dependencies = [ + "clap", + "platform-challenge-sdk", + "platform-core", + "serde", + "serde_json", +] + [[package]] name = "term-challenge-server" version = "0.1.0" @@ -7218,3 +7229,8 @@ dependencies = [ "cc", "pkg-config", ] + +[[patch.unused]] +name = "w3f-bls" +version = "0.1.3" +source = "git+https://github.com/opentensor/bls?branch=fix-no-std#4ac443d11a6c9fdebe329d113702ad7387ba1688" diff --git a/Cargo.toml b/Cargo.toml index 0fc36be9..14452c6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = [".", "wasm", "cli", "core", "server", "storage"] +members = [".", "wasm", "cli", "core", "lib", "server", "storage"] default-members = ["wasm"] [workspace.package] @@ -19,6 +19,9 @@ bincode = "1.3" aes-gcm = "0.10" parity-scale-codec = { version = "3.7.5", features = ["derive"] } +[patch.crates-io] +w3f-bls = { git = "https://github.com/opentensor/bls", branch = "fix-no-std" } + [package] name = "term-challenge" version.workspace = true diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 00000000..4e79e6c9 --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "term-challenge-lib" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +description = "Terminal Benchmark Challenge — validator-side library" + +[lib] +name = "term_challenge_lib" +path = "src/lib.rs" + +[[bin]] +name = "term-sudo" +path = "src/bin/term-sudo.rs" + +[dependencies] +platform-core = { workspace = true } +platform-challenge-sdk = { workspace = true } + +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +clap = { version = "4.5", features = ["derive"] } + diff --git a/lib/src/admin/mod.rs b/lib/src/admin/mod.rs new file mode 100644 index 00000000..fc095e8f --- /dev/null +++ b/lib/src/admin/mod.rs @@ -0,0 +1,11 @@ +use platform_challenge_sdk::ChallengeId; +use platform_core::Hotkey; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AdminAction { + pub challenge_id: ChallengeId, + pub issuer: Hotkey, + pub action: String, + pub payload: serde_json::Value, +} diff --git a/lib/src/bin/term-sudo.rs b/lib/src/bin/term-sudo.rs new file mode 100644 index 00000000..b9da4bc0 --- /dev/null +++ b/lib/src/bin/term-sudo.rs @@ -0,0 +1,51 @@ +use clap::Parser; +use term_challenge_lib::{ChallengeId, Hotkey}; + +#[derive(Parser)] +#[command(name = "term-sudo", about = "Term Challenge admin CLI")] +struct Cli { + #[arg(long)] + challenge_id: String, + + #[arg(long)] + hotkey: String, + + #[command(subcommand)] + command: Command, +} + +#[derive(clap::Subcommand)] +enum Command { + Status, + ResetEpoch { + #[arg(long)] + epoch: u64, + }, +} + +fn main() { + let cli = Cli::parse(); + + let challenge_id = ChallengeId::from_str(&cli.challenge_id).unwrap_or_else(|| { + eprintln!("Invalid challenge ID: {}", cli.challenge_id); + std::process::exit(1); + }); + + let hotkey = Hotkey::from_ss58(&cli.hotkey).unwrap_or_else(|| { + eprintln!("Invalid SS58 hotkey: {}", cli.hotkey); + std::process::exit(1); + }); + + match cli.command { + Command::Status => { + println!("Challenge: {}", challenge_id); + println!("Hotkey: {:?}", hotkey); + println!("Status: OK"); + } + Command::ResetEpoch { epoch } => { + println!("Challenge: {}", challenge_id); + println!("Hotkey: {:?}", hotkey); + println!("Reset epoch: {}", epoch); + } + } +} diff --git a/lib/src/cache/mod.rs b/lib/src/cache/mod.rs new file mode 100644 index 00000000..2c01ea27 --- /dev/null +++ b/lib/src/cache/mod.rs @@ -0,0 +1,29 @@ +use platform_challenge_sdk::ChallengeId; +use platform_core::Hotkey; +use std::collections::HashMap; + +pub struct ScoreCache { + scores: HashMap<(ChallengeId, Hotkey), f64>, +} + +impl ScoreCache { + pub fn new() -> Self { + Self { + scores: HashMap::new(), + } + } + + pub fn get(&self, challenge_id: &ChallengeId, hotkey: &Hotkey) -> Option { + self.scores.get(&(*challenge_id, hotkey.clone())).copied() + } + + pub fn insert(&mut self, challenge_id: ChallengeId, hotkey: Hotkey, score: f64) { + self.scores.insert((challenge_id, hotkey), score); + } +} + +impl Default for ScoreCache { + fn default() -> Self { + Self::new() + } +} diff --git a/lib/src/chain/mod.rs b/lib/src/chain/mod.rs new file mode 100644 index 00000000..2d926fe0 --- /dev/null +++ b/lib/src/chain/mod.rs @@ -0,0 +1,12 @@ +use platform_challenge_sdk::ChallengeId; +use platform_core::Hotkey; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ChainSubmission { + pub challenge_id: ChallengeId, + pub miner: Hotkey, + pub agent_hash: String, + pub epoch: u64, + pub score: f64, +} diff --git a/lib/src/dataset.rs b/lib/src/dataset.rs new file mode 100644 index 00000000..318b948d --- /dev/null +++ b/lib/src/dataset.rs @@ -0,0 +1,21 @@ +use platform_challenge_sdk::ChallengeId; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DatasetEntry { + pub challenge_id: ChallengeId, + pub task_ids: Vec, + pub selected_at_epoch: u64, + pub dataset_hash: String, +} + +impl DatasetEntry { + pub fn new(challenge_id: ChallengeId, task_ids: Vec, epoch: u64, hash: String) -> Self { + Self { + challenge_id, + task_ids, + selected_at_epoch: epoch, + dataset_hash: hash, + } + } +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs new file mode 100644 index 00000000..a8a75426 --- /dev/null +++ b/lib/src/lib.rs @@ -0,0 +1,14 @@ +pub mod dataset; + +pub mod admin; +pub mod cache; +pub mod chain; +pub mod synthetic; +pub mod util; +pub mod validation; +pub mod worker; + +pub use dataset::DatasetEntry; + +pub use platform_challenge_sdk::ChallengeId; +pub use platform_core::Hotkey; diff --git a/lib/src/synthetic/mod.rs b/lib/src/synthetic/mod.rs new file mode 100644 index 00000000..7fb0b32b --- /dev/null +++ b/lib/src/synthetic/mod.rs @@ -0,0 +1,11 @@ +use platform_challenge_sdk::ChallengeId; +use platform_core::Hotkey; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SyntheticTask { + pub challenge_id: ChallengeId, + pub owner: Hotkey, + pub task_data: Vec, + pub created_epoch: u64, +} diff --git a/lib/src/util/hotkey.rs b/lib/src/util/hotkey.rs new file mode 100644 index 00000000..9be97bd5 --- /dev/null +++ b/lib/src/util/hotkey.rs @@ -0,0 +1,5 @@ +use platform_core::Hotkey; + +pub fn parse_hotkey(ss58: &str) -> Option { + Hotkey::from_ss58(ss58) +} diff --git a/lib/src/util/mod.rs b/lib/src/util/mod.rs new file mode 100644 index 00000000..af03861e --- /dev/null +++ b/lib/src/util/mod.rs @@ -0,0 +1,3 @@ +pub mod hotkey; + +pub use hotkey::parse_hotkey; diff --git a/lib/src/validation/mod.rs b/lib/src/validation/mod.rs new file mode 100644 index 00000000..594308c1 --- /dev/null +++ b/lib/src/validation/mod.rs @@ -0,0 +1,11 @@ +use platform_challenge_sdk::ChallengeId; +use platform_core::Hotkey; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ValidationResult { + pub challenge_id: ChallengeId, + pub validator: Hotkey, + pub is_valid: bool, + pub reason: Option, +} diff --git a/lib/src/worker/mod.rs b/lib/src/worker/mod.rs new file mode 100644 index 00000000..33280f71 --- /dev/null +++ b/lib/src/worker/mod.rs @@ -0,0 +1,20 @@ +use platform_challenge_sdk::ChallengeId; +use platform_core::Hotkey; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct WorkerJob { + pub challenge_id: ChallengeId, + pub assigned_validator: Hotkey, + pub agent_hash: String, + pub epoch: u64, + pub status: WorkerJobStatus, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum WorkerJobStatus { + Queued, + Running, + Completed, + Failed, +} diff --git a/server/Cargo.toml b/server/Cargo.toml index 66f26039..7de3ad3c 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -14,7 +14,7 @@ name = "term-challenge-server" path = "src/main.rs" [dependencies] -platform-challenge-sdk = { git = "https://github.com/PlatformNetwork/platform-v2", branch = "main", features = ["http-server"] } +platform-challenge-sdk = { workspace = true } axum = { version = "0.7", features = ["json"] } tokio = { version = "1.40", features = ["full"] } diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 5e19fe84..ca868ce7 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -7,8 +7,8 @@ license.workspace = true description = "Storage layer for Term Challenge using platform SDK types" [dependencies] -platform-core = { git = "https://github.com/PlatformNetwork/platform-v2", branch = "main" } -platform-challenge-sdk = { git = "https://github.com/PlatformNetwork/platform-v2", branch = "main" } +platform-core = { workspace = true } +platform-challenge-sdk = { workspace = true } tokio = { version = "1.40", features = ["full"] } tokio-postgres = { version = "0.7", features = ["with-uuid-1", "with-chrono-0_4", "with-serde_json-1"] }