Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ members = [
]

[workspace.package]
version = "0.3.4"
version = "0.3.5"
edition = "2024"
license = "MIT"
repository = "https://github.com/profullstack/c0mpute"
Expand Down
2 changes: 1 addition & 1 deletion apps/tui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@c0mpute/tui",
"version": "0.3.4",
"version": "0.3.5",
"private": true,
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "c0mpute",
"version": "0.3.4",
"version": "0.3.5",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/releases/latest.json/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.3.4";
const FALLBACK_VERSION = "0.3.5";
const FALLBACK_MIN_REQUIRED = "0.0.1";

type Channel = "stable" | "beta" | "nightly";
Expand Down
65 changes: 52 additions & 13 deletions node/crates/c0mpute-core/src/hosting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ pub enum HostingMsg {
/// Where it is paid for marketplace copies.
#[serde(default, skip_serializing_if = "Option::is_none")]
payout: Option<Payee>,
/// It hosts but cannot take another copy (disk at its free floor or
/// hosting budget used up). Other nodes leave it out of the
/// rendezvous fill, so a site it would rank first for still gets its
/// copies from the next ranked nodes. Absent (false) from older nodes.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
full: bool,
/// Free disk above its floor, whole GiB: nodes with more room rank
/// first in the rendezvous fill. Absent from older nodes.
#[serde(default, skip_serializing_if = "Option::is_none")]
room_gib: Option<u64>,
},
/// A signed availability receipt (proof-of-storage challenge result).
/// Older nodes do not know this variant and skip it.
Expand Down Expand Up @@ -395,6 +405,8 @@ impl Hosting {
addrs,
price,
payout,
full,
room_gib,
} => {
if let Some(pid) = msg.source {
for a in addrs.iter().take(8) {
Expand All @@ -416,10 +428,10 @@ impl Hosting {
let (_, all) = partial.remove(&source).unwrap_or_default();
let price = price.as_deref().and_then(|p| crate::payments::parse_usd(p).ok());
let payout = payout.filter(|p| p.valid());
h.0.map
.write()
.await
.apply_holding_meta(&source, all, Instant::now(), hosts, price, payout);
let mut map = h.0.map.write().await;
map.apply_holding_meta(&source, all, Instant::now(), hosts, price, payout);
map.set_full(&source, full);
map.set_room(&source, room_gib);
}
}
HostingMsg::Receipt { receipt } => {
Expand Down Expand Up @@ -462,15 +474,13 @@ impl Hosting {
} else {
(None, None)
};
let full = self.0.hosts && self.is_full().await;
let room_gib = if self.0.hosts { self.room_gib() } else { None };
if self.0.hosts || !mine.is_empty() {
self.0.map.write().await.apply_holding_meta(
&self.0.me,
mine,
Instant::now(),
self.0.hosts,
price,
payout.clone(),
);
let mut map = self.0.map.write().await;
map.apply_holding_meta(&self.0.me, mine, Instant::now(), self.0.hosts, price, payout.clone());
map.set_full(&self.0.me, full);
map.set_room(&self.0.me, room_gib);
} else {
return;
}
Expand Down Expand Up @@ -499,6 +509,8 @@ impl Hosting {
addrs: addrs.clone(),
price: price.map(crate::payments::format_usd),
payout: payout.clone(),
full,
room_gib,
})
.await;
}
Expand Down Expand Up @@ -546,6 +558,9 @@ impl Hosting {

/// One pass: prune silent peers, then replicate, update or drop.
pub async fn repair_once(&self) {
// A full node does not put itself forward for new copies (it would be
// chosen, fail, and hold the slot from the next ranked node).
let can_take = self.0.hosts && !self.is_full().await;
let actions: Vec<(SiteRecord, Action)> = {
let mut map = self.0.map.write().await;
map.prune(Instant::now(), self.0.timing.holder_ttl);
Expand All @@ -556,7 +571,7 @@ impl Hosting {
&map,
&r.site_id,
&self.0.me,
self.0.hosts,
can_take,
TARGET_REPLICAS,
MAX_REPLICAS,
);
Expand Down Expand Up @@ -608,6 +623,23 @@ impl Hosting {
});
}

/// True when this node cannot take another copy: its disk is at the free
/// floor (with a little headroom) or its hosting budget is used up.
pub async fn is_full(&self) -> bool {
let (disk_free, disk_total) = crate::opendisk::disk_for(self.0.store.root());
!leaves_free_floor(disk_free, disk_total, FULL_HEADROOM_BYTES)
|| self.held_bytes().await.saturating_add(FULL_HEADROOM_BYTES) > self.0.max_bytes
}

/// Free disk above the floor in whole GiB, or None when the disk is unknown.
fn room_gib(&self) -> Option<u64> {
let (disk_free, disk_total) = crate::opendisk::disk_for(self.0.store.root());
if disk_total == 0 {
return None;
}
Some(disk_free.saturating_sub(free_disk_floor(disk_total)) / GIB_BYTES)
}

async fn held_bytes(&self) -> u64 {
self.0.catalog.read().await.sites.values().map(|r| r.size).sum()
}
Expand Down Expand Up @@ -1429,6 +1461,9 @@ const GIB_BYTES: u64 = 1024 * 1024 * 1024;
/// whichever is more, capped at 50 GiB. Every node hosts by default, including
/// busy production boxes, so the 5 GiB hosting budget alone could fill a disk
/// that was already nearly full.
/// Room a node must have beyond its floor/budget to call itself not full.
const FULL_HEADROOM_BYTES: u64 = 256 * 1024 * 1024;

fn free_disk_floor(disk_total: u64) -> u64 {
(disk_total / 20).clamp(10 * GIB_BYTES, 50 * GIB_BYTES)
}
Expand Down Expand Up @@ -1646,6 +1681,8 @@ mod tests {
addrs: vec![],
price: Some("0.004".into()),
payout: None,
full: false,
room_gib: Some(3000),
};
let bytes = serde_json::to_vec(&m).unwrap();
assert!(bytes.len() < 1024);
Expand Down Expand Up @@ -1679,6 +1716,8 @@ mod tests {
wallet: "0x".to_string() + &"a".repeat(40),
coin: "USDC_POL".into(),
}),
full: true,
room_gib: Some(u64::MAX),
})
.unwrap();
assert!(bytes.len() < 64 * 1024, "{} bytes", bytes.len());
Expand Down
Loading
Loading