diff --git a/Cargo.lock b/Cargo.lock index f979f14b..91265a8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,7 +478,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "c0mpute-api" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "c0mpute-proto", @@ -493,7 +493,7 @@ dependencies = [ [[package]] name = "c0mpute-bench" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "blake3", @@ -504,7 +504,7 @@ dependencies = [ [[package]] name = "c0mpute-cli" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "axum", @@ -534,7 +534,7 @@ dependencies = [ [[package]] name = "c0mpute-core" -version = "0.2.65" +version = "0.2.66" dependencies = [ "aes-gcm", "anyhow", @@ -578,7 +578,7 @@ dependencies = [ [[package]] name = "c0mpute-doctor" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "serde", @@ -590,7 +590,7 @@ dependencies = [ [[package]] name = "c0mpute-envelope" -version = "0.2.65" +version = "0.2.66" dependencies = [ "blake3", "bs58", @@ -603,7 +603,7 @@ dependencies = [ [[package]] name = "c0mpute-gateway" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "axum", @@ -625,7 +625,7 @@ dependencies = [ [[package]] name = "c0mpute-market" -version = "0.2.65" +version = "0.2.66" dependencies = [ "c0mpute-envelope", "serde", @@ -635,7 +635,7 @@ dependencies = [ [[package]] name = "c0mpute-net" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "async-trait", @@ -652,7 +652,7 @@ dependencies = [ [[package]] name = "c0mpute-placement" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "async-trait", @@ -673,7 +673,7 @@ dependencies = [ [[package]] name = "c0mpute-proto" -version = "0.2.65" +version = "0.2.66" dependencies = [ "blake3", "hex", @@ -685,7 +685,7 @@ dependencies = [ [[package]] name = "c0mpute-secure-chat" -version = "0.2.65" +version = "0.2.66" dependencies = [ "aes-gcm", "anyhow", @@ -708,7 +708,7 @@ dependencies = [ [[package]] name = "c0mpute-store" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "blake3", @@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "c0mpute-transcode" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "c0mpute-proto", @@ -739,7 +739,7 @@ dependencies = [ [[package]] name = "c0mpute-update" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "hex", @@ -753,7 +753,7 @@ dependencies = [ [[package]] name = "c0mpute-verify" -version = "0.2.65" +version = "0.2.66" dependencies = [ "anyhow", "blake3", diff --git a/Cargo.toml b/Cargo.toml index b294e01f..f39beda4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.2.65" +version = "0.2.66" edition = "2024" license = "MIT" repository = "https://github.com/profullstack/c0mpute" diff --git a/apps/tui/package.json b/apps/tui/package.json index 9cfae0cb..0ed320ba 100644 --- a/apps/tui/package.json +++ b/apps/tui/package.json @@ -1,6 +1,6 @@ { "name": "@c0mpute/tui", - "version": "0.2.65", + "version": "0.2.66", "private": true, "type": "module", "bin": { diff --git a/apps/web/package.json b/apps/web/package.json index 7451627a..4f0e2948 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "c0mpute", - "version": "0.2.65", + "version": "0.2.66", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/app/releases/latest.json/route.ts b/apps/web/src/app/releases/latest.json/route.ts index 317b449a..96a6fc78 100644 --- a/apps/web/src/app/releases/latest.json/route.ts +++ b/apps/web/src/app/releases/latest.json/route.ts @@ -28,7 +28,7 @@ export const dynamic = "force-dynamic"; * this in sync with the workspace version in `Cargo.toml` on each release so a * freshly built CLI reports "already latest" rather than a phantom upgrade. */ -const FALLBACK_VERSION = "0.2.65"; +const FALLBACK_VERSION = "0.2.66"; const FALLBACK_MIN_REQUIRED = "0.0.1"; type Channel = "stable" | "beta" | "nightly"; diff --git a/node/crates/c0mpute-core/src/buyer.rs b/node/crates/c0mpute-core/src/buyer.rs index 92a1c493..9d2ee7bb 100644 --- a/node/crates/c0mpute-core/src/buyer.rs +++ b/node/crates/c0mpute-core/src/buyer.rs @@ -309,6 +309,10 @@ pub async fn run_auction(net: Arc, auction: JobAuction) -> Result spec_inline: Some(auction.spec_inline.clone()), published_at_ms: unix_ms(), external_ref: auction.external_ref.clone(), + buyer_addrs: crate::reachability::buyer_external_addrs( + std::env::var("C0MPUTE_P2P_EXTERNAL").ok().as_deref(), + &offer.buyer_peer_id, + ), }; publish_json(&net, &topic, &accept).await?; if let Some(tx) = &auction.accept_notify { diff --git a/node/crates/c0mpute-core/src/dispatch.rs b/node/crates/c0mpute-core/src/dispatch.rs index 428f1f9a..742c6a1a 100644 --- a/node/crates/c0mpute-core/src/dispatch.rs +++ b/node/crates/c0mpute-core/src/dispatch.rs @@ -572,6 +572,7 @@ mod tests { spec_inline: Some(serde_json::json!({"input_url": "https://example.com/a.mp4"})), published_at_ms: 0, external_ref: Some("infernet:job_42".into()), + buyer_addrs: Vec::new(), } } diff --git a/node/crates/c0mpute-core/src/lib.rs b/node/crates/c0mpute-core/src/lib.rs index b5df0f8a..73e4be7d 100644 --- a/node/crates/c0mpute-core/src/lib.rs +++ b/node/crates/c0mpute-core/src/lib.rs @@ -13,6 +13,7 @@ pub(crate) mod gateway_util; pub mod jobs_gateway; pub mod net_guard; pub mod pricing; +pub mod reachability; pub mod private; pub mod register; pub mod resources; diff --git a/node/crates/c0mpute-core/src/reachability.rs b/node/crates/c0mpute-core/src/reachability.rs new file mode 100644 index 00000000..b6802d94 --- /dev/null +++ b/node/crates/c0mpute-core/src/reachability.rs @@ -0,0 +1,122 @@ +//! Getting results back from workers that cannot be dialed. +//! +//! Workers listen on a random port, usually behind NAT or a firewall, and +//! c0mpute has no relay, so a buyer often cannot open a connection to the +//! worker that holds its result. A buyer gateway, on the other hand, is one +//! machine we control: it listens on a fixed open port (`C0MPUTE_P2P_LISTEN`) +//! and advertises its public address (`C0MPUTE_P2P_EXTERNAL`) in every +//! `JobAccept`. The winning worker dials that address before publishing its +//! receipt, and the buyer's result fetch reuses the connection. + +use c0mpute_net::Multiaddr; +use c0mpute_net::Protocol; + +/// At most this many buyer addresses are dialed, whatever the accept says. +pub const MAX_BUYER_ADDRS: usize = 4; + +fn parse_list(raw: Option<&str>) -> Vec { + raw.unwrap_or("") + .split(',') + .map(str::trim) + .filter(|s| !s.is_empty()) + .filter_map(|s| s.parse::().ok()) + .collect() +} + +/// `C0MPUTE_P2P_LISTEN`: comma-separated multiaddrs to listen on. +pub fn listen_addrs(raw: Option<&str>) -> Vec { + parse_list(raw) +} + +/// `C0MPUTE_P2P_EXTERNAL` as the strings a buyer puts in its accepts, each +/// ending in `/p2p/`. +pub fn buyer_external_addrs(raw: Option<&str>, buyer_peer_id: &str) -> Vec { + parse_list(raw) + .into_iter() + .filter_map(|addr| { + let has_peer = addr.iter().any(|p| matches!(p, Protocol::P2p(_))); + let s = addr.to_string(); + if has_peer { + s.ends_with(&format!("/p2p/{buyer_peer_id}")).then_some(s) + } else { + Some(format!("{s}/p2p/{buyer_peer_id}")) + } + }) + .take(MAX_BUYER_ADDRS) + .collect() +} + +/// The buyer addresses a worker may dial: public IPv4/IPv6 over TCP, ending +/// in the accept's own buyer peer id, at most [`MAX_BUYER_ADDRS`]. Anything +/// else is dropped, so an accept cannot make a worker dial a private network +/// or a third party. +pub fn dialable_buyer_addrs(addrs: &[String], buyer_peer_id: &str) -> Vec { + addrs + .iter() + .filter_map(|s| s.parse::().ok()) + .filter(|addr| { + let mut ip_ok = false; + let mut tcp = false; + let mut peer_ok = false; + for p in addr.iter() { + match p { + Protocol::Ip4(ip) => ip_ok = crate::net_guard::is_public_ip(ip.into()), + Protocol::Ip6(ip) => ip_ok = crate::net_guard::is_public_ip(ip.into()), + Protocol::Tcp(_) => tcp = true, + Protocol::P2p(peer) => peer_ok = peer.to_base58() == buyer_peer_id, + _ => {} + } + } + ip_ok && tcp && peer_ok + }) + .take(MAX_BUYER_ADDRS) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + const PEER: &str = "12D3KooWADm5FfNxzPRanv1vacpPD7eh1bWv6pqmDCGE69Zof2n6"; + const OTHER: &str = "12D3KooWN99F7k4Fj6SdUqZ2VG4ZBLuw1e1ftwdPDBx3GsiDXzqv"; + + #[test] + fn external_addrs_get_the_buyer_peer_id() { + let a = buyer_external_addrs(Some("/ip4/23.95.228.174/tcp/46341"), PEER); + assert_eq!(a, vec![format!("/ip4/23.95.228.174/tcp/46341/p2p/{PEER}")]); + let wrong = buyer_external_addrs(Some(&format!("/ip4/1.2.3.4/tcp/1/p2p/{OTHER}")), PEER); + assert!(wrong.is_empty(), "an address for another peer is dropped"); + assert!(buyer_external_addrs(None, PEER).is_empty()); + assert!(buyer_external_addrs(Some("not a multiaddr"), PEER).is_empty()); + } + + #[test] + fn workers_only_dial_public_tcp_addrs_of_the_buyer() { + let good = format!("/ip4/23.95.228.174/tcp/46341/p2p/{PEER}"); + let private = format!("/ip4/10.0.0.5/tcp/46341/p2p/{PEER}"); + let loopback = format!("/ip4/127.0.0.1/tcp/46341/p2p/{PEER}"); + let other_peer = format!("/ip4/23.95.228.174/tcp/46341/p2p/{OTHER}"); + let no_peer = "/ip4/23.95.228.174/tcp/46341".to_string(); + let addrs = vec![good.clone(), private, loopback, other_peer, no_peer]; + let ok = dialable_buyer_addrs(&addrs, PEER); + assert_eq!(ok.len(), 1); + assert_eq!(ok[0].to_string(), good); + } + + #[test] + fn at_most_four_addrs() { + let addrs: Vec = (1..10) + .map(|i| format!("/ip4/23.95.228.{i}/tcp/46341/p2p/{PEER}")) + .collect(); + assert_eq!(dialable_buyer_addrs(&addrs, PEER).len(), MAX_BUYER_ADDRS); + } + + #[test] + fn listen_list_parses() { + assert_eq!( + listen_addrs(Some("/ip4/0.0.0.0/tcp/46341, /ip6/::/tcp/46341")).len(), + 2 + ); + assert!(listen_addrs(Some("")).is_empty()); + } +} diff --git a/node/crates/c0mpute-core/src/runner.rs b/node/crates/c0mpute-core/src/runner.rs index e87178cf..47bc56a6 100644 --- a/node/crates/c0mpute-core/src/runner.rs +++ b/node/crates/c0mpute-core/src/runner.rs @@ -593,6 +593,20 @@ pub(crate) async fn publish_receipt( signature: None, external_ref: accept.external_ref.clone(), }; + // Reach back to the buyer first: it can fetch the result only over a + // connection, and it usually cannot dial our random, firewalled port. + if receipt.output_hash.is_some() { + let addrs = + crate::reachability::dialable_buyer_addrs(&accept.buyer_addrs, &accept.buyer_peer_id); + if !addrs.is_empty() { + for addr in addrs { + if let Err(e) = net.dial(addr).await { + tracing::debug!(err = %e, "dial back to buyer failed"); + } + } + tokio::time::sleep(std::time::Duration::from_millis(1500)).await; + } + } let topic = job_topic(&accept.workload_type); if let Ok(payload) = serde_json::to_vec(&receipt) { if let Err(e) = net.publish(&topic, payload).await { diff --git a/node/crates/c0mpute-core/src/status_aggregator.rs b/node/crates/c0mpute-core/src/status_aggregator.rs index f164427c..4be2709b 100644 --- a/node/crates/c0mpute-core/src/status_aggregator.rs +++ b/node/crates/c0mpute-core/src/status_aggregator.rs @@ -883,6 +883,7 @@ mod tests { spec_inline: None, published_at_ms: now - 5_000, external_ref: None, + buyer_addrs: Vec::new(), }) .await; assert_eq!(jobs.in_flight.read().await.len(), 1); @@ -920,6 +921,7 @@ mod tests { spec_inline: None, published_at_ms: now, external_ref: None, + buyer_addrs: Vec::new(), }) .await; jobs.on_receipt(JobReceipt { diff --git a/node/crates/c0mpute-core/src/supervisor.rs b/node/crates/c0mpute-core/src/supervisor.rs index 2748dc86..0c21f711 100644 --- a/node/crates/c0mpute-core/src/supervisor.rs +++ b/node/crates/c0mpute-core/src/supervisor.rs @@ -48,9 +48,17 @@ impl Supervisor { .await; info!(count = bootstrap_addrs.len(), "bootstrap peers loaded"); - let net_cfg = NetworkConfig::for_dir(identity_dir) + let mut net_cfg = NetworkConfig::for_dir(identity_dir) .with_local_source(local_source) .with_bootstrap(bootstrap_addrs); + // A fixed, firewall-opened port for nodes that must be dialable + // (buyer gateways): C0MPUTE_P2P_LISTEN=/ip4/0.0.0.0/tcp/46341 + let listen = + crate::reachability::listen_addrs(std::env::var("C0MPUTE_P2P_LISTEN").ok().as_deref()); + if !listen.is_empty() { + info!(?listen, "libp2p listening on configured addresses"); + net_cfg = net_cfg.with_listen(listen); + } let libp2p_net: Arc = Arc::new(Libp2pNetwork::spawn(net_cfg).await?); info!( diff --git a/node/crates/c0mpute-core/src/webhook.rs b/node/crates/c0mpute-core/src/webhook.rs index a091d848..5c45702e 100644 --- a/node/crates/c0mpute-core/src/webhook.rs +++ b/node/crates/c0mpute-core/src/webhook.rs @@ -1228,7 +1228,7 @@ mod tests { fn accepts_must_match_the_webhook_offer_we_bid_on() { let s = serde_json::to_value(spec(3)).unwrap(); let bid = crate::dispatch::OpenBid { cap: 0.002, price: 0.001, buyer_peer_id: "buyer".into(), spec_inline: Some(s.clone()), external_ref: Some("coinpay:inv_1".into()), expires_at_ms: u64::MAX / 2 }; - let accept = JobAccept { job_id: "j".into(), buyer_peer_id: "buyer".into(), winning_bidder_peer_id: "us".into(), agreed_price_usd: 0.001, workload_type: WORKLOAD.into(), spec_inline: Some(s), published_at_ms: 0, external_ref: Some("coinpay:inv_1".into()) }; + let accept = JobAccept { job_id: "j".into(), buyer_peer_id: "buyer".into(), winning_bidder_peer_id: "us".into(), agreed_price_usd: 0.001, workload_type: WORKLOAD.into(), spec_inline: Some(s), published_at_ms: 0, external_ref: Some("coinpay:inv_1".into()), buyer_addrs: Vec::new() }; assert!(bid.matches(&accept, 0).is_ok()); // A forged accept swapping in another buyer key (to read the call) is refused. let mut a = accept.clone(); diff --git a/node/crates/c0mpute-net/src/topics.rs b/node/crates/c0mpute-net/src/topics.rs index ad9ea8d9..302a74c5 100644 --- a/node/crates/c0mpute-net/src/topics.rs +++ b/node/crates/c0mpute-net/src/topics.rs @@ -194,6 +194,12 @@ pub struct JobAccept { /// [`JobOffer::external_ref`]. #[serde(default, skip_serializing_if = "Option::is_none")] pub external_ref: Option, + /// The buyer's publicly dialable multiaddrs (`.../p2p/`). + /// A winning worker behind NAT dials one of these before publishing its + /// receipt, so the buyer can fetch the result over that connection; the + /// buyer usually cannot dial a worker's random, firewalled port. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub buyer_addrs: Vec, } /// Worker's completion receipt. Includes the output hash so the buyer diff --git a/package.json b/package.json index 1e1c82ec..7199b696 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "c0mpute-monorepo", - "version": "0.2.65", + "version": "0.2.66", "private": true, "workspaces": [ "apps/*", diff --git a/packages/shared/package.json b/packages/shared/package.json index e563b77a..c5358510 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@c0mpute/shared", - "version": "0.2.65", + "version": "0.2.66", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/plugins/ad-generator-agent/module.toml b/plugins/ad-generator-agent/module.toml index 6407daa5..15ccd5b1 100644 --- a/plugins/ad-generator-agent/module.toml +++ b/plugins/ad-generator-agent/module.toml @@ -21,7 +21,7 @@ [module] id = "ad-generator-agent" name = "Ad Generator Agent — every ad type from one URL" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "applications" description = "Turns a landing-page URL into a complete ad set (display banners, text link, terminal, feed item, image, GIF, 5-second video pre-roll and audio) by buying c0mpute work: copy from Infernet inference, voiceover from CPU text-to-speech, artwork from diffusion when workers offer it. No paid AI API by default." diff --git a/plugins/ads-manager/module.toml b/plugins/ads-manager/module.toml index e7cd3a97..51081b93 100644 --- a/plugins/ads-manager/module.toml +++ b/plugins/ads-manager/module.toml @@ -18,7 +18,7 @@ [module] id = "ads-manager" name = "Ads Manager — shared ad operations layer" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "applications" description = "Unified ad operations for the c0mpute network: campaign management, creative registry, publisher inventory, OpenRTB auction primitives, CoinPay escrow, and reporting. Required dependency for ctv-ads and all future ad format plugins." diff --git a/plugins/coinpay/module.toml b/plugins/coinpay/module.toml index 384df7a5..d61cbc62 100644 --- a/plugins/coinpay/module.toml +++ b/plugins/coinpay/module.toml @@ -7,7 +7,7 @@ [module] id = "coinpay" name = "CoinPay — DID, payments, escrow, reputation" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "applications" description = "Decentralized identifiers, wallet linking, escrow, signed receipts, and worker reputation. The trust spine of the c0mpute network." diff --git a/plugins/crawl/module.toml b/plugins/crawl/module.toml index 8a1925e6..1b0e1e8c 100644 --- a/plugins/crawl/module.toml +++ b/plugins/crawl/module.toml @@ -14,7 +14,7 @@ [module] id = "crawl" name = "Crawl — distributed web fetching" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "data" description = "Decentralized web crawling and page-rendering pool. Static fetches and JS-rendered pages, robots-aware, WARC-capable." diff --git a/plugins/ctv-ads/module.toml b/plugins/ctv-ads/module.toml index cf9af9a5..5e4679c6 100644 --- a/plugins/ctv-ads/module.toml +++ b/plugins/ctv-ads/module.toml @@ -20,7 +20,7 @@ [module] id = "ctv-ads" name = "CTV Ads — Connected TV ad marketplace" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "applications" description = "Decentralised Connected TV advertising on the c0mpute network. VAST 4.x endpoint, OpenRTB real-time auction, cryptographically verified impressions, CoinPay settlement. Supports Roku, Fire TV, Apple TV, Smart TVs, and FAST channels. Live-stream integration for ad-monetised broadcasts." diff --git a/plugins/diffusion/module.toml b/plugins/diffusion/module.toml index 105e7ed3..003704c9 100644 --- a/plugins/diffusion/module.toml +++ b/plugins/diffusion/module.toml @@ -11,7 +11,7 @@ [module] id = "diffusion" name = "Diffusion — image generation" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "ai" description = "Decentralized text-to-image on plain CPUs: Z-Image-Turbo through stable-diffusion.cpp, provisioned by every node with the RAM for it." diff --git a/plugins/embed/module.toml b/plugins/embed/module.toml index 141085ee..7bcb5ab9 100644 --- a/plugins/embed/module.toml +++ b/plugins/embed/module.toml @@ -13,7 +13,7 @@ [module] id = "embed" name = "Embeddings — vector generation" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "ai" description = "Bulk text-embedding generation (sentence-transformers, BGE, Nomic) with CPU fallback and optional GPU acceleration." diff --git a/plugins/gameserver/module.toml b/plugins/gameserver/module.toml index 1371728d..b8596fda 100644 --- a/plugins/gameserver/module.toml +++ b/plugins/gameserver/module.toml @@ -14,7 +14,7 @@ [module] id = "gameserver" name = "Game server — Minecraft / Factorio / Valheim" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "infrastructure" description = "Continuous-reservation hosting for game servers. Multi-replica placement, persistent world state on c0mpute storage, proof-of-serve settlement." diff --git a/plugins/hosting/module.toml b/plugins/hosting/module.toml index db3c90f7..47e044c0 100644 --- a/plugins/hosting/module.toml +++ b/plugins/hosting/module.toml @@ -12,7 +12,7 @@ [module] id = "hosting" name = "Hosting — censorship-resistant static sites" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "data" description = "Static-site hosting with multi-replica placement, mutable naming, epoch settlement, and slashable proof-of-serve. See DIP-0015." diff --git a/plugins/infernet/module.toml b/plugins/infernet/module.toml index 4a62af1a..8a21aa16 100644 --- a/plugins/infernet/module.toml +++ b/plugins/infernet/module.toml @@ -10,7 +10,7 @@ [module] id = "infernet" name = "Infernet Protocol — AI inference" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "ai" description = "Decentralized AI inference workloads on c0mpute. Runs models on consumer/prosumer GPUs with model-hash and runtime-image verification. Also trains and serves temporal policies (CfC/LTC liquid networks, GRU baseline) and fine-tunes language models (LoRA) with live progress, on CPU or GPU." diff --git a/plugins/live-stream/module.toml b/plugins/live-stream/module.toml index b3836369..de07dfbc 100644 --- a/plugins/live-stream/module.toml +++ b/plugins/live-stream/module.toml @@ -23,7 +23,7 @@ [module] id = "live-stream" name = "Live Stream — p2p HLS live streaming" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "media" description = "Decentralized live video streaming via BitTorrent-style HLS segment swarming. Broadcaster pushes RTMP/SRT; c0mpute nodes swarm segments to viewers. No central CDN. Censorship-resistant. Per-GB micropayments via CoinPay." diff --git a/plugins/ocr/module.toml b/plugins/ocr/module.toml index 62c19543..48df133f 100644 --- a/plugins/ocr/module.toml +++ b/plugins/ocr/module.toml @@ -12,7 +12,7 @@ [module] id = "ocr" name = "OCR — document text extraction" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "ai" description = "Decentralized OCR for PDFs and scanned documents (Tesseract, PaddleOCR, docTR) with CPU or GPU runners." diff --git a/plugins/podcast-ads/module.toml b/plugins/podcast-ads/module.toml index 3fe9b393..c88da7e3 100644 --- a/plugins/podcast-ads/module.toml +++ b/plugins/podcast-ads/module.toml @@ -22,7 +22,7 @@ [module] id = "podcast-ads" name = "Podcast Ads — audio ad marketplace" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "applications" description = "Decentralised audio advertising for c0mpute-hosted podcasts. DAAST 1.0 endpoint, dynamic ad insertion, Podcasting 2.0 revenue splits, and CoinPay settlement. Publishers sell ad slots; advertisers run campaigns — zero platform cut beyond protocol split." diff --git a/plugins/podcasting/module.toml b/plugins/podcasting/module.toml index a5f47c0a..bb77998a 100644 --- a/plugins/podcasting/module.toml +++ b/plugins/podcasting/module.toml @@ -20,7 +20,7 @@ [module] id = "podcasting" name = "Podcasting — p2p podcast publishing" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "media" description = "Censorship-resistant podcast hosting on the c0mpute network. Upload episodes, stream live shows, auto-transcribe, and monetise via CoinPay subscriptions, pay-per-episode, value-for-value, and audio ads — with zero platform cut. RSS 2.0 + Podcasting 2.0 compatible with all major podcast apps." diff --git a/plugins/podcasts/module.toml b/plugins/podcasts/module.toml index bd332708..9df2636e 100644 --- a/plugins/podcasts/module.toml +++ b/plugins/podcasts/module.toml @@ -19,7 +19,7 @@ [module] id = "podcasts" name = "Podcasts — p2p podcast listener" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "media" description = "Discover, subscribe to, and play podcasts hosted on the c0mpute network or any standard RSS feed. Stream live episodes, tip creators directly via CoinPay, and participate in value-for-value streaming payments. Works alongside any existing podcast app via OPML export and local RSS proxy." diff --git a/plugins/render/module.toml b/plugins/render/module.toml index d44cabb4..bbf6162d 100644 --- a/plugins/render/module.toml +++ b/plugins/render/module.toml @@ -13,7 +13,7 @@ [module] id = "render" name = "Render — Blender 3D rendering" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "media" description = "Decentralized 3D rendering using Blender headless (Cycles/Eevee) across consumer GPUs and CPUs." diff --git a/plugins/secure-chat/module.toml b/plugins/secure-chat/module.toml index c7c92af6..39bb771b 100644 --- a/plugins/secure-chat/module.toml +++ b/plugins/secure-chat/module.toml @@ -18,7 +18,7 @@ [module] id = "secure-chat" name = "Secure Chat — E2E encrypted p2p messaging" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "applications" description = "End-to-end encrypted peer-to-peer messaging across the c0mpute network. No central server. Keys stay with the user. Identity and reputation via CoinPay DID. Group chats, sealed-sender mode, and offline store-and-forward included." diff --git a/plugins/storage/module.toml b/plugins/storage/module.toml index 2cacb309..d26f4d58 100644 --- a/plugins/storage/module.toml +++ b/plugins/storage/module.toml @@ -12,7 +12,7 @@ [module] id = "storage" name = "Storage — Reed-Solomon shard service" -version = "0.2.65" +version = "0.2.66" kind = "service" category = "data" description = "Reed-Solomon 10/14 shard storage with auto-repair. Compute-locality, E2E encryption, content-addressed, pay-as-you-go. See DIP-0012." diff --git a/plugins/train/module.toml b/plugins/train/module.toml index 23a7158c..b4ca6f35 100644 --- a/plugins/train/module.toml +++ b/plugins/train/module.toml @@ -13,7 +13,7 @@ [module] id = "train" name = "Fine-tune — LoRA/QLoRA training" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "ai" description = "Decentralized model fine-tuning (LoRA, QLoRA) on consumer/prosumer GPUs using axolotl or unsloth." diff --git a/plugins/transcode/module.toml b/plugins/transcode/module.toml index 2df63d14..aba9ffb8 100644 --- a/plugins/transcode/module.toml +++ b/plugins/transcode/module.toml @@ -7,7 +7,7 @@ [module] id = "transcode" name = "FFmpeg Transcoding" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "media" description = "Decentralized audio/video transcoding via FFmpeg with hardware acceleration (NVENC, QSV, AMF, VideoToolbox, AV1)." diff --git a/plugins/tts/module.toml b/plugins/tts/module.toml index 35edc264..aae10d76 100644 --- a/plugins/tts/module.toml +++ b/plugins/tts/module.toml @@ -15,7 +15,7 @@ [module] id = "tts" name = "TTS — text-to-speech" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "ai" description = "CPU text-to-speech inference over c0mpute using Piper, Kokoro, or Pocket TTS." diff --git a/plugins/video/module.toml b/plugins/video/module.toml index d66f5fd8..9695e925 100644 --- a/plugins/video/module.toml +++ b/plugins/video/module.toml @@ -44,7 +44,7 @@ [module] id = "video" name = "Video — generation (Wan 2.2, LTX-2.3 with audio)" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "ai" description = "Decentralized video generation on consumer/prosumer GPUs: Wan 2.2 text/image-to-video, and LTX-2.3 video with a synchronized soundtrack." diff --git a/plugins/webhook/module.toml b/plugins/webhook/module.toml index 0b14a5d0..422400fa 100644 --- a/plugins/webhook/module.toml +++ b/plugins/webhook/module.toml @@ -7,7 +7,7 @@ [module] id = "webhook" name = "Webhooks — paid, end-to-end encrypted delivery" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "infrastructure" description = "Send webhooks from c0mpute workers, paid per delivery. The URL, headers, body and signing secret are sealed to the one worker that wins the job; relays and the market see only ciphertext. Standard Webhooks signatures, bounded retries with backoff, SSRF-guarded delivery (public addresses only, no redirects)." diff --git a/plugins/whisper/module.toml b/plugins/whisper/module.toml index ea3fd748..64fcc3e5 100644 --- a/plugins/whisper/module.toml +++ b/plugins/whisper/module.toml @@ -7,7 +7,7 @@ [module] id = "whisper" name = "Whisper — speech-to-text" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "ai" description = "CPU speech-to-text inference over c0mpute using faster-whisper." diff --git a/plugins/workflow-step/module.toml b/plugins/workflow-step/module.toml index e1e0f930..bb92c224 100644 --- a/plugins/workflow-step/module.toml +++ b/plugins/workflow-step/module.toml @@ -14,7 +14,7 @@ [module] id = "workflow-step" name = "Workflow step — webhook workflow steps (meshhook)" -version = "0.2.65" +version = "0.2.66" kind = "workload" category = "infrastructure" description = "Runs one step of a webhook workflow on a c0mpute worker: a JMESPath transform or a guarded HTTP call. Applications reach it through `c0mpute jobs serve`."