Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e0aa8d3
fix(client): let store AIMD ramp off the cold-start floor (V2-554)
jacderida Jul 1, 2026
c37afd9
perf(client): de-wave the merkle store phase into one cap-bounded fan…
jacderida Jul 1, 2026
f5a7ebf
fix: bound merkle spill store memory and emit terminal wave-complete …
jacderida Jul 2, 2026
feec056
refactor!: remove the unused UploadEvent::WaveComplete event
jacderida Jul 2, 2026
278e6ea
Merge pull request #137 from jacderida/chrisoneil/v2-554-upload-aimd-…
jacderida Jul 2, 2026
fb99095
feat: evict a node when its disk runs low
jacderida Jun 30, 2026
694ec73
fix: skip evicted nodes when starting or stopping
jacderida Jul 1, 2026
a976938
test: cover loading a pre-upgrade registry file
jacderida Jul 1, 2026
34d8fef
fix: retry data-dir deletion during eviction to reclaim space on Windows
jacderida Jul 2, 2026
7bbb49e
fix: address eviction-safety review
jacderida Jul 3, 2026
a93e999
Merge pull request #138 from jacderida/feat/node-eviction-low-disk
jacderida Jul 3, 2026
ee39cb1
chore(release): release ant-client from main -> 0.2.10,0.3.0
jacderida Jul 3, 2026
7255f5b
Merge pull request #139 from WithAutonomi/release-ant-2026.07.1
jacderida Jul 3, 2026
b60c3df
feat(client): resolve-before-pay for commitment-bound quotes (ADR-0003)
grumbach Jun 17, 2026
e1439ee
chore: TEMP ADR-0004 git deps for CI — STRIP BEFORE MERGE
grumbach Jun 30, 2026
eec61c8
feat(client): thread ADR-0004 commitment sidecar through witnessed qu…
grumbach Jun 30, 2026
4302c64
fix(ci): rustfmt + bump quinn-proto 0.11.14 -> 0.11.15 (RUSTSEC-2026-…
grumbach Jun 30, 2026
9b93ad9
ci: run e2e_adr0004 in the E2E job
grumbach Jul 1, 2026
d676532
chore: bump ADR-0004 git deps to clean-rebased revs
grumbach Jul 1, 2026
440c8c9
test(adr-0004): cover client binding signature/hash/count checks
grumbach Jul 3, 2026
0414f39
chore(deps): regenerate Cargo.lock after rebase onto main (0.3.0)
grumbach Jul 3, 2026
325e821
style: rustfmt the ADR-0004 binding tests
grumbach Jul 3, 2026
556256e
fix(client): drop commitment sidecars from per-chunk merkle proofs
grumbach Jul 7, 2026
4e3aa7b
chore(deps): bump crossbeam-epoch 0.9.18 -> 0.9.20 (RUSTSEC-2026-0204)
grumbach Jul 7, 2026
22cf9fb
Add ADR-0005 client-side earned reward eligibility gate
grumbach Jul 9, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
anvil --version
forge --version
- name: Run E2E tests (serial)
run: cargo test -p ant-core --features test-utils --test e2e_chunk --test e2e_data --test e2e_file --test e2e_payment --test e2e_security --test e2e_cost_estimate -- --test-threads=1
run: cargo test -p ant-core --features test-utils --test e2e_chunk --test e2e_data --test e2e_file --test e2e_payment --test e2e_security --test e2e_cost_estimate --test e2e_adr0004 -- --test-threads=1

test-merkle:
name: Merkle E2E (${{ matrix.os }})
Expand Down
29 changes: 13 additions & 16 deletions Cargo.lock

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

20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
[workspace]
members = ["ant-core", "ant-cli"]
resolver = "2"

# ─── TEMPORARY: STRIP BEFORE MERGE — ADR-0004/0005 coordinated cutover ───
# The ADR-0004 commitment-bound-quote types (signed `committed_key_count`/
# `commitment_pin`, the commitment sidecar) and the ADR-0005 audit-report wire
# types are not yet published to crates.io, so this workspace cannot build
# against the published `ant-protocol`/`evmlib`. These git patches point those
# deps at their PR branches so CI can build the coordinated change.
#
# AT RELEASE (once the ADR-0004/0005 versions are published, in order
# evmlib → ant-protocol):
# 1. delete this entire [patch.crates-io] block, AND
# 2. bump the `ant-protocol` pin in ant-core/Cargo.toml to the published
# version (evmlib is re-exported through ant-protocol, no direct pin):
# ant-protocol = "2.3.0" # <- published ant-protocol
# # (which in turn depends on the published evmlib = "0.9.0")
[patch.crates-io]
evmlib = { git = "https://github.com/WithAutonomi/evmlib", branch = "adr-0003-signed-quote-fields" }
# ADR-0005 audit-report wire types (github.com/WithAutonomi/ant-protocol/pull/19),
# based on the ADR-0004 protocol branch and pinning the published evmlib 0.9.0.
ant-protocol = { git = "https://github.com/grumbach/ant-protocol", branch = "adr-0005-earned-reward-eligibility" }
2 changes: 1 addition & 1 deletion ant-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ant-cli"
version = "0.2.9"
version = "0.2.10"
edition = "2021"
description = "Unified CLI (`ant`) for the Autonomi network: store and retrieve data, and manage local nodes."
license = "MIT OR Apache-2.0"
Expand Down
7 changes: 0 additions & 7 deletions ant-cli/src/commands/data/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,13 +426,6 @@ async fn drive_upload_progress(
let pos = std::cmp::max(pb.position(), stored as u64);
pb.set_position(pos);
}
UploadEvent::WaveComplete {
stored_so_far,
total: _,
..
} => {
pb.set_position(stored_so_far as u64);
}
}
}

Expand Down
39 changes: 39 additions & 0 deletions ant-cli/src/commands/node/dismiss.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use clap::Args;
use colored::Colorize;

use ant_core::node::daemon::client;
use ant_core::node::types::DaemonConfig;

#[derive(Args)]
pub struct DismissArgs {
/// The ID of the evicted node to dismiss (remove it from the registry/list).
pub node_id: u32,
}

impl DismissArgs {
pub async fn execute(self, json_output: bool) -> anyhow::Result<()> {
let config = DaemonConfig::default();

// Dual-path: go through the daemon when it's running so its in-memory registry stays in
// sync; otherwise operate directly on the registry file.
let status = client::status(&config).await?;
let result = if status.running {
client::dismiss_node(&config, self.node_id).await?
} else {
ant_core::node::remove_node(self.node_id, &config.registry_path)?
};

if json_output {
println!("{}", serde_json::to_string_pretty(&result)?);
} else {
println!(
"{} Dismissed node {} ({})",
"✓".green().bold(),
result.removed.id.to_string().bold(),
result.removed.service_name.dimmed()
);
}

Ok(())
}
}
4 changes: 4 additions & 0 deletions ant-cli/src/commands/node/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod add;
pub mod daemon;
pub mod dismiss;
pub mod reset;
pub mod start;
pub mod status;
Expand All @@ -9,6 +10,7 @@ use clap::Subcommand;

use crate::commands::node::add::AddArgs;
use crate::commands::node::daemon::DaemonCommand;
use crate::commands::node::dismiss::DismissArgs;
use crate::commands::node::reset::ResetArgs;
use crate::commands::node::start::StartArgs;
use crate::commands::node::status::StatusArgs;
Expand All @@ -23,6 +25,8 @@ pub enum NodeCommand {
#[command(subcommand)]
command: DaemonCommand,
},
/// Dismiss an evicted node, removing it from the registry/list
Dismiss(DismissArgs),
/// Reset all node state (removes all data, logs, and clears the registry)
Reset(ResetArgs),
/// Start node(s). With no arguments starts all nodes; use --service-name for a specific node.
Expand Down
51 changes: 50 additions & 1 deletion ant-cli/src/commands/node/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::Args;
use colored::Colorize;

use ant_core::node::daemon::client;
use ant_core::node::daemon::health::{FleetHealth, HealthLevel};
use ant_core::node::types::{DaemonConfig, NodeStatus};

#[derive(Args)]
Expand All @@ -18,9 +19,27 @@ impl StatusArgs {
ant_core::node::node_status_offline(&config.registry_path)?
};

// Fleet health is only meaningful while the daemon is running (it owns the disk monitor).
let health = if daemon_status.running {
client::fleet_health(&config).await.ok()
} else {
None
};

if json_output {
println!("{}", serde_json::to_string_pretty(&result)?);
// Fold health into the JSON payload alongside node status.
let payload = serde_json::json!({
"nodes": result.nodes,
"total_running": result.total_running,
"total_stopped": result.total_stopped,
"health": health,
});
println!("{}", serde_json::to_string_pretty(&payload)?);
return Ok(());
} else {
if let Some(health) = &health {
print_fleet_health(health);
}
if result.nodes.is_empty() {
println!(
"{} No nodes registered. Add nodes first with: {}",
Expand Down Expand Up @@ -50,6 +69,7 @@ impl StatusArgs {
NodeStatus::UpgradeScheduled => {
format!("{} {}", "●".cyan(), "Upgrade scheduled".cyan())
}
NodeStatus::Evicted => format!("{} {}", "●".magenta(), "Evicted".magenta()),
};
let version_display = match &node.pending_version {
Some(pending) => format!("{} → {}", node.version, pending),
Expand All @@ -62,6 +82,15 @@ impl StatusArgs {
version_display.dimmed(),
status_display
);
// Supplementary text explaining an eviction, plus how to clear it.
if let Some(eviction) = &node.eviction {
println!(" {}", eviction.reason.dimmed());
println!(
" {} {}",
"dismiss with:".dimmed(),
format!("ant node dismiss {}", node.node_id).cyan()
);
}
}

if !daemon_status.running {
Expand All @@ -77,3 +106,23 @@ impl StatusArgs {
Ok(())
}
}

/// Print the fleet health summary: an overall line, plus a detail line per non-green check.
fn print_fleet_health(health: &FleetHealth) {
let (dot, label) = match health.overall {
HealthLevel::Green => ("●".green(), "Healthy".green()),
HealthLevel::Warning => ("●".yellow(), "Warning".yellow()),
HealthLevel::Critical => ("●".red(), "Critical".red()),
};
println!(" {} Fleet health: {}", dot, label);

// Surface the reason(s) whenever the fleet is not fully healthy.
for check in health
.checks
.iter()
.filter(|c| c.level != HealthLevel::Green)
{
println!(" {} {}", "→".dimmed(), check.summary.dimmed());
}
println!();
}
3 changes: 3 additions & 0 deletions ant-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ async fn run() -> anyhow::Result<()> {
commands::node::NodeCommand::Daemon { command } => {
command.execute(json).await?;
}
commands::node::NodeCommand::Dismiss(args) => {
args.execute(json).await?;
}
commands::node::NodeCommand::Reset(args) => {
args.execute(json).await?;
}
Expand Down
8 changes: 4 additions & 4 deletions ant-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ant-core"
version = "0.2.9"
version = "0.3.0"
edition = "2021"
description = "Headless Rust library for the Autonomi network: data storage and retrieval with self-encryption and EVM payments, plus node lifecycle management."
license = "MIT OR Apache-2.0"
Expand Down Expand Up @@ -37,7 +37,7 @@ tower-http = { version = "0.6.8", features = ["cors"] }
# under `ant_protocol::{evm, transport, pqc}`. This is the ONE pin for
# those three deps — do not add direct evmlib/saorsa-core/saorsa-pqc
# deps here or the version can skew between ant-client and ant-node.
ant-protocol = "2.2.2"
ant-protocol = "2.3.0"
xor_name = "5"
self_encryption = "0.36"
futures = "0.3"
Expand Down Expand Up @@ -65,7 +65,7 @@ sysinfo = { version = "0.32", default-features = false, features = ["system"] }
# `ant-protocol` pin above points at a git branch, this ant-node must point at
# the matching ant-node branch carrying the same saorsa-core / ant-protocol
# lineage rather than a released version.
ant-node = { version = "0.14.2", optional = true }
ant-node = { git = "https://github.com/grumbach/ant-node", branch = "adr-0005-earned-reward-eligibility", optional = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[target.'cfg(unix)'.dependencies]
Expand Down Expand Up @@ -96,7 +96,7 @@ test-utils = []
# always compile even without the `devnet` feature. Pinned to the same
# version as the runtime dep so there is a single ant-node /
# saorsa-core version across the whole graph.
ant-node = { version = "0.14.2", features = ["test-utils"] }
ant-node = { git = "https://github.com/grumbach/ant-node", branch = "adr-0005-earned-reward-eligibility", features = ["test-utils"] }
serial_test = "3"
anyhow = "1"
alloy = { version = "1.6", features = ["node-bindings"] }
Expand Down
4 changes: 3 additions & 1 deletion ant-core/examples/bench-quoting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ async fn bench_normal_once(client: &Client, rep: usize) -> Rep {
address,
data_size: 1024,
data_type: 0,
report_nonce: [0u8; 32],
}),
};
let bytes = match msg.encode() {
Expand Down Expand Up @@ -509,6 +510,7 @@ async fn bench_merkle_once(client: &Client, rep: usize, concurrency: usize) -> R
data_type: 0,
data_size: 1024,
merkle_payment_timestamp,
report_nonce: [0u8; 32],
}),
};
let bytes = match msg.encode() {
Expand All @@ -531,7 +533,7 @@ async fn bench_merkle_once(client: &Client, rep: usize, concurrency: usize) -> R
&addrs_clone,
|body| match body {
ChunkMessageBody::MerkleCandidateQuoteResponse(
MerkleCandidateQuoteResponse::Success { candidate_node },
MerkleCandidateQuoteResponse::Success { candidate_node, .. },
) => match rmp_serde::from_slice::<MerklePaymentCandidateNode>(
&candidate_node,
) {
Expand Down
Loading