From b9d5c9858e6c26425d920418d1e7634446530248 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 12:58:05 -0400 Subject: [PATCH 01/47] feat(hegel-workload): scaffold Rust project with deps --- hegel-workload/Cargo.toml | 16 ++++++++++++++++ hegel-workload/src/main.rs | 6 ++++++ 2 files changed, 22 insertions(+) create mode 100644 hegel-workload/Cargo.toml create mode 100644 hegel-workload/src/main.rs diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml new file mode 100644 index 00000000..da27cdfe --- /dev/null +++ b/hegel-workload/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hegel-workload" +version = "0.1.0" +edition = "2021" + +[features] +antithesis = ["hegeltest/antithesis"] + +[dependencies] +hegeltest = "0.3" +libp2p = { version = "0.55", features = ["gossipsub", "tcp", "noise", "yamux", "tokio", "macros"] } +tokio = { version = "1", features = ["full"] } +log = "0.4" +env_logger = "0.11" +sha2 = "0.10" +futures = "0.3" diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs new file mode 100644 index 00000000..bef4df36 --- /dev/null +++ b/hegel-workload/src/main.rs @@ -0,0 +1,6 @@ +use log::info; + +fn main() { + env_logger::init(); + info!("hegel-workload starting"); +} From 39ff5b848f1c0a587b62c166b58983fd9d641960 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:00:02 -0400 Subject: [PATCH 02/47] feat(hegel-workload): add CBOR construction helpers Port Go cbor_helpers.go patterns to Rust for building raw CBOR bytes directly without Filecoin type serializers. Includes helpers for all major CBOR types (uint64, int64, bytes, text, nil, bool, array), CID encoding, big integer bytes, and random CID generation. 19 tests covering all encoding paths. --- hegel-workload/src/cbor.rs | 241 +++++++++++++++++++++++++++++++++++++ hegel-workload/src/main.rs | 2 + 2 files changed, 243 insertions(+) create mode 100644 hegel-workload/src/cbor.rs diff --git a/hegel-workload/src/cbor.rs b/hegel-workload/src/cbor.rs new file mode 100644 index 00000000..62678cb0 --- /dev/null +++ b/hegel-workload/src/cbor.rs @@ -0,0 +1,241 @@ +/// Write a CBOR major type header into a Vec. +fn write_major_type(buf: &mut Vec, major: u8, value: u64) { + let mt = major << 5; + if value < 24 { + buf.push(mt | value as u8); + } else if value < 256 { + buf.push(mt | 24); + buf.push(value as u8); + } else if value < 65536 { + buf.push(mt | 25); + buf.extend_from_slice(&(value as u16).to_be_bytes()); + } else if value < 4_294_967_296 { + buf.push(mt | 26); + buf.extend_from_slice(&(value as u32).to_be_bytes()); + } else { + buf.push(mt | 27); + buf.extend_from_slice(&value.to_be_bytes()); + } +} + +pub fn cbor_uint64(v: u64) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 0, v); + buf +} + +pub fn cbor_int64(v: i64) -> Vec { + if v >= 0 { + cbor_uint64(v as u64) + } else { + let mut buf = Vec::new(); + write_major_type(&mut buf, 1, (-v - 1) as u64); + buf + } +} + +pub fn cbor_bytes(b: &[u8]) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 2, b.len() as u64); + buf.extend_from_slice(b); + buf +} + +pub fn cbor_text(s: &str) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 3, s.len() as u64); + buf.extend_from_slice(s.as_bytes()); + buf +} + +pub fn cbor_nil() -> Vec { + vec![0xf6] +} + +pub fn cbor_bool(v: bool) -> Vec { + if v { + vec![0xf5] + } else { + vec![0xf4] + } +} + +pub fn cbor_array(elements: &[&[u8]]) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 4, elements.len() as u64); + for e in elements { + buf.extend_from_slice(e); + } + buf +} + +pub fn cbor_cid(cid_bytes: &[u8]) -> Vec { + let mut buf = Vec::new(); + write_major_type(&mut buf, 6, 42); // CBOR tag 42 + let tagged_len = cid_bytes.len() + 1; + write_major_type(&mut buf, 2, tagged_len as u64); + buf.push(0x00); // multibase identity prefix + buf.extend_from_slice(cid_bytes); + buf +} + +pub fn big_int_bytes(v: u64) -> Vec { + if v == 0 { + return vec![]; + } + let raw = v.to_be_bytes(); + let start = raw.iter().position(|&b| b != 0).unwrap_or(7); + let mut result = Vec::with_capacity(1 + raw.len() - start); + result.push(0x00); // positive sign + result.extend_from_slice(&raw[start..]); + result +} + +pub fn random_cid() -> Vec { + use sha2::{Digest, Sha256}; + let data: Vec = (0..32).map(|_| rand_byte()).collect(); + let hash = Sha256::digest(&data); + let mut cid = Vec::new(); + cid.push(0x01); // CIDv1 + cid.push(0x71); // dag-cbor codec + cid.push(0x12); // sha2-256 hash function + cid.push(0x20); // 32-byte digest + cid.extend_from_slice(&hash); + cid +} + +fn rand_byte() -> u8 { + use std::collections::hash_map::RandomState; + use std::hash::{BuildHasher, Hasher}; + let s = RandomState::new(); + let mut h = s.build_hasher(); + h.write_u8(0); + (h.finish() & 0xFF) as u8 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cbor_uint64_zero() { + assert_eq!(cbor_uint64(0), vec![0x00]); + } + + #[test] + fn test_cbor_uint64_small() { + assert_eq!(cbor_uint64(23), vec![0x17]); + } + + #[test] + fn test_cbor_uint64_one_byte() { + assert_eq!(cbor_uint64(24), vec![0x18, 0x18]); + } + + #[test] + fn test_cbor_nil() { + assert_eq!(cbor_nil(), vec![0xf6]); + } + + #[test] + fn test_cbor_bool() { + assert_eq!(cbor_bool(true), vec![0xf5]); + assert_eq!(cbor_bool(false), vec![0xf4]); + } + + #[test] + fn test_cbor_bytes_empty() { + assert_eq!(cbor_bytes(&[]), vec![0x40]); + } + + #[test] + fn test_cbor_bytes_data() { + assert_eq!(cbor_bytes(&[1, 2, 3]), vec![0x43, 1, 2, 3]); + } + + #[test] + fn test_cbor_array_empty() { + assert_eq!(cbor_array(&[]), vec![0x80]); + } + + #[test] + fn test_cbor_array_nested() { + let elements: Vec> = vec![cbor_uint64(1), cbor_nil()]; + let refs: Vec<&[u8]> = elements.iter().map(|e| e.as_slice()).collect(); + assert_eq!(cbor_array(&refs), vec![0x82, 0x01, 0xf6]); + } + + #[test] + fn test_cbor_int64_positive() { + assert_eq!(cbor_int64(0), cbor_uint64(0)); + assert_eq!(cbor_int64(10), cbor_uint64(10)); + } + + #[test] + fn test_cbor_int64_negative() { + assert_eq!(cbor_int64(-1), vec![0x20]); + assert_eq!(cbor_int64(-10), vec![0x29]); + } + + #[test] + fn test_big_int_bytes_zero() { + assert_eq!(big_int_bytes(0), vec![]); + } + + #[test] + fn test_big_int_bytes_positive() { + assert_eq!(big_int_bytes(1), vec![0x00, 0x01]); + assert_eq!(big_int_bytes(256), vec![0x00, 0x01, 0x00]); + } + + #[test] + fn test_cbor_text() { + assert_eq!(cbor_text(""), vec![0x60]); + assert_eq!(cbor_text("hi"), vec![0x62, b'h', b'i']); + } + + #[test] + fn test_cbor_cid() { + let cid_bytes = vec![0x01, 0x71, 0x12, 0x20]; + let encoded = cbor_cid(&cid_bytes); + // Tag 42: 0xd8 0x2a, then byte string of len 5 (4 + 1 prefix): 0x45, then 0x00 prefix + assert_eq!(encoded[0], 0xd8); + assert_eq!(encoded[1], 0x2a); + assert_eq!(encoded[2], 0x45); + assert_eq!(encoded[3], 0x00); + assert_eq!(&encoded[4..], &cid_bytes[..]); + } + + #[test] + fn test_random_cid_length() { + let cid = random_cid(); + // CIDv1 header (4 bytes) + 32-byte SHA-256 digest = 36 bytes + assert_eq!(cid.len(), 36); + assert_eq!(cid[0], 0x01); // CIDv1 + assert_eq!(cid[1], 0x71); // dag-cbor + assert_eq!(cid[2], 0x12); // sha2-256 + assert_eq!(cid[3], 0x20); // 32 bytes + } + + #[test] + fn test_cbor_uint64_two_byte() { + // 256 = 0x100, needs 2-byte encoding + assert_eq!(cbor_uint64(256), vec![0x19, 0x01, 0x00]); + } + + #[test] + fn test_cbor_uint64_four_byte() { + // 65536 = 0x10000, needs 4-byte encoding + assert_eq!(cbor_uint64(65536), vec![0x1a, 0x00, 0x01, 0x00, 0x00]); + } + + #[test] + fn test_cbor_uint64_eight_byte() { + let v: u64 = 4_294_967_296; // 0x100000000 + let encoded = cbor_uint64(v); + assert_eq!( + encoded, + vec![0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00] + ); + } +} diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index bef4df36..390ec90c 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -1,3 +1,5 @@ +mod cbor; + use log::info; fn main() { From 4ffed2244c539aed8c2f11aa38b71211617ca1f9 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:01:29 -0400 Subject: [PATCH 03/47] feat(hegel-workload): add peer discovery from multiaddr files --- hegel-workload/src/discovery.rs | 109 ++++++++++++++++++++++++++++++++ hegel-workload/src/main.rs | 1 + 2 files changed, 110 insertions(+) create mode 100644 hegel-workload/src/discovery.rs diff --git a/hegel-workload/src/discovery.rs b/hegel-workload/src/discovery.rs new file mode 100644 index 00000000..afce7ee8 --- /dev/null +++ b/hegel-workload/src/discovery.rs @@ -0,0 +1,109 @@ +use libp2p::{Multiaddr, PeerId}; +use log::{info, warn}; +use std::time::Duration; + +/// A discovered target node with its libp2p address and peer ID. +#[derive(Debug, Clone)] +pub struct TargetNode { + pub name: String, + pub addr: Multiaddr, + pub peer_id: PeerId, +} + +/// Parse a multiaddr string like "/ip4/.../tcp/.../p2p/" into (dial_addr, peer_id). +/// Returns the multiaddr without the /p2p/ suffix (for dialing) and the extracted PeerId. +pub fn parse_multiaddr(s: &str) -> Option<(Multiaddr, PeerId)> { + let full: Multiaddr = s.parse().ok()?; + let mut addr = Multiaddr::empty(); + let mut peer_id = None; + for proto in full.iter() { + match proto { + libp2p::multiaddr::Protocol::P2p(id) => { + peer_id = Some(id); + } + other => { + addr.push(other); + } + } + } + Some((addr, peer_id?)) +} + +/// Discover nodes by reading multiaddr files from devgen directory. +/// Retries every 5 seconds for up to 5 minutes per node. +pub fn discover_nodes(names: &[String], devgen_dir: &str) -> Vec { + let mut nodes = Vec::new(); + for name in names { + let path = format!("{}/{}/{}-ipv4addr", devgen_dir, name, name); + info!("waiting for multiaddr file: {}", path); + + let mut content = None; + for attempt in 0..60 { + match std::fs::read_to_string(&path) { + Ok(s) if !s.trim().is_empty() => { + content = Some(s); + break; + } + _ => { + if attempt % 12 == 0 { + info!("still waiting for {} (attempt {})", path, attempt); + } + std::thread::sleep(Duration::from_secs(5)); + } + } + } + + let Some(data) = content else { + warn!("timed out waiting for {}, skipping", path); + continue; + }; + + let line = data.trim(); + match parse_multiaddr(line) { + Some((addr, peer_id)) => { + info!("discovered {} at {} (peer {})", name, addr, peer_id); + nodes.push(TargetNode { + name: name.clone(), + addr, + peer_id, + }); + } + None => { + warn!("failed to parse multiaddr for {}: {:?}", name, line); + } + } + } + nodes +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_multiaddr_line() { + let line = "/ip4/172.18.0.6/tcp/37081/p2p/12D3KooWKDdkB19Qi5S5Kq69dPfczv1Zdx1dj4ivsgLNKkKtWx5a"; + let (addr, peer_id) = parse_multiaddr(line).unwrap(); + assert!(addr.to_string().contains("172.18.0.6")); + assert!(!peer_id.to_string().is_empty()); + } + + #[test] + fn test_parse_multiaddr_with_whitespace() { + let line = "/ip4/10.0.0.1/tcp/1234/p2p/12D3KooWKDdkB19Qi5S5Kq69dPfczv1Zdx1dj4ivsgLNKkKtWx5a"; + let result = parse_multiaddr(line.trim()); + assert!(result.is_some()); + } + + #[test] + fn test_parse_multiaddr_invalid() { + assert!(parse_multiaddr("not-a-multiaddr").is_none()); + assert!(parse_multiaddr("").is_none()); + } + + #[test] + fn test_parse_multiaddr_no_peer_id() { + // Multiaddr without /p2p/ component should return None + assert!(parse_multiaddr("/ip4/10.0.0.1/tcp/1234").is_none()); + } +} diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index 390ec90c..b4feb42a 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -1,4 +1,5 @@ mod cbor; +mod discovery; use log::info; From 554c19b729861362d1a1bb5ab04aad6a4a545b38 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:02:52 -0400 Subject: [PATCH 04/47] feat(hegel-workload): add libp2p GossipSub network layer --- hegel-workload/src/main.rs | 1 + hegel-workload/src/network.rs | 121 ++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 hegel-workload/src/network.rs diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index b4feb42a..91c2779f 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -1,5 +1,6 @@ mod cbor; mod discovery; +mod network; use log::info; diff --git a/hegel-workload/src/network.rs b/hegel-workload/src/network.rs new file mode 100644 index 00000000..a42a2c86 --- /dev/null +++ b/hegel-workload/src/network.rs @@ -0,0 +1,121 @@ +use libp2p::{ + gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId, ValidationMode}, + identity, + noise, + swarm::SwarmEvent, + tcp, yamux, Multiaddr, PeerId, Swarm, +}; +use log::{info, warn}; +use sha2::{Sha256, Digest}; +use std::time::Duration; +use tokio::sync::mpsc; +use futures::StreamExt; + +/// Messages sent from the generator thread to the network task. +pub struct PublishRequest { + pub topic: String, + pub data: Vec, +} + +/// Build a libp2p Swarm with GossipSub configured for Filecoin interop. +pub fn build_swarm() -> Result, Box> { + let local_key = identity::Keypair::generate_ed25519(); + + // Content-hash message ID function — critical for Lotus interop. + let message_id_fn = |message: &gossipsub::Message| -> MessageId { + let mut hasher = Sha256::new(); + hasher.update(&message.data); + MessageId::from(hasher.finalize().to_vec()) + }; + + let gossipsub_config = gossipsub::ConfigBuilder::default() + .heartbeat_interval(Duration::from_secs(1)) + .validation_mode(ValidationMode::Permissive) + .message_id_fn(message_id_fn) + .build() + .map_err(|e| format!("gossipsub config error: {}", e))?; + + let gossipsub = gossipsub::Behaviour::new( + MessageAuthenticity::Signed(local_key.clone()), + gossipsub_config, + ) + .map_err(|e| format!("gossipsub behaviour error: {}", e))?; + + let swarm = libp2p::SwarmBuilder::with_existing_identity(local_key) + .with_tokio() + .with_tcp( + tcp::Config::default(), + noise::Config::new, + yamux::Config::default, + )? + .with_behaviour(|_| gossipsub)? + .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60))) + .build(); + + Ok(swarm) +} + +/// Run the network event loop. Connects to peers, subscribes to topics, and +/// publishes messages received on the `rx` channel. +pub async fn run_network( + mut swarm: Swarm, + peers: Vec<(Multiaddr, PeerId)>, + topics: Vec, + mut rx: mpsc::Receiver, +) { + // Listen on random port + swarm + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .expect("failed to listen"); + + // Dial all peers + for (addr, peer_id) in &peers { + let dial_addr = addr.clone().with(libp2p::multiaddr::Protocol::P2p(*peer_id)); + match swarm.dial(dial_addr.clone()) { + Ok(_) => info!("dialing {}", dial_addr), + Err(e) => warn!("failed to dial {}: {}", dial_addr, e), + } + } + + // Subscribe to topics + for topic_str in &topics { + let topic = IdentTopic::new(topic_str); + if let Err(e) = swarm.behaviour_mut().subscribe(&topic) { + warn!("failed to subscribe to {}: {}", topic_str, e); + } else { + info!("subscribed to {}", topic_str); + } + } + + info!("waiting 5s for GossipSub mesh formation..."); + let mesh_wait = tokio::time::sleep(Duration::from_secs(5)); + tokio::pin!(mesh_wait); + + loop { + tokio::select! { + _ = &mut mesh_wait => {} + event = swarm.select_next_some() => { + match event { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + info!("connected to {}", peer_id); + } + SwarmEvent::ConnectionClosed { peer_id, .. } => { + warn!("disconnected from {}", peer_id); + } + _ => {} + } + } + Some(req) = rx.recv() => { + let topic = IdentTopic::new(&req.topic); + match swarm.behaviour_mut().publish(topic, req.data) { + Ok(msg_id) => { + log::debug!("published to {}: {:?}", req.topic, msg_id); + } + Err(e) => { + warn!("publish to {} failed: {}", req.topic, e); + } + } + } + } + } +} From e647786a40dc1a7ac74859d7d878d92b3f7ba0f8 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:05:54 -0400 Subject: [PATCH 05/47] feat(hegel-workload): add Hegel address generators for all protocol types Composite generators for Filecoin addresses across all five protocol types (ID, secp256k1, actor, BLS, delegated) plus invalid protocol edge cases. Includes placeholder modules for message and block generators. --- hegel-workload/src/generators/address.rs | 167 ++++++++++++++++++++++ hegel-workload/src/generators/blocks.rs | 1 + hegel-workload/src/generators/messages.rs | 1 + hegel-workload/src/generators/mod.rs | 3 + hegel-workload/src/main.rs | 1 + 5 files changed, 173 insertions(+) create mode 100644 hegel-workload/src/generators/address.rs create mode 100644 hegel-workload/src/generators/blocks.rs create mode 100644 hegel-workload/src/generators/messages.rs create mode 100644 hegel-workload/src/generators/mod.rs diff --git a/hegel-workload/src/generators/address.rs b/hegel-workload/src/generators/address.rs new file mode 100644 index 00000000..5f141c11 --- /dev/null +++ b/hegel-workload/src/generators/address.rs @@ -0,0 +1,167 @@ +use hegel::generators as gs; + +/// Generate a Filecoin address as raw bytes. +#[hegel::composite] +pub fn filecoin_address(tc: hegel::TestCase) -> Vec { + let protocol: u8 = tc.draw(gs::sampled_from(vec![0u8, 1, 2, 3, 4, 5, 0xff])); + match protocol { + 0 => tc.draw(id_address()), + 1 => tc.draw(hash_address(1)), + 2 => tc.draw(hash_address(2)), + 3 => tc.draw(bls_address()), + 4 => tc.draw(delegated_address()), + _ => { + let len: usize = tc.draw(gs::integers::().min_value(0).max_value(20)); + let mut addr = vec![protocol]; + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + addr.extend(payload); + addr + } + } +} + +/// ID address: protocol 0 + varint-encoded actor ID with edge cases. +#[hegel::composite] +pub fn id_address(tc: hegel::TestCase) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(4)); + match variant { + 0 => vec![0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f], // overflow varint + 1 => { + vec![ + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, + ] // 10-byte varint + } + 2 => vec![0x00, 0x80], // truncated varint + 3 => vec![0x00], // empty payload + _ => { + let len: usize = tc.draw(gs::integers::().min_value(1).max_value(9)); + let mut addr = vec![0x00]; + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + addr.extend(payload); + addr + } + } +} + +/// Hash address (secp256k1=1 or actor=2): 20-byte payload with fuzzed lengths. +#[hegel::composite] +pub fn hash_address(tc: hegel::TestCase, proto_byte: u8) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(3)); + match variant { + 0 => { + let len: usize = tc.draw(gs::integers::().min_value(0).max_value(40)); + let mut addr = vec![proto_byte]; + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + addr.extend(payload); + addr + } + 1 => vec![proto_byte], // empty payload + 2 => { + let mut addr = vec![proto_byte]; + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(30).max_size(30)); + addr.extend(payload); + addr + } + _ => { + let mut addr = vec![proto_byte]; + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(20).max_size(20)); + addr.extend(payload); + addr + } + } +} + +/// BLS address: protocol 3, 48-byte public key with varied lengths. +#[hegel::composite] +pub fn bls_address(tc: hegel::TestCase) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); + match variant { + 0 => { + let len: usize = tc.draw(gs::integers::().min_value(0).max_value(60)); + let mut addr = vec![0x03]; + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + addr.extend(payload); + addr + } + 1 => vec![0x03], // empty payload + _ => { + let mut addr = vec![0x03]; + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(48).max_size(48)); + addr.extend(payload); + addr + } + } +} + +/// Delegated address: protocol 4, namespace varint + sub-address with edge cases. +#[hegel::composite] +pub fn delegated_address(tc: hegel::TestCase) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(5)); + match variant { + 0 => vec![0x04, 0x0a], // empty sub-address + 1 => vec![0x04, 0xff, 0xff, 0xff, 0xff, 0x0f], // max namespace varint + 2 => vec![0x04, 0x80], // truncated namespace varint + 3 => { + let mut addr = vec![0x04, 0x0a]; // namespace 10 + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(128).max_size(128)); + addr.extend(payload); + addr + } + 4 => { + let mut addr = vec![0x04, 0x00]; // namespace 0 (invalid) + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(20).max_size(20)); + addr.extend(payload); + addr + } + _ => { + let len: usize = tc.draw(gs::integers::().min_value(0).max_value(40)); + let mut addr = vec![0x04, 0x0a]; // EAM namespace 10 + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + addr.extend(payload); + addr + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[hegel::test(test_cases = 50)] + fn test_filecoin_address_produces_bytes(tc: hegel::TestCase) { + let addr: Vec = tc.draw(filecoin_address()); + assert!(!addr.is_empty(), "address must not be empty"); + let proto = addr[0]; + assert!( + proto <= 5 || proto == 0xff, + "unexpected protocol byte: {}", + proto + ); + } + + #[hegel::test(test_cases = 50)] + fn test_valid_id_address(tc: hegel::TestCase) { + let addr: Vec = tc.draw(id_address()); + assert_eq!(addr[0], 0x00, "ID address protocol must be 0"); + assert!( + addr.len() >= 1, + "ID address must have at least protocol byte" + ); + } + + #[hegel::test(test_cases = 50)] + fn test_bls_address_protocol(tc: hegel::TestCase) { + let addr: Vec = tc.draw(bls_address()); + assert_eq!(addr[0], 0x03, "BLS address protocol must be 3"); + } +} diff --git a/hegel-workload/src/generators/blocks.rs b/hegel-workload/src/generators/blocks.rs new file mode 100644 index 00000000..2c9afa1e --- /dev/null +++ b/hegel-workload/src/generators/blocks.rs @@ -0,0 +1 @@ +// Implemented in Task 7 diff --git a/hegel-workload/src/generators/messages.rs b/hegel-workload/src/generators/messages.rs new file mode 100644 index 00000000..bbcca094 --- /dev/null +++ b/hegel-workload/src/generators/messages.rs @@ -0,0 +1 @@ +// Implemented in Task 6 diff --git a/hegel-workload/src/generators/mod.rs b/hegel-workload/src/generators/mod.rs new file mode 100644 index 00000000..80665be8 --- /dev/null +++ b/hegel-workload/src/generators/mod.rs @@ -0,0 +1,3 @@ +pub mod address; +pub mod blocks; +pub mod messages; diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index 91c2779f..c122682e 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -1,5 +1,6 @@ mod cbor; mod discovery; +mod generators; mod network; use log::info; From f26f81cb0b7cf0e0acf026ba4e4fe8fae0242dd4 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:08:28 -0400 Subject: [PATCH 06/47] feat(hegel-workload): add Hegel SignedMessage generator Implement CBOR SignedMessage generator with fuzzed fields for Filecoin message format: array(2) [Message, Signature] where Message is array(10) with varied addresses, nonces, BigInt values, gas params, methods, and params. Includes edge-case generation for signatures with varied type bytes and lengths. --- hegel-workload/src/generators/messages.rs | 144 +++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/hegel-workload/src/generators/messages.rs b/hegel-workload/src/generators/messages.rs index bbcca094..e28a6285 100644 --- a/hegel-workload/src/generators/messages.rs +++ b/hegel-workload/src/generators/messages.rs @@ -1 +1,143 @@ -// Implemented in Task 6 +use hegel::generators as gs; + +use crate::cbor::*; +use crate::generators::address::filecoin_address; + +/// Generate a complete SignedMessage as CBOR: array(2) [Message, Signature]. +#[hegel::composite] +pub fn signed_message(tc: hegel::TestCase) -> Vec { + let msg = tc.draw(filecoin_message()); + let sig = tc.draw(signature()); + cbor_array(&[&msg, &sig]) +} + +/// Generate a Filecoin Message as CBOR array(10): +/// [Version, To, From, Nonce, Value, GasLimit, GasFeeCap, GasPremium, Method, Params] +#[hegel::composite] +fn filecoin_message(tc: hegel::TestCase) -> Vec { + let version = cbor_uint64(0); + + let to_addr: Vec = tc.draw(filecoin_address()); + let to = cbor_bytes(&to_addr); + + let from_addr: Vec = tc.draw(filecoin_address()); + let from = cbor_bytes(&from_addr); + + let nonce_val: u64 = + tc.draw(gs::sampled_from(vec![0u64, 1, u64::MAX - 1, u64::MAX, 42, 1000, 1_000_000])); + let nonce = cbor_uint64(nonce_val); + + let value_raw: Vec = tc.draw(big_int_value()); + let value = cbor_bytes(&value_raw); + + let gas_limit_val: i64 = tc.draw(gs::sampled_from(vec![ + 0i64, + 1, + -1, + 10_000_000, + i64::MAX, + i64::MIN + 1, + 100_000, + -100_000, + ])); + let gas_limit = cbor_int64(gas_limit_val); + + let gas_fee_cap_raw: Vec = tc.draw(big_int_value()); + let gas_fee_cap = cbor_bytes(&gas_fee_cap_raw); + + let gas_premium_raw: Vec = tc.draw(big_int_value()); + let gas_premium = cbor_bytes(&gas_premium_raw); + + let method_val: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, u64::MAX, 999999, + ])); + let method = cbor_uint64(method_val); + + let params = tc.draw(message_params()); + + cbor_array(&[ + &version, + &to, + &from, + &nonce, + &value, + &gas_limit, + &gas_fee_cap, + &gas_premium, + &method, + ¶ms, + ]) +} + +/// Generate edge-case BigInt values for Value/GasFeeCap/GasPremium fields. +#[hegel::composite] +fn big_int_value(tc: hegel::TestCase) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(4)); + match variant { + 0 => big_int_bytes(0), // zero + 1 => big_int_bytes(1), // minimal positive + 2 => big_int_bytes(u64::MAX), // max uint64 + 3 => vec![0x00], // sign-only (positive sign, zero magnitude) + _ => { + // large random value + let len: usize = tc.draw(gs::integers::().min_value(1).max_value(16)); + let mut bytes = vec![0x00u8]; // positive sign + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + bytes.extend(payload); + bytes + } + } +} + +/// Generate a Signature as CBOR bytes: type_byte followed by signature data. +#[hegel::composite] +fn signature(tc: hegel::TestCase) -> Vec { + let sig_type: u8 = tc.draw(gs::sampled_from(vec![1u8, 2, 0, 3, 0xff])); + let sig_len: usize = tc.draw(gs::sampled_from(vec![0usize, 1, 64, 65, 96, 48, 128])); + + let mut sig_data = vec![sig_type]; + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(sig_len).max_size(sig_len)); + sig_data.extend(payload); + cbor_bytes(&sig_data) +} + +/// Generate message params: empty, valid CBOR empty array, or random bytes. +#[hegel::composite] +fn message_params(tc: hegel::TestCase) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); + match variant { + 0 => cbor_bytes(&[]), // empty params + 1 => cbor_bytes(&cbor_array(&[])), // valid CBOR empty array as params + _ => { + // random bytes + let len: usize = tc.draw(gs::integers::().min_value(1).max_value(64)); + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + cbor_bytes(&payload) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[hegel::test(test_cases = 50)] + fn test_signed_message_is_valid_cbor_structure(tc: hegel::TestCase) { + let msg_bytes: Vec = tc.draw(signed_message()); + assert_eq!(msg_bytes[0], 0x82, "SignedMessage must be CBOR array(2)"); + } + + #[hegel::test(test_cases = 50)] + fn test_signed_message_nonempty(tc: hegel::TestCase) { + let msg_bytes: Vec = tc.draw(signed_message()); + assert!( + msg_bytes.len() > 10, + "message too short: {} bytes", + msg_bytes.len() + ); + } +} From 431912f5c13816aee26f6e04f8f391916a6f4a84 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:09:56 -0400 Subject: [PATCH 07/47] feat(hegel-workload): add Hegel BlockMsg generator --- hegel-workload/src/generators/blocks.rs | 203 +++++++++++++++++++++++- 1 file changed, 202 insertions(+), 1 deletion(-) diff --git a/hegel-workload/src/generators/blocks.rs b/hegel-workload/src/generators/blocks.rs index 2c9afa1e..8b1da3bc 100644 --- a/hegel-workload/src/generators/blocks.rs +++ b/hegel-workload/src/generators/blocks.rs @@ -1 +1,202 @@ -// Implemented in Task 7 +use hegel::generators as gs; + +use crate::cbor::*; +use crate::generators::address::filecoin_address; + +/// Generate a `BlockMsg` as CBOR array(3): [Header, BlsMessages []CID, SecpkMessages []CID]. +#[hegel::composite] +pub fn block_msg(tc: hegel::TestCase) -> Vec { + let header = tc.draw(block_header()); + let bls_msgs = tc.draw(cid_list()); + let secpk_msgs = tc.draw(cid_list()); + cbor_array(&[&header, &bls_msgs, &secpk_msgs]) +} + +/// Generate a Filecoin block header as a 16-field CBOR array: +/// [Miner, Ticket, ElectionProof, BeaconEntries, WinPoStProof, Parents, +/// ParentWeight, Height, ParentStateRoot, ParentMessageReceipts, Messages, +/// BLSAggregate, Timestamp, BlockSig, ForkSignaling, ParentBaseFee] +#[hegel::composite] +fn block_header(tc: hegel::TestCase) -> Vec { + // Miner address + let miner_addr: Vec = tc.draw(filecoin_address()); + let miner = cbor_bytes(&miner_addr); + + // Ticket + let ticket = tc.draw(ticket_field()); + + // ElectionProof + let election_proof = tc.draw(election_proof_field()); + + // BeaconEntries: nil or empty array + let beacon_variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(1)); + let beacon_entries = match beacon_variant { + 0 => cbor_nil(), + _ => cbor_array(&[]), + }; + + // WinPoStProof: always empty array + let win_post_proof = cbor_array(&[]); + + // Parents + let parents = tc.draw(parent_cids()); + + // ParentWeight + let pw_val: u64 = tc.draw(gs::sampled_from(vec![0u64, 1, 100, 999_999_999, u64::MAX])); + let parent_weight = cbor_bytes(&big_int_bytes(pw_val)); + + // Height + let h_val: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, + 1, + 10, + 100, + 1000, + u64::MAX, + u64::MAX - 1, + ])); + let height = cbor_uint64(h_val); + + // ParentStateRoot, ParentMessageReceipts, Messages — random CIDs + let state_root_cid = random_cid(); + let parent_state_root = cbor_cid(&state_root_cid); + + let msg_receipts_cid = random_cid(); + let parent_msg_receipts = cbor_cid(&msg_receipts_cid); + + let messages_cid = random_cid(); + let messages = cbor_cid(&messages_cid); + + // BLSAggregate: nil or BLS type byte + let bls_agg_variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(1)); + let bls_aggregate = match bls_agg_variant { + 0 => cbor_nil(), + _ => cbor_bytes(&[0x02]), + }; + + // Timestamp: fixed + let timestamp = cbor_uint64(1_700_000_000); + + // BlockSig: nil or BLS sig with random 8 bytes + let sig_variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(1)); + let block_sig = match sig_variant { + 0 => cbor_nil(), + _ => { + let sig_payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(8).max_size(8)); + let mut sig_data = vec![0x02u8]; // BLS type + sig_data.extend(sig_payload); + cbor_bytes(&sig_data) + } + }; + + // ForkSignaling: 0 + let fork_signaling = cbor_uint64(0); + + // ParentBaseFee + let parent_base_fee = cbor_bytes(&big_int_bytes(100)); + + cbor_array(&[ + &miner, + &ticket, + &election_proof, + &beacon_entries, + &win_post_proof, + &parents, + &parent_weight, + &height, + &parent_state_root, + &parent_msg_receipts, + &messages, + &bls_aggregate, + ×tamp, + &block_sig, + &fork_signaling, + &parent_base_fee, + ]) +} + +/// Ticket field: nil, valid [VRFProof 32 bytes], or empty array. +#[hegel::composite] +fn ticket_field(tc: hegel::TestCase) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); + match variant { + 0 => cbor_nil(), + 1 => { + let vrf_proof: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); + let proof_bytes = cbor_bytes(&vrf_proof); + cbor_array(&[&proof_bytes]) + } + _ => cbor_array(&[]), + } +} + +/// ElectionProof field: nil or [WinCount i64, VRFProof 32 bytes]. +#[hegel::composite] +fn election_proof_field(tc: hegel::TestCase) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(1)); + match variant { + 0 => cbor_nil(), + _ => { + let win_count_val: i64 = + tc.draw(gs::sampled_from(vec![0i64, 1, -1, 5, 100, i64::MAX, i64::MIN + 1])); + let win_count = cbor_int64(win_count_val); + let vrf_proof: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); + let proof_bytes = cbor_bytes(&vrf_proof); + cbor_array(&[&win_count, &proof_bytes]) + } + } +} + +/// Parent CIDs: nil, empty, single CID, or two CIDs. +#[hegel::composite] +fn parent_cids(tc: hegel::TestCase) -> Vec { + let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(3)); + match variant { + 0 => cbor_nil(), + 1 => cbor_array(&[]), + 2 => { + let cid = random_cid(); + let c = cbor_cid(&cid); + cbor_array(&[&c]) + } + _ => { + let cid1 = random_cid(); + let c1 = cbor_cid(&cid1); + let cid2 = random_cid(); + let c2 = cbor_cid(&cid2); + cbor_array(&[&c1, &c2]) + } + } +} + +/// CID list: 0-5 random CIDs for BlsMessages/SecpkMessages. +#[hegel::composite] +fn cid_list(tc: hegel::TestCase) -> Vec { + let count: usize = tc.draw(gs::integers::().min_value(0).max_value(5)); + let cids: Vec> = (0..count).map(|_| { + let raw = random_cid(); + cbor_cid(&raw) + }).collect(); + let refs: Vec<&[u8]> = cids.iter().map(|c| c.as_slice()).collect(); + cbor_array(&refs) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[hegel::test(test_cases = 50)] + fn test_block_msg_is_cbor_array_3(tc: hegel::TestCase) { + let block_bytes: Vec = tc.draw(block_msg()); + assert_eq!(block_bytes[0], 0x83, "BlockMsg must be CBOR array(3)"); + } + + #[hegel::test(test_cases = 50)] + fn test_block_msg_nonempty(tc: hegel::TestCase) { + let block_bytes: Vec = tc.draw(block_msg()); + assert!(block_bytes.len() > 30, "block too short: {} bytes", block_bytes.len()); + } +} From d009e879f991d422a6391e22a22157880b09f51e Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:10:45 -0400 Subject: [PATCH 08/47] feat(hegel-workload): add properties module for assertion logging --- hegel-workload/src/main.rs | 1 + hegel-workload/src/properties.rs | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 hegel-workload/src/properties.rs diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index c122682e..1ab255dd 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -2,6 +2,7 @@ mod cbor; mod discovery; mod generators; mod network; +mod properties; use log::info; diff --git a/hegel-workload/src/properties.rs b/hegel-workload/src/properties.rs new file mode 100644 index 00000000..95fee1e0 --- /dev/null +++ b/hegel-workload/src/properties.rs @@ -0,0 +1,7 @@ +use log::info; + +/// Log a generation event. In Antithesis mode, the hegeltest crate automatically +/// emits assertions to sdk.jsonl — this function provides additional logging. +pub fn log_generation(topic: &str, data_len: usize) { + info!("generated {} bytes for topic {}", data_len, topic); +} From 2fe71dd10e4ffcc16933d69cf0d3a3364bfb0622 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:13:26 -0400 Subject: [PATCH 09/47] feat(hegel-workload): implement main loop with Hegel generation and GossipSub publishing --- hegel-workload/src/main.rs | 112 ++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index 1ab255dd..9abb62c9 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -4,9 +4,119 @@ mod generators; mod network; mod properties; -use log::info; +use discovery::discover_nodes; +use generators::blocks::block_msg; +use generators::messages::signed_message; +use network::{build_swarm, run_network, PublishRequest}; +use properties::log_generation; + +use hegel::generators as gs; +use log::{error, info, warn}; +use tokio::sync::mpsc; fn main() { env_logger::init(); info!("hegel-workload starting"); + + // Parse configuration from environment + let stress_nodes = std::env::var("STRESS_NODES").unwrap_or_else(|_| "lotus0".to_string()); + let node_names: Vec = stress_nodes + .split(',') + .map(|s| s.trim().to_string()) + .collect(); + let devgen_dir = std::env::var("DEVGEN_DIR").unwrap_or_else(|_| "/root/devgen".to_string()); + let network_name = read_network_name(&devgen_dir); + let batch_size: u64 = std::env::var("HEGEL_BATCH_SIZE") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(100); + + info!( + "config: nodes={:?}, network={}, batch_size={}", + node_names, network_name, batch_size + ); + + // Discover peers + let nodes = discover_nodes(&node_names, &devgen_dir); + if nodes.is_empty() { + error!("no nodes discovered, exiting"); + std::process::exit(1); + } + info!("discovered {} nodes", nodes.len()); + + // Build topics + let msgs_topic = format!("/fil/msgs/{}", network_name); + let blocks_topic = format!("/fil/blocks/{}", network_name); + let topics = vec![msgs_topic.clone(), blocks_topic.clone()]; + + // Build swarm and channel + let swarm = build_swarm().expect("failed to build libp2p swarm"); + let (tx, rx) = mpsc::channel::(256); + + // Prepare peer info for the network task + let peers: Vec<_> = nodes.iter().map(|n| (n.addr.clone(), n.peer_id)).collect(); + + // Spawn tokio runtime for network + let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime"); + + // Spawn network task + rt.spawn(run_network(swarm, peers, topics, rx)); + + // Wait for mesh formation before generating + std::thread::sleep(std::time::Duration::from_secs(8)); + info!("starting Hegel generation loop"); + + // Main Hegel loop + loop { + let tx_ref = &tx; + let msgs_topic_ref = &msgs_topic; + let blocks_topic_ref = &blocks_topic; + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + hegel::Hegel::new(|tc| { + let use_blocks: bool = tc.draw(gs::booleans()); + + if use_blocks { + let data: Vec = tc.draw(block_msg()); + log_generation(blocks_topic_ref, data.len()); + let _ = tx_ref.blocking_send(PublishRequest { + topic: blocks_topic_ref.to_string(), + data, + }); + } else { + let data: Vec = tc.draw(signed_message()); + log_generation(msgs_topic_ref, data.len()); + let _ = tx_ref.blocking_send(PublishRequest { + topic: msgs_topic_ref.to_string(), + data, + }); + } + }) + .settings(hegel::Settings::new().test_cases(batch_size)) + .run(); + })); + + if let Err(e) = result { + warn!("Hegel batch failure (expected in fuzzing): {:?}", e); + } + } +} + +/// Read the network name from lotus0's devgen directory. +fn read_network_name(devgen_dir: &str) -> String { + let path = format!("{}/lotus0/network_name", devgen_dir); + for _ in 0..60 { + if let Ok(name) = std::fs::read_to_string(&path) { + let name = name.trim().to_string(); + if !name.is_empty() { + return name; + } + } + std::thread::sleep(std::time::Duration::from_secs(5)); + } + warn!( + "could not read network name from {}, defaulting to '2k'", + path + ); + "2k".to_string() } From bb4b86aad0fa4b9e8eef70f88848d8c80971850d Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:15:20 -0400 Subject: [PATCH 10/47] feat(hegel-workload): add Dockerfile, entrypoint, and docker-compose/Makefile integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Multi-stage Dockerfile: Rust builder → Ubuntu runtime with Python+uv - Bash entrypoint waits for node multiaddr files before launching binary - docker-compose.yaml: hegel-workload service with devgen volume mounts - Makefile: build-hegel-workload target, added to build-all --- Makefile | 8 +- docker-compose.yaml | 16 + hegel-workload/Cargo.lock | 3863 ++++++++++++++++++++++++++++++++++ hegel-workload/Dockerfile | 28 + hegel-workload/entrypoint.sh | 56 + 5 files changed, 3970 insertions(+), 1 deletion(-) create mode 100644 hegel-workload/Cargo.lock create mode 100644 hegel-workload/Dockerfile create mode 100755 hegel-workload/entrypoint.sh diff --git a/Makefile b/Makefile index cdddcef7..debdf1a5 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,11 @@ build-workload: @echo "Building workload for $(TARGET_ARCH)..." $(BUILD_CMD) -t workload:latest -f workload/Dockerfile workload +.PHONY: build-hegel-workload +build-hegel-workload: + @echo "Building hegel-workload for $(TARGET_ARCH)..." + $(BUILD_CMD) -t hegel-workload:latest -f hegel-workload/Dockerfile hegel-workload + .PHONY: build-filwizard build-filwizard: @echo "Building filwizard for $(TARGET_ARCH)..." @@ -137,7 +142,7 @@ build-nodes: build-lotus build-forest build-curio @echo "Node images built." .PHONY: build-all -build-all: build-drand build-lotus build-forest build-curio build-workload build-filwizard +build-all: build-drand build-lotus build-forest build-curio build-workload build-filwizard build-hegel-workload @echo "All images built." # ========================================== @@ -174,6 +179,7 @@ help: @echo " make build-curio Build curio image" @echo " make build-filwizard Build filwizard image" @echo " make build-workload Build workload image" + @echo " make build-hegel-workload Build hegel-workload image (Rust/Hegel)" @echo "" @echo "Build groups:" @echo " make build-infra Build infrastructure (drand)" diff --git a/docker-compose.yaml b/docker-compose.yaml index 99dc8a18..b2e57f10 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -225,6 +225,22 @@ services: - ./data/forest0:/root/devgen/forest0 - ./data/curio:/root/devgen/curio + hegel-workload: + <<: [ *filecoin_service ] + image: hegel-workload:latest + container_name: hegel-workload + environment: + - STRESS_NODES=lotus0,lotus1,lotus2,lotus3,forest0 + - ANTITHESIS_OUTPUT_DIR=/tmp/antithesis + - HEGEL_BATCH_SIZE=100 + - RUST_LOG=info + volumes: + - ./data/lotus0:/root/devgen/lotus0 + - ./data/lotus1:/root/devgen/lotus1 + - ./data/lotus2:/root/devgen/lotus2 + - ./data/lotus3:/root/devgen/lotus3 + - ./data/forest0:/root/devgen/forest0 + lotus-miner0: <<: [ *filecoin_service, *needs_lotus0_healthy ] image: lotus:${LOTUS_MINER_0_TAG:-${LOTUS_TAG:-latest}} diff --git a/hegel-workload/Cargo.lock b/hegel-workload/Cargo.lock new file mode 100644 index 00000000..c2110d70 --- /dev/null +++ b/hegel-workload/Cargo.lock @@ -0,0 +1,3863 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attohttpc" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" +dependencies = [ + "http 0.2.12", + "log", + "url", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "data-encoding-macro" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +dependencies = [ + "data-encoding", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls", + "rustls-pki-types", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hegel-workload" +version = "0.1.0" +dependencies = [ + "env_logger", + "futures", + "hegeltest", + "libp2p", + "log", + "sha2", + "tokio", +] + +[[package]] +name = "hegeltest" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ed973cb20e511f82953288d815cb08983695a26519c3e9f59941d163bf9d19" +dependencies = [ + "ciborium", + "crc32fast", + "hegeltest-macros", + "paste", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "hegeltest-macros" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f02fefc14c7e6bf4999b549649e463276b3cf342cdfc1e25935f14fa48c2164" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hickory-proto" +version = "0.25.0-alpha.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d00147af6310f4392a31680db52a3ed45a2e0f68eb18e8c3fe5537ecc96d9e2" +dependencies = [ + "async-recursion", + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.2", + "socket2 0.5.10", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.0-alpha.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5762f69ebdbd4ddb2e975cd24690bf21fe6b2604039189c26acddbc427f12887" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.2", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http 1.4.0", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2 0.6.3", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if-addrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "if-watch" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c02a5161c313f0cbdbadc511611893584a10a7b6153cb554bdf83ddce99ec2" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration", + "tokio", + "windows", +] + +[[package]] +name = "igd-next" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b0d7d4541def58a37bf8efc559683f21edce7c82f0d866c93ac21f7e098f93" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http 1.4.0", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.8.5", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2 0.6.3", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libp2p" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72dc443ddd0254cb49a794ed6b6728400ee446a0f7ab4a07d0209ee98de20e9" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom 0.2.17", + "libp2p-allow-block-list", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dns", + "libp2p-gossipsub", + "libp2p-identity", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-quic", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-upnp", + "libp2p-yamux", + "multiaddr", + "pin-project", + "rw-stream-sink", + "thiserror 2.0.18", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38944b7cb981cc93f2f0fb411ff82d0e983bd226fbcc8d559639a3a73236568b" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efe9323175a17caa8a2ed4feaf8a548eeef5e0b72d03840a0eab4bcb0210ce1c" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-core" +version = "0.43.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "thiserror 2.0.18", + "tracing", + "unsigned-varint 0.8.0", + "web-time", +] + +[[package]] +name = "libp2p-dns" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b780a1150214155b0ed1cdf09fbd2e1b0442604f9146a431d1b21d23eef7bd7" +dependencies = [ + "async-trait", + "futures", + "hickory-resolver", + "libp2p-core", + "libp2p-identity", + "parking_lot", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d558548fa3b5a8e9b66392f785921e363c57c05dcadfda4db0d41ae82d313e4a" +dependencies = [ + "async-channel", + "asynchronous-codec", + "base64", + "byteorder", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "getrandom 0.2.17", + "hashlink", + "hex_fmt", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "prometheus-client", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "regex", + "sha2", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-identity" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" +dependencies = [ + "bs58", + "ed25519-dalek", + "hkdf", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "sha2", + "thiserror 2.0.18", + "tracing", + "zeroize", +] + +[[package]] +name = "libp2p-mdns" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d0ba095e1175d797540e16b62e7576846b883cb5046d4159086837b36846cc" +dependencies = [ + "futures", + "hickory-proto", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-metrics" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce58c64292e87af624fcb86465e7dd8342e46a388d71e8fec0ab37ee789630a" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-gossipsub", + "libp2p-identity", + "libp2p-swarm", + "pin-project", + "prometheus-client", + "web-time", +] + +[[package]] +name = "libp2p-noise" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc73eacbe6462a0eb92a6527cac6e63f02026e5407f8831bde8293f19217bfbf" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "libp2p-identity", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "snow", + "static_assertions", + "thiserror 2.0.18", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-quic" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41432a159b00424a0abaa2c80d786cddff81055ac24aa127e0cf375f7858d880" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "quinn", + "rand 0.8.5", + "ring", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-swarm" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803399b4b6f68adb85e63ab573ac568154b193e9a640f03e0f2890eabbcb37f8" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "lru", + "multistream-select", + "once_cell", + "rand 0.8.5", + "smallvec", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libp2p-tcp" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65346fb4d36035b23fec4e7be4c320436ba53537ce9b6be1d1db1f70c905cad0" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-tls" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring", + "rustls", + "rustls-webpki", + "thiserror 2.0.18", + "x509-parser", + "yasna", +] + +[[package]] +name = "libp2p-upnp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d457b9ecceb66e7199f049926fad447f1f17f040e8d29d690c086b4cab8ed14a" +dependencies = [ + "futures", + "futures-timer", + "igd-next", + "libp2p-core", + "libp2p-swarm", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-yamux" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8" +dependencies = [ + "either", + "futures", + "libp2p-core", + "thiserror 2.0.18", + "tracing", + "yamux 0.12.1", + "yamux 0.13.10", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint 0.8.0", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "netlink-packet-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" +dependencies = [ + "paste", +] + +[[package]] +name = "netlink-packet-route" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" +dependencies = [ + "bitflags", + "libc", + "log", + "netlink-packet-core", +] + +[[package]] +name = "netlink-proto" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.18", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus-client" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" +dependencies = [ + "dtoa", + "itoa", + "parking_lot", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror 1.0.69", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rtnetlink" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b960d5d873a75b5be9761b1e73b146f52dddcd27bac75263f40fba686d4d7b5" +dependencies = [ + "futures-channel", + "futures-util", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek", + "rand_core 0.6.4", + "ring", + "rustc_version", + "sha2", + "subtle", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc0882f7b5bb01ae8c5215a1230832694481c1a4be062fd410e12ea3da5b631" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75973d3066e01d035dbedaad2864c398df42f8dd7b1ea057c35b8407c015b537" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91af5e4be765819e0bcfee7322c14374dc821e35e72fa663a830bbc7dc199eac" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9bf0406a78f02f336bf1e451799cca198e8acde4ffa278f0fb20487b150a633" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + +[[package]] +name = "yamux" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "yamux" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1991f6690292030e31b0144d73f5e8368936c58e45e7068254f7138b23b00672" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.9.2", + "static_assertions", + "web-time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/hegel-workload/Dockerfile b/hegel-workload/Dockerfile new file mode 100644 index 00000000..aa30f77b --- /dev/null +++ b/hegel-workload/Dockerfile @@ -0,0 +1,28 @@ +# Stage 1: Build the Rust binary +FROM rust:1.82 AS builder +WORKDIR /build +COPY Cargo.toml Cargo.lock ./ +COPY src/ ./src/ +RUN cargo build --release --features antithesis + +# Stage 2: Runtime with Python 3 + uv (required by Hegel's hegel-core Python backend) +FROM ubuntu:24.04 +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 \ + python3-venv \ + curl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install uv (Python package manager used by Hegel to auto-install hegel-core) +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH="/root/.local/bin:${PATH}" + +# Copy the built binary +COPY --from=builder /build/target/release/hegel-workload /usr/local/bin/hegel-workload + +# Copy entrypoint +COPY entrypoint.sh /opt/antithesis/entrypoint.sh +RUN chmod +x /opt/antithesis/entrypoint.sh + +ENTRYPOINT ["/opt/antithesis/entrypoint.sh"] diff --git a/hegel-workload/entrypoint.sh b/hegel-workload/entrypoint.sh new file mode 100755 index 00000000..6455b642 --- /dev/null +++ b/hegel-workload/entrypoint.sh @@ -0,0 +1,56 @@ +#!/bin/bash +set -euo pipefail + +echo "[hegel-workload] starting entrypoint" + +# Default devgen directory +DEVGEN_DIR="${DEVGEN_DIR:-/root/devgen}" + +# Parse STRESS_NODES into an array +IFS=',' read -ra NODES <<< "${STRESS_NODES:-lotus0}" + +# Wait for at least one node's multiaddr file to exist +echo "[hegel-workload] waiting for node multiaddr files..." +MAX_WAIT=300 +WAITED=0 +FOUND=false +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + for node in "${NODES[@]}"; do + node=$(echo "$node" | tr -d ' ') + ADDR_FILE="${DEVGEN_DIR}/${node}/${node}-ipv4addr" + if [ -f "$ADDR_FILE" ] && [ -s "$ADDR_FILE" ]; then + echo "[hegel-workload] found multiaddr for ${node}" + FOUND=true + break + fi + done + if [ "$FOUND" = true ]; then + break + fi + sleep 5 + WAITED=$((WAITED + 5)) +done + +if [ "$FOUND" = false ]; then + echo "[hegel-workload] ERROR: no multiaddr files found after ${MAX_WAIT}s" + exit 1 +fi + +# Wait for network_name file +NETWORK_FILE="${DEVGEN_DIR}/lotus0/network_name" +echo "[hegel-workload] waiting for network name at ${NETWORK_FILE}..." +WAITED=0 +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + if [ -f "$NETWORK_FILE" ] && [ -s "$NETWORK_FILE" ]; then + echo "[hegel-workload] network name: $(cat "$NETWORK_FILE")" + break + fi + sleep 5 + WAITED=$((WAITED + 5)) +done + +# Give nodes a few more seconds to finish connecting to each other +sleep 10 + +echo "[hegel-workload] launching hegel-workload binary" +exec /usr/local/bin/hegel-workload From d6d75dae0043fbf5ac604e8f3c43d2d56f5d3f07 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 13:19:53 -0400 Subject: [PATCH 11/47] fix(hegel-workload): address code review findings - Replace fragile rand_byte() with getrandom::getrandom() for proper Antithesis-interceptable randomness in CID generation - Fix mesh_wait busy-poll: await mesh formation before entering main event loop instead of re-polling a completed Sleep future - Add depends_on lotus0 healthy to docker-compose service --- docker-compose.yaml | 2 +- hegel-workload/Cargo.lock | 1 + hegel-workload/Cargo.toml | 1 + hegel-workload/src/cbor.rs | 15 +++++++-------- hegel-workload/src/network.rs | 17 ++++++++++++++--- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index b2e57f10..f7b2d3ce 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -226,7 +226,7 @@ services: - ./data/curio:/root/devgen/curio hegel-workload: - <<: [ *filecoin_service ] + <<: [ *filecoin_service, *needs_lotus0_healthy ] image: hegel-workload:latest container_name: hegel-workload environment: diff --git a/hegel-workload/Cargo.lock b/hegel-workload/Cargo.lock index c2110d70..89956997 100644 --- a/hegel-workload/Cargo.lock +++ b/hegel-workload/Cargo.lock @@ -1043,6 +1043,7 @@ version = "0.1.0" dependencies = [ "env_logger", "futures", + "getrandom 0.2.17", "hegeltest", "libp2p", "log", diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml index da27cdfe..221889a0 100644 --- a/hegel-workload/Cargo.toml +++ b/hegel-workload/Cargo.toml @@ -13,4 +13,5 @@ tokio = { version = "1", features = ["full"] } log = "0.4" env_logger = "0.11" sha2 = "0.10" +getrandom = "0.2" futures = "0.3" diff --git a/hegel-workload/src/cbor.rs b/hegel-workload/src/cbor.rs index 62678cb0..783dc892 100644 --- a/hegel-workload/src/cbor.rs +++ b/hegel-workload/src/cbor.rs @@ -93,7 +93,7 @@ pub fn big_int_bytes(v: u64) -> Vec { pub fn random_cid() -> Vec { use sha2::{Digest, Sha256}; - let data: Vec = (0..32).map(|_| rand_byte()).collect(); + let data = random_bytes(32); let hash = Sha256::digest(&data); let mut cid = Vec::new(); cid.push(0x01); // CIDv1 @@ -104,13 +104,12 @@ pub fn random_cid() -> Vec { cid } -fn rand_byte() -> u8 { - use std::collections::hash_map::RandomState; - use std::hash::{BuildHasher, Hasher}; - let s = RandomState::new(); - let mut h = s.build_hasher(); - h.write_u8(0); - (h.finish() & 0xFF) as u8 +/// Generate random bytes using getrandom. Inside Antithesis, getrandom is +/// intercepted so this is deterministic per-run. +pub fn random_bytes(len: usize) -> Vec { + let mut buf = vec![0u8; len]; + getrandom::getrandom(&mut buf).expect("getrandom failed"); + buf } #[cfg(test)] diff --git a/hegel-workload/src/network.rs b/hegel-workload/src/network.rs index a42a2c86..6489d578 100644 --- a/hegel-workload/src/network.rs +++ b/hegel-workload/src/network.rs @@ -87,13 +87,24 @@ pub async fn run_network( } } + // Drive swarm events during mesh formation wait info!("waiting 5s for GossipSub mesh formation..."); - let mesh_wait = tokio::time::sleep(Duration::from_secs(5)); - tokio::pin!(mesh_wait); + let mesh_deadline = tokio::time::Instant::now() + Duration::from_secs(5); + while tokio::time::Instant::now() < mesh_deadline { + tokio::select! { + _ = tokio::time::sleep_until(mesh_deadline) => { break; } + event = swarm.select_next_some() => { + if let SwarmEvent::ConnectionEstablished { peer_id, .. } = event { + info!("connected to {} during mesh formation", peer_id); + } + } + } + } + info!("mesh formation complete, entering event loop"); + // Main event loop loop { tokio::select! { - _ = &mut mesh_wait => {} event = swarm.select_next_some() => { match event { SwarmEvent::ConnectionEstablished { peer_id, .. } => { From 724ab22072258eb8e44e5ec3b354a85aebf2c188 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 14:46:07 -0400 Subject: [PATCH 12/47] Update gitignore --- hegel-workload/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 hegel-workload/.gitignore diff --git a/hegel-workload/.gitignore b/hegel-workload/.gitignore new file mode 100644 index 00000000..dda6af7b --- /dev/null +++ b/hegel-workload/.gitignore @@ -0,0 +1,2 @@ +.hegel/ +target/ \ No newline at end of file From 7d3662201a4316c98cba02903227c96f21a06256 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 15:49:16 -0400 Subject: [PATCH 13/47] Pin git tags for curio/drand/lotus/forest --- Makefile | 46 +++++++++++++++++++++++++++++++++++++++------- curio/Dockerfile | 3 ++- drand/Dockerfile | 2 +- lotus/Dockerfile | 5 +++-- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index debdf1a5..4ffe9f97 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,12 @@ # ========================================== -include versions.env -# Git tags/commits for each image -drand_tag = $(shell git ls-remote --tags https://github.com/drand/drand.git | grep -E 'refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$$' | tail -n1 | sed 's/.*refs\/tags\///') -lotus_tag = $(shell git ls-remote https://github.com/filecoin-project/lotus.git HEAD | cut -f1) -curio_tag = $(shell git ls-remote https://github.com/filecoin-project/curio.git refs/heads/pdpv0 | cut -f1) -forest_commit = $(shell git ls-remote https://github.com/ChainSafe/forest.git HEAD | cut -f1) +# Pinned versions — read from each Dockerfile so builds are reproducible. +# To update: change the pinned value in the Dockerfile, then run `make check-versions`. +drand_tag = $(shell grep -oP 'ARG GIT_BRANCH="\K[^"]+' drand/Dockerfile) +lotus_tag = $(shell grep -oP 'ARG REF="\K[^"]+' lotus/Dockerfile) +curio_tag = $(shell grep -oP 'ARG GIT_COMMIT="\K[^"]+' curio/Dockerfile) +forest_commit = $(shell grep -oP 'ARG GIT_COMMIT="\K[^"]+' forest/Dockerfile) # Docker configuration builder = docker @@ -52,6 +53,36 @@ show-arch: @echo "Current target architecture: $(TARGET_ARCH)" @echo "Docker platform: $(DOCKER_PLATFORM)" +# ========================================== +# Version drift check +# ========================================== +.PHONY: check-versions +check-versions: + @echo "Checking pinned versions against upstream..." + @UP_DRAND=$$(git ls-remote --tags https://github.com/drand/drand.git \ + | grep -E 'refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$$' | tail -n1 | sed 's/.*refs\/tags\///'); \ + UP_LOTUS=$$(git ls-remote https://github.com/filecoin-project/lotus.git HEAD | cut -f1); \ + UP_CURIO=$$(git ls-remote https://github.com/filecoin-project/curio.git refs/heads/pdpv0 | cut -f1); \ + UP_FOREST=$$(git ls-remote https://github.com/ChainSafe/forest.git HEAD | cut -f1); \ + WARN=0; \ + if [ "$$UP_DRAND" != "$(drand_tag)" ]; then \ + echo " WARNING: drand pinned=$(drand_tag) upstream=$$UP_DRAND"; WARN=1; \ + else echo " drand: up to date ($(drand_tag))"; fi; \ + if [ "$$UP_LOTUS" != "$(lotus_tag)" ]; then \ + echo " WARNING: lotus pinned=$(lotus_tag) upstream=$$UP_LOTUS"; WARN=1; \ + else echo " lotus: up to date ($(lotus_tag))"; fi; \ + if [ "$$UP_CURIO" != "$(curio_tag)" ]; then \ + echo " WARNING: curio pinned=$(curio_tag) upstream=$$UP_CURIO"; WARN=1; \ + else echo " curio: up to date ($(curio_tag))"; fi; \ + if [ "$$UP_FOREST" != "$(forest_commit)" ]; then \ + echo " WARNING: forest pinned=$(forest_commit) upstream=$$UP_FOREST"; WARN=1; \ + else echo " forest: up to date ($(forest_commit))"; fi; \ + if [ "$$WARN" -eq 1 ]; then \ + echo ""; \ + echo "Some pinned versions are behind upstream."; \ + echo "To update: change the pinned value in the relevant Dockerfile, re-test patches, and commit."; \ + fi + # ========================================== # Build individual images # ========================================== @@ -65,7 +96,7 @@ build-drand: build-lotus: @echo "Building lotus for $(TARGET_ARCH)..." @echo " Git commit: $(lotus_tag)" - $(BUILD_CMD) --build-arg=GIT_BRANCH=$(lotus_tag) -t lotus:latest -f lotus/Dockerfile lotus + $(BUILD_CMD) --build-arg=REF=$(lotus_tag) -t lotus:latest -f lotus/Dockerfile lotus .PHONY: build-forest build-forest: @@ -78,7 +109,7 @@ build-curio: @echo "Building curio for $(TARGET_ARCH)..." @echo " Git commit: $(curio_tag)" @echo " Build mode: $(BUILD_MODE)" - $(BUILD_CMD) --build-arg=GIT_BRANCH=$(curio_tag) --build-arg=LOTUS_TAG=$(lotus_tag) --build-arg=BUILD_MODE=$(BUILD_MODE) -t curio:latest -f curio/Dockerfile curio + $(BUILD_CMD) --build-arg=GIT_COMMIT=$(curio_tag) --build-arg=LOTUS_TAG=$(lotus_tag) --build-arg=BUILD_MODE=$(BUILD_MODE) -t curio:latest -f curio/Dockerfile curio .PHONY: build-workload build-workload: @@ -201,6 +232,7 @@ help: @echo "" @echo "Info:" @echo " make show-versions Show all image version tags" + @echo " make check-versions Check pinned versions against upstream" @echo " make show-arch Show target architecture" @echo "" @echo "Current arch: $(TARGET_ARCH)" \ No newline at end of file diff --git a/curio/Dockerfile b/curio/Dockerfile index bba3d5a9..83e43bec 100644 --- a/curio/Dockerfile +++ b/curio/Dockerfile @@ -47,7 +47,8 @@ RUN set -eux; \ RUN git clone https://github.com/filecoin-project/curio.git /opt/curio WORKDIR /opt/curio -RUN git checkout pdpv0 +ARG GIT_COMMIT="2bfe87dfe72d6face2c5e11d4b714bc636778ff7" +RUN git checkout ${GIT_COMMIT} RUN git config --global user.email "richard.yao@antithesis.com" RUN git config --global user.name "Richard" COPY ./increase-cpu-avail-antithesis.patch ./increase-cpu-avail-antithesis.patch diff --git a/drand/Dockerfile b/drand/Dockerfile index d590f575..7f4a0a73 100644 --- a/drand/Dockerfile +++ b/drand/Dockerfile @@ -3,7 +3,7 @@ FROM docker.io/golang:1.25.1 RUN apt-get update && \ apt-get install -y ca-certificates build-essential clang ocl-icd-opencl-dev ocl-icd-libopencl1 jq libhwloc-dev git -ARG GIT_BRANCH="v2.1.3" +ARG GIT_BRANCH="v2.1.5" RUN git clone --depth=1 --branch="${GIT_BRANCH}" https://github.com/drand/drand /drand diff --git a/lotus/Dockerfile b/lotus/Dockerfile index 71dc6bd2..6af141f6 100644 --- a/lotus/Dockerfile +++ b/lotus/Dockerfile @@ -6,9 +6,10 @@ FROM golang:$GO_VERSION RUN apt-get update && apt-get install -y jq libhwloc-dev ocl-icd-opencl-dev && rm -rf /var/lib/apt/lists/* # cloning lotus -ARG REF="master" -RUN git clone --depth=1 https://github.com/filecoin-project/lotus.git --branch=${REF} /lotus +ARG REF="006da4c7e1c1c29ac02b32112c0d205e4085ba35" +RUN git clone https://github.com/filecoin-project/lotus.git /lotus WORKDIR /lotus +RUN git checkout ${REF} # copy & apply lotus patch (local drand; setting bootstrap and finality epoch) COPY patches/lotus.patch lotus.patch From 666f0187a1ba605dd4f86df5802fade65fcefcbf Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 16:42:32 -0400 Subject: [PATCH 14/47] Disable instrumentation for LOCAL_BUILD=1 --- forest/Dockerfile | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/forest/Dockerfile b/forest/Dockerfile index 682ad78a..cbea62f5 100644 --- a/forest/Dockerfile +++ b/forest/Dockerfile @@ -37,27 +37,35 @@ WORKDIR /forest COPY ./forest_config.toml.tpl . COPY libvoidstar.so /usr/local/lib/libvoidstar.so ENV LD_LIBRARY_PATH="/usr/local/lib:${LD_LIBRARY_PATH}" -# Build forest binaries + Rust instrumentation on main forest binary for code coverage +# Build forest binaries + optional Rust instrumentation on main forest binary for code coverage +# LOCAL_BUILD=1 (default): skip instrumentation for fast local builds +# LOCAL_BUILD=0 (CI): enable Antithesis instrumentation with libvoidstar RUN cargo build --release --bin forest-cli && \ cargo build --release --bin forest-tool && \ cargo build --release --bin forest-wallet && \ cp /forest/target/release/forest-cli /usr/local/cargo/bin/forest-cli && \ cp /forest/target/release/forest-tool /usr/local/cargo/bin/forest-tool && \ cp /forest/target/release/forest-wallet /usr/local/cargo/bin/forest-wallet && \ - # Setup Antithesis instrumentation - export LD_LIBRARY_PATH=/usr/local/lib && \ - export RUSTFLAGS="-Ccodegen-units=1 -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 \ - -Cllvm-args=-sanitizer-coverage-trace-pc-guard -Clink-arg=-Wl,-z,undefs \ - -Clink-args=-Wl,--build-id -L/usr/local/lib -lvoidstar" && \ - cd /forest && \ - sed -i 's/strip = true/strip = false/' Cargo.toml && \ - cargo build --release --bin forest && \ - cp /forest/target/release/forest /usr/local/cargo/bin/forest && \ - cd / && \ - mkdir -p /symbols && \ - ln -s /usr/local/cargo/bin/forest /symbols/forest && \ - ldd /symbols/forest | grep "libvoidstar" && \ - nm /symbols/forest | grep "sanitizer_cov_trace_pc_guard"; + if [ "$LOCAL_BUILD" = "0" ]; then \ + echo "Building forest with Antithesis instrumentation..." && \ + export LD_LIBRARY_PATH=/usr/local/lib && \ + export RUSTFLAGS="-Ccodegen-units=1 -Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 \ + -Cllvm-args=-sanitizer-coverage-trace-pc-guard -Clink-arg=-Wl,-z,undefs \ + -Clink-args=-Wl,--build-id -L/usr/local/lib -lvoidstar" && \ + sed -i 's/strip = true/strip = false/' Cargo.toml && \ + cargo build --release --bin forest && \ + cp /forest/target/release/forest /usr/local/cargo/bin/forest && \ + mkdir -p /symbols && \ + ln -s /usr/local/cargo/bin/forest /symbols/forest && \ + ldd /symbols/forest | grep "libvoidstar" && \ + nm /symbols/forest | grep "sanitizer_cov_trace_pc_guard"; \ + else \ + echo "Building forest without instrumentation (local build)..." && \ + cargo build --release --bin forest && \ + cp /forest/target/release/forest /usr/local/cargo/bin/forest && \ + mkdir -p /symbols && \ + ln -s /usr/local/cargo/bin/forest /symbols/forest; \ + fi WORKDIR /forest From 403acaac0b560898335f2bd60e4595fd6bed8f65 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 17:24:04 -0400 Subject: [PATCH 15/47] Fix BUILD_MODE expansion --- Makefile | 6 +++--- curio/Dockerfile | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 4ffe9f97..a7ec6e03 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,8 @@ BUILD_CMD = docker build TARGET_ARCH ?= $(shell uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') DOCKER_PLATFORM = linux/$(TARGET_ARCH) -# Build mode: "local" uses local images, "remote" uses Antithesis registry -BUILD_MODE ?= local +# Build mode: empty for local images, "remote" uses Antithesis registry +BUILD_MODE ?= # ========================================== # Show version info @@ -109,7 +109,7 @@ build-curio: @echo "Building curio for $(TARGET_ARCH)..." @echo " Git commit: $(curio_tag)" @echo " Build mode: $(BUILD_MODE)" - $(BUILD_CMD) --build-arg=GIT_COMMIT=$(curio_tag) --build-arg=LOTUS_TAG=$(lotus_tag) --build-arg=BUILD_MODE=$(BUILD_MODE) -t curio:latest -f curio/Dockerfile curio + $(BUILD_CMD) --build-arg=GIT_COMMIT=$(curio_tag) --build-arg=LOTUS_TAG=latest --build-arg=BUILD_MODE=$(BUILD_MODE) -t curio:latest -f curio/Dockerfile curio .PHONY: build-workload build-workload: diff --git a/curio/Dockerfile b/curio/Dockerfile index 83e43bec..858bbbeb 100644 --- a/curio/Dockerfile +++ b/curio/Dockerfile @@ -1,7 +1,7 @@ # STAGE 1: Use the pre-built lotus image as a source for its binaries # This avoids rebuilding lotus and uses the same version as the rest of your environment. -# BUILD_MODE: "local" uses local lotus image, "remote" uses Antithesis registry -ARG BUILD_MODE=local +# BUILD_MODE: empty (default) uses local lotus image, any non-empty value uses Antithesis registry +ARG BUILD_MODE= ARG LOTUS_TAG=latest ARG ANTITHESIS_REGISTRY=us-central1-docker.pkg.dev/molten-verve-216720/filecoin-repository FROM ${BUILD_MODE:+${ANTITHESIS_REGISTRY}/}lotus:${LOTUS_TAG} as lotus-builder From 394b4cd60fe243bdf7137afe0048b84334f23457 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 18:43:29 -0400 Subject: [PATCH 16/47] Update rust version for hegel-workload --- hegel-workload/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hegel-workload/Dockerfile b/hegel-workload/Dockerfile index aa30f77b..5c1794a2 100644 --- a/hegel-workload/Dockerfile +++ b/hegel-workload/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build the Rust binary -FROM rust:1.82 AS builder +FROM rust:1.86 AS builder WORKDIR /build COPY Cargo.toml Cargo.lock ./ COPY src/ ./src/ From acd59c26200bbbee3f429463eaca8cdfab047e53 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 20:07:00 -0400 Subject: [PATCH 17/47] Simplify default stress nodes for hegel --- docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index f7b2d3ce..2f5bbc10 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -230,7 +230,7 @@ services: image: hegel-workload:latest container_name: hegel-workload environment: - - STRESS_NODES=lotus0,lotus1,lotus2,lotus3,forest0 + - STRESS_NODES=${STRESS_NODES:-lotus0,lotus1} - ANTITHESIS_OUTPUT_DIR=/tmp/antithesis - HEGEL_BATCH_SIZE=100 - RUST_LOG=info From 870fe52273442b60730eaed2dc587017185cc61f Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 20:11:52 -0400 Subject: [PATCH 18/47] Backoff between spam messages --- hegel-workload/src/main.rs | 17 +++++++++-- hegel-workload/src/network.rs | 54 ++++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index 9abb62c9..fe985b66 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -12,6 +12,7 @@ use properties::log_generation; use hegel::generators as gs; use log::{error, info, warn}; +use std::time::Duration; use tokio::sync::mpsc; fn main() { @@ -30,6 +31,12 @@ fn main() { .ok() .and_then(|s| s.parse().ok()) .unwrap_or(100); + let publish_delay: Duration = Duration::from_millis( + std::env::var("PUBLISH_DELAY_MS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(500), + ); info!( "config: nodes={:?}, network={}, batch_size={}", @@ -52,6 +59,7 @@ fn main() { // Build swarm and channel let swarm = build_swarm().expect("failed to build libp2p swarm"); let (tx, rx) = mpsc::channel::(256); + let (ready_tx, ready_rx) = tokio::sync::oneshot::channel(); // Prepare peer info for the network task let peers: Vec<_> = nodes.iter().map(|n| (n.addr.clone(), n.peer_id)).collect(); @@ -60,10 +68,11 @@ fn main() { let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime"); // Spawn network task - rt.spawn(run_network(swarm, peers, topics, rx)); + rt.spawn(run_network(swarm, peers, topics, rx, ready_tx)); - // Wait for mesh formation before generating - std::thread::sleep(std::time::Duration::from_secs(8)); + // Wait for mesh to be ready before generating + info!("waiting for mesh to be ready..."); + let _ = rt.block_on(ready_rx); info!("starting Hegel generation loop"); // Main Hegel loop @@ -91,6 +100,8 @@ fn main() { data, }); } + + std::thread::sleep(publish_delay); }) .settings(hegel::Settings::new().test_cases(batch_size)) .run(); diff --git a/hegel-workload/src/network.rs b/hegel-workload/src/network.rs index 6489d578..e02b6e17 100644 --- a/hegel-workload/src/network.rs +++ b/hegel-workload/src/network.rs @@ -57,11 +57,15 @@ pub fn build_swarm() -> Result, Box, peers: Vec<(Multiaddr, PeerId)>, topics: Vec, mut rx: mpsc::Receiver, + ready_tx: tokio::sync::oneshot::Sender<()>, ) { // Listen on random port swarm @@ -78,21 +82,41 @@ pub async fn run_network( } // Subscribe to topics - for topic_str in &topics { - let topic = IdentTopic::new(topic_str); - if let Err(e) = swarm.behaviour_mut().subscribe(&topic) { - warn!("failed to subscribe to {}: {}", topic_str, e); - } else { - info!("subscribed to {}", topic_str); - } - } + let topic_hashes: Vec<_> = topics + .iter() + .map(|t| { + let topic = IdentTopic::new(t); + if let Err(e) = swarm.behaviour_mut().subscribe(&topic) { + warn!("failed to subscribe to {}: {}", t, e); + } else { + info!("subscribed to {}", t); + } + topic.hash() + }) + .collect(); - // Drive swarm events during mesh formation wait - info!("waiting 5s for GossipSub mesh formation..."); - let mesh_deadline = tokio::time::Instant::now() + Duration::from_secs(5); + // Wait for at least one peer in the mesh for any topic (up to 60s) + info!("waiting for GossipSub mesh peers..."); + let mesh_deadline = tokio::time::Instant::now() + Duration::from_secs(60); + let mut ready_tx = Some(ready_tx); while tokio::time::Instant::now() < mesh_deadline { + // Check if any topic has mesh peers + if ready_tx.is_some() { + for hash in &topic_hashes { + if !swarm.behaviour().mesh_peers(hash).collect::>().is_empty() { + info!("mesh formed — peers available, signalling ready"); + if let Some(tx) = ready_tx.take() { + let _ = tx.send(()); + } + break; + } + } + } + if ready_tx.is_none() { + break; + } tokio::select! { - _ = tokio::time::sleep_until(mesh_deadline) => { break; } + _ = tokio::time::sleep(Duration::from_millis(500)) => {} event = swarm.select_next_some() => { if let SwarmEvent::ConnectionEstablished { peer_id, .. } = event { info!("connected to {} during mesh formation", peer_id); @@ -100,7 +124,11 @@ pub async fn run_network( } } } - info!("mesh formation complete, entering event loop"); + if let Some(tx) = ready_tx.take() { + warn!("mesh formation timed out after 60s, proceeding anyway"); + let _ = tx.send(()); + } + info!("entering event loop"); // Main event loop loop { From 954aa1c07ec7753180598487b88fc50e103aa71c Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 20:22:53 -0400 Subject: [PATCH 19/47] Update start-lotus script to wait for addr file. Hegel register explicit publish peers --- hegel-workload/src/network.rs | 66 ++++++++++++++++------------------- lotus/scripts/start-lotus.sh | 20 +++++++++-- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/hegel-workload/src/network.rs b/hegel-workload/src/network.rs index e02b6e17..82795032 100644 --- a/hegel-workload/src/network.rs +++ b/hegel-workload/src/network.rs @@ -72,60 +72,54 @@ pub async fn run_network( .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .expect("failed to listen"); - // Dial all peers + // Register peers as explicit GossipSub peers and dial them. + // Explicit peers always receive published messages regardless of mesh state, + // which avoids InsufficientPeers errors when Go/Rust GossipSub protocol + // negotiation doesn't fully establish a mesh. for (addr, peer_id) in &peers { + swarm.behaviour_mut().add_explicit_peer(peer_id); let dial_addr = addr.clone().with(libp2p::multiaddr::Protocol::P2p(*peer_id)); match swarm.dial(dial_addr.clone()) { - Ok(_) => info!("dialing {}", dial_addr), + Ok(_) => info!("dialing {} (explicit peer)", dial_addr), Err(e) => warn!("failed to dial {}: {}", dial_addr, e), } } // Subscribe to topics - let topic_hashes: Vec<_> = topics - .iter() - .map(|t| { - let topic = IdentTopic::new(t); - if let Err(e) = swarm.behaviour_mut().subscribe(&topic) { - warn!("failed to subscribe to {}: {}", t, e); - } else { - info!("subscribed to {}", t); - } - topic.hash() - }) - .collect(); + for t in &topics { + let topic = IdentTopic::new(t); + if let Err(e) = swarm.behaviour_mut().subscribe(&topic) { + warn!("failed to subscribe to {}: {}", t, e); + } else { + info!("subscribed to {}", t); + } + } - // Wait for at least one peer in the mesh for any topic (up to 60s) - info!("waiting for GossipSub mesh peers..."); - let mesh_deadline = tokio::time::Instant::now() + Duration::from_secs(60); + // Wait for at least one connection to be established (up to 60s). + // With explicit peers, we don't need mesh formation — just a live connection. + info!("waiting for peer connections..."); + let deadline = tokio::time::Instant::now() + Duration::from_secs(60); + let mut connected = false; let mut ready_tx = Some(ready_tx); - while tokio::time::Instant::now() < mesh_deadline { - // Check if any topic has mesh peers - if ready_tx.is_some() { - for hash in &topic_hashes { - if !swarm.behaviour().mesh_peers(hash).collect::>().is_empty() { - info!("mesh formed — peers available, signalling ready"); - if let Some(tx) = ready_tx.take() { - let _ = tx.send(()); - } - break; - } - } - } - if ready_tx.is_none() { - break; - } + while tokio::time::Instant::now() < deadline && !connected { tokio::select! { - _ = tokio::time::sleep(Duration::from_millis(500)) => {} + _ = tokio::time::sleep_until(deadline) => { break; } event = swarm.select_next_some() => { if let SwarmEvent::ConnectionEstablished { peer_id, .. } = event { - info!("connected to {} during mesh formation", peer_id); + info!("connected to {} (explicit peer)", peer_id); + connected = true; } } } } + if connected { + // Brief pause for protocol negotiation after first connection + tokio::time::sleep(Duration::from_secs(2)).await; + info!("peer connected, signalling ready"); + } else { + warn!("no peer connections after 60s, proceeding anyway"); + } if let Some(tx) = ready_tx.take() { - warn!("mesh formation timed out after 60s, proceeding anyway"); let _ = tx.send(()); } info!("entering event loop"); diff --git a/lotus/scripts/start-lotus.sh b/lotus/scripts/start-lotus.sh index 17692749..580a0f07 100755 --- a/lotus/scripts/start-lotus.sh +++ b/lotus/scripts/start-lotus.sh @@ -124,6 +124,20 @@ fi connect_with_retries() { local max_retries=10 local addr_file="$1" + local node_name="$2" + local wait_retries=3 + + # Wait briefly for the addr file to appear; skip if the node isn't running + for (( j=1; j<=wait_retries; j++ )); do + if [ -f "$addr_file" ] && [ -s "$addr_file" ]; then + break + fi + if [ "$j" -eq "$wait_retries" ]; then + echo "SKIP: $node_name addr file not found at $addr_file (node probably not running)" + return 0 + fi + sleep 2 + done for (( j=1; j<=max_retries; j++ )); do echo "attempt $j/$max_retries..." @@ -137,7 +151,7 @@ connect_with_retries() { fi done - echo "ERROR: reached $max_retries attempts." + echo "ERROR: reached $max_retries attempts for $node_name." return 1 } @@ -152,7 +166,7 @@ for (( i=0; i<$NUM_LOTUS_CLIENTS; i++ )); do addr_file="${OTHER_LOTUS_DATA_DIR}/lotus${i}-ipv4addr" echo "Connecting to lotus$i at $addr_file" - connect_with_retries "$addr_file" + connect_with_retries "$addr_file" "lotus$i" done echo "connecting to forest nodes..." @@ -162,7 +176,7 @@ for (( i=0; i<$NUM_FOREST_CLIENTS; i++ )); do addr_file="${FOREST_DATA_DIR}/forest${i}-ipv4addr" echo "Connecting to forest$i at $addr_file" - connect_with_retries "$addr_file" + connect_with_retries "$addr_file" "forest$i" done touch "${SHARED_CONFIGS}/lotus-${node_number}-ready" From 9ce16ac1399e0b27b368b1eef4b30be435d0fbd8 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 20:52:07 -0400 Subject: [PATCH 20/47] Fix hegel-workload negotiation --- hegel-workload/Cargo.lock | 33 +++++++++++++++++++++++++++++++++ hegel-workload/Cargo.toml | 2 +- hegel-workload/entrypoint.sh | 3 +++ hegel-workload/src/network.rs | 34 ++++++++++++++++++++++++---------- 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/hegel-workload/Cargo.lock b/hegel-workload/Cargo.lock index 89956997..d1619ecd 100644 --- a/hegel-workload/Cargo.lock +++ b/hegel-workload/Cargo.lock @@ -806,6 +806,16 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-bounded" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" +dependencies = [ + "futures-timer", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1527,6 +1537,7 @@ dependencies = [ "libp2p-core", "libp2p-dns", "libp2p-gossipsub", + "libp2p-identify", "libp2p-identity", "libp2p-mdns", "libp2p-metrics", @@ -1636,6 +1647,27 @@ dependencies = [ "web-time", ] +[[package]] +name = "libp2p-identify" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c06862544f02d05d62780ff590cc25a75f5c2b9df38ec7a370dcae8bb873cf" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "libp2p-identity" version = "0.2.13" @@ -1682,6 +1714,7 @@ dependencies = [ "futures", "libp2p-core", "libp2p-gossipsub", + "libp2p-identify", "libp2p-identity", "libp2p-swarm", "pin-project", diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml index 221889a0..888b61af 100644 --- a/hegel-workload/Cargo.toml +++ b/hegel-workload/Cargo.toml @@ -8,7 +8,7 @@ antithesis = ["hegeltest/antithesis"] [dependencies] hegeltest = "0.3" -libp2p = { version = "0.55", features = ["gossipsub", "tcp", "noise", "yamux", "tokio", "macros"] } +libp2p = { version = "0.55", features = ["gossipsub", "identify", "tcp", "noise", "yamux", "tokio", "macros"] } tokio = { version = "1", features = ["full"] } log = "0.4" env_logger = "0.11" diff --git a/hegel-workload/entrypoint.sh b/hegel-workload/entrypoint.sh index 6455b642..5e49cf90 100755 --- a/hegel-workload/entrypoint.sh +++ b/hegel-workload/entrypoint.sh @@ -3,6 +3,9 @@ set -euo pipefail echo "[hegel-workload] starting entrypoint" +# Ensure Antithesis output directory exists (needed by hegeltest SDK even outside Antithesis) +mkdir -p "${ANTITHESIS_OUTPUT_DIR:-/tmp/antithesis}" + # Default devgen directory DEVGEN_DIR="${DEVGEN_DIR:-/root/devgen}" diff --git a/hegel-workload/src/network.rs b/hegel-workload/src/network.rs index 82795032..35a7cb9c 100644 --- a/hegel-workload/src/network.rs +++ b/hegel-workload/src/network.rs @@ -1,8 +1,9 @@ use libp2p::{ gossipsub::{self, IdentTopic, MessageAuthenticity, MessageId, ValidationMode}, + identify, identity, noise, - swarm::SwarmEvent, + swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Multiaddr, PeerId, Swarm, }; use log::{info, warn}; @@ -17,8 +18,16 @@ pub struct PublishRequest { pub data: Vec, } -/// Build a libp2p Swarm with GossipSub configured for Filecoin interop. -pub fn build_swarm() -> Result, Box> { +/// Combined behaviour: GossipSub for pubsub + Identify for protocol negotiation. +/// Lotus requires the identify protocol before accepting GossipSub traffic. +#[derive(NetworkBehaviour)] +pub struct Behaviour { + gossipsub: gossipsub::Behaviour, + identify: identify::Behaviour, +} + +/// Build a libp2p Swarm with GossipSub + Identify configured for Filecoin interop. +pub fn build_swarm() -> Result, Box> { let local_key = identity::Keypair::generate_ed25519(); // Content-hash message ID function — critical for Lotus interop. @@ -41,6 +50,11 @@ pub fn build_swarm() -> Result, Box Result, Box Result, Box, + mut swarm: Swarm, peers: Vec<(Multiaddr, PeerId)>, topics: Vec, mut rx: mpsc::Receiver, @@ -77,7 +91,7 @@ pub async fn run_network( // which avoids InsufficientPeers errors when Go/Rust GossipSub protocol // negotiation doesn't fully establish a mesh. for (addr, peer_id) in &peers { - swarm.behaviour_mut().add_explicit_peer(peer_id); + swarm.behaviour_mut().gossipsub.add_explicit_peer(peer_id); let dial_addr = addr.clone().with(libp2p::multiaddr::Protocol::P2p(*peer_id)); match swarm.dial(dial_addr.clone()) { Ok(_) => info!("dialing {} (explicit peer)", dial_addr), @@ -88,7 +102,7 @@ pub async fn run_network( // Subscribe to topics for t in &topics { let topic = IdentTopic::new(t); - if let Err(e) = swarm.behaviour_mut().subscribe(&topic) { + if let Err(e) = swarm.behaviour_mut().gossipsub.subscribe(&topic) { warn!("failed to subscribe to {}: {}", t, e); } else { info!("subscribed to {}", t); @@ -113,8 +127,8 @@ pub async fn run_network( } } if connected { - // Brief pause for protocol negotiation after first connection - tokio::time::sleep(Duration::from_secs(2)).await; + // Brief pause for identify exchange + gossipsub subscription propagation + tokio::time::sleep(Duration::from_secs(3)).await; info!("peer connected, signalling ready"); } else { warn!("no peer connections after 60s, proceeding anyway"); @@ -140,7 +154,7 @@ pub async fn run_network( } Some(req) = rx.recv() => { let topic = IdentTopic::new(&req.topic); - match swarm.behaviour_mut().publish(topic, req.data) { + match swarm.behaviour_mut().gossipsub.publish(topic, req.data) { Ok(msg_id) => { log::debug!("published to {}: {:?}", req.topic, msg_id); } From 35b99a6d4140e8dfdfb639e07bcd923e0fbff536 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 21:17:46 -0400 Subject: [PATCH 21/47] Directly use types from Forest/filecoin-rust --- hegel-workload/Cargo.lock | 336 +++++++++++++++++++--- hegel-workload/Cargo.toml | 2 + hegel-workload/src/generators/address.rs | 206 +++++-------- hegel-workload/src/generators/blocks.rs | 14 +- hegel-workload/src/generators/messages.rs | 154 +++++----- 5 files changed, 456 insertions(+), 256 deletions(-) diff --git a/hegel-workload/Cargo.lock b/hegel-workload/Cargo.lock index d1619ecd..96d2de3d 100644 --- a/hegel-workload/Cargo.lock +++ b/hegel-workload/Cargo.lock @@ -126,6 +126,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "asn1-rs" version = "0.7.1" @@ -150,7 +156,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -162,7 +168,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -203,7 +209,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -214,7 +220,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -286,6 +292,9 @@ name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] [[package]] name = "blake2" @@ -296,6 +305,17 @@ dependencies = [ "digest", ] +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -332,6 +352,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cbor4ii" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" +dependencies = [ + "serde", +] + [[package]] name = "cc" version = "1.2.58" @@ -405,6 +434,20 @@ dependencies = [ "half", ] +[[package]] +name = "cid" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" +dependencies = [ + "core2", + "multibase", + "multihash", + "serde", + "serde_bytes", + "unsigned-varint 0.8.0", +] + [[package]] name = "cipher" version = "0.4.4" @@ -443,6 +486,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "core-foundation" version = "0.9.4" @@ -560,7 +609,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -586,7 +635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn", + "syn 2.0.117", ] [[package]] @@ -641,7 +690,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -689,7 +738,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -867,7 +916,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -916,6 +965,56 @@ dependencies = [ "slab", ] +[[package]] +name = "fvm_ipld_blockstore" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b8b31e022f71b73440054f7e5171231a1ebc745adf075014d5aa8ea78ea283" +dependencies = [ + "anyhow", + "cid", + "multihash-codetable", +] + +[[package]] +name = "fvm_ipld_encoding" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fd0c7d16be0076920acd5bf13e705a80dfe6540d4722b19745daa9ea93722a" +dependencies = [ + "anyhow", + "cid", + "fvm_ipld_blockstore", + "multihash-codetable", + "serde", + "serde_ipld_dagcbor", + "serde_repr", + "serde_tuple", + "thiserror 2.0.18", +] + +[[package]] +name = "fvm_shared" +version = "4.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d3a23bf6d2a96ca280745afea1a79c5b701d5ac07814ec50ea34380762d47a" +dependencies = [ + "anyhow", + "bitflags", + "blake2b_simd", + "cid", + "data-encoding", + "data-encoding-macro", + "fvm_ipld_encoding", + "num-bigint", + "num-derive", + "num-integer", + "num-traits", + "serde", + "thiserror 2.0.18", + "unsigned-varint 0.8.0", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1053,6 +1152,8 @@ version = "0.1.0" dependencies = [ "env_logger", "futures", + "fvm_ipld_encoding", + "fvm_shared", "getrandom 0.2.17", "hegeltest", "libp2p", @@ -1084,7 +1185,7 @@ checksum = "4f02fefc14c7e6bf4999b549649e463276b3cf342cdfc1e25935f14fa48c2164" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1451,6 +1552,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ipld-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090f624976d72f0b0bb71b86d58dc16c15e069193067cb3a3a09d655246cbbda" +dependencies = [ + "cid", + "serde", + "serde_bytes", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1490,7 +1602,7 @@ checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1799,7 +1911,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1917,7 +2029,7 @@ checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1998,9 +2110,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" dependencies = [ "core2", + "serde", "unsigned-varint 0.8.0", ] +[[package]] +name = "multihash-codetable" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67996849749d25f1da9f238e8ace2ece8f9d6bdf3f9750aaf2ae7de3a5cad8ea" +dependencies = [ + "blake2b_simd", + "core2", + "multihash-derive", +] + +[[package]] +name = "multihash-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1b7edab35d920890b88643a765fc9bd295cf0201f4154dda231bef9b8404eb" +dependencies = [ + "core2", + "multihash", + "multihash-derive-impl", +] + +[[package]] +name = "multihash-derive-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3dc7141bd06405929948754f0628d247f5ca1865be745099205e5086da957cb" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + [[package]] name = "multistream-select" version = "0.13.0" @@ -2107,6 +2255,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -2220,7 +2379,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2322,7 +2481,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", ] [[package]] @@ -2354,7 +2522,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2715,6 +2883,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -2732,7 +2910,19 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "serde_ipld_dagcbor" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" +dependencies = [ + "cbor4ii", + "ipld-core", + "scopeguard", + "serde", ] [[package]] @@ -2748,6 +2938,38 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_tuple" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f025b91216f15a2a32aa39669329a475733590a015835d1783549a56d09427" +dependencies = [ + "serde", + "serde_tuple_macros", +] + +[[package]] +name = "serde_tuple_macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4076151d1a2b688e25aaf236997933c66e18b870d0369f8b248b8ab2be630d7e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2861,6 +3083,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -2880,7 +3113,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2949,7 +3182,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2960,7 +3193,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3044,7 +3277,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3060,6 +3293,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da053d28fe57e2c9d21b48261e14e7b4c8b670b54d2c684847b91feaf4c7dac5" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9" +dependencies = [ + "winnow", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -3085,7 +3348,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3255,7 +3518,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -3371,7 +3634,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3382,7 +3645,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3595,6 +3858,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -3625,7 +3897,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3641,7 +3913,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3792,7 +4064,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -3813,7 +4085,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3833,7 +4105,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -3854,7 +4126,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3887,7 +4159,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml index 888b61af..0ce0ca48 100644 --- a/hegel-workload/Cargo.toml +++ b/hegel-workload/Cargo.toml @@ -15,3 +15,5 @@ env_logger = "0.11" sha2 = "0.10" getrandom = "0.2" futures = "0.3" +fvm_shared = { version = "4", features = [] } +fvm_ipld_encoding = "0.5" diff --git a/hegel-workload/src/generators/address.rs b/hegel-workload/src/generators/address.rs index 5f141c11..5552e602 100644 --- a/hegel-workload/src/generators/address.rs +++ b/hegel-workload/src/generators/address.rs @@ -1,136 +1,83 @@ +use fvm_shared::address::Address; use hegel::generators as gs; -/// Generate a Filecoin address as raw bytes. +/// Generate a structurally valid Filecoin Address. +/// Uses fvm_shared constructors to guarantee correct encoding. +/// Values are fuzzed (random keys, arbitrary actor IDs) so that +/// semantic validation fails deeper in the pipeline. #[hegel::composite] -pub fn filecoin_address(tc: hegel::TestCase) -> Vec { - let protocol: u8 = tc.draw(gs::sampled_from(vec![0u8, 1, 2, 3, 4, 5, 0xff])); +pub fn filecoin_address(tc: hegel::TestCase) -> Address { + let protocol: u8 = tc.draw(gs::sampled_from(vec![0u8, 1, 2, 3, 4])); match protocol { 0 => tc.draw(id_address()), - 1 => tc.draw(hash_address(1)), - 2 => tc.draw(hash_address(2)), + 1 => tc.draw(secp256k1_address()), + 2 => tc.draw(actor_address()), 3 => tc.draw(bls_address()), - 4 => tc.draw(delegated_address()), - _ => { - let len: usize = tc.draw(gs::integers::().min_value(0).max_value(20)); - let mut addr = vec![protocol]; - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); - addr.extend(payload); - addr - } + _ => tc.draw(delegated_address()), } } -/// ID address: protocol 0 + varint-encoded actor ID with edge cases. +/// ID address with fuzzed actor IDs (system actors, miners, nonexistent, boundary). #[hegel::composite] -pub fn id_address(tc: hegel::TestCase) -> Vec { - let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(4)); - match variant { - 0 => vec![0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f], // overflow varint - 1 => { - vec![ - 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, - ] // 10-byte varint - } - 2 => vec![0x00, 0x80], // truncated varint - 3 => vec![0x00], // empty payload - _ => { - let len: usize = tc.draw(gs::integers::().min_value(1).max_value(9)); - let mut addr = vec![0x00]; - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); - addr.extend(payload); - addr - } - } +pub fn id_address(tc: hegel::TestCase) -> Address { + let actor_id: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, // system actor + 1, // init actor + 2, // reward actor + 3, // cron actor + 4, // storage power actor + 5, // storage market actor + 6, // verified registry actor + 7, // datacap actor + 10, // EAM actor + 99, // burnt funds actor + 1000, // first miner + 1001, // second miner + 2000, // some account + 999_999, // high actor ID + ])); + Address::new_id(actor_id) } -/// Hash address (secp256k1=1 or actor=2): 20-byte payload with fuzzed lengths. +/// Secp256k1 address with random 65-byte public key (won't match any real actor). #[hegel::composite] -pub fn hash_address(tc: hegel::TestCase, proto_byte: u8) -> Vec { - let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(3)); - match variant { - 0 => { - let len: usize = tc.draw(gs::integers::().min_value(0).max_value(40)); - let mut addr = vec![proto_byte]; - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); - addr.extend(payload); - addr - } - 1 => vec![proto_byte], // empty payload - 2 => { - let mut addr = vec![proto_byte]; - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(30).max_size(30)); - addr.extend(payload); - addr - } - _ => { - let mut addr = vec![proto_byte]; - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(20).max_size(20)); - addr.extend(payload); - addr - } - } +pub fn secp256k1_address(tc: hegel::TestCase) -> Address { + // Secp256k1 public keys are 65 bytes (uncompressed) + let pubkey: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(65).max_size(65)); + Address::new_secp256k1(&pubkey).expect("65-byte pubkey should produce valid secp256k1 address") } -/// BLS address: protocol 3, 48-byte public key with varied lengths. +/// Actor address with random 32-byte data. #[hegel::composite] -pub fn bls_address(tc: hegel::TestCase) -> Vec { - let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); - match variant { - 0 => { - let len: usize = tc.draw(gs::integers::().min_value(0).max_value(60)); - let mut addr = vec![0x03]; - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); - addr.extend(payload); - addr - } - 1 => vec![0x03], // empty payload - _ => { - let mut addr = vec![0x03]; - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(48).max_size(48)); - addr.extend(payload); - addr - } - } +pub fn actor_address(tc: hegel::TestCase) -> Address { + let data: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); + Address::new_actor(&data) } -/// Delegated address: protocol 4, namespace varint + sub-address with edge cases. +/// BLS address with random 48-byte public key. #[hegel::composite] -pub fn delegated_address(tc: hegel::TestCase) -> Vec { - let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(5)); - match variant { - 0 => vec![0x04, 0x0a], // empty sub-address - 1 => vec![0x04, 0xff, 0xff, 0xff, 0xff, 0x0f], // max namespace varint - 2 => vec![0x04, 0x80], // truncated namespace varint - 3 => { - let mut addr = vec![0x04, 0x0a]; // namespace 10 - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(128).max_size(128)); - addr.extend(payload); - addr - } - 4 => { - let mut addr = vec![0x04, 0x00]; // namespace 0 (invalid) - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(20).max_size(20)); - addr.extend(payload); - addr - } - _ => { - let len: usize = tc.draw(gs::integers::().min_value(0).max_value(40)); - let mut addr = vec![0x04, 0x0a]; // EAM namespace 10 - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); - addr.extend(payload); - addr - } - } +pub fn bls_address(tc: hegel::TestCase) -> Address { + let pubkey: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(48).max_size(48)); + Address::new_bls(&pubkey).expect("48-byte pubkey should produce valid BLS address") +} + +/// Delegated address with fuzzed namespace and sub-address. +#[hegel::composite] +pub fn delegated_address(tc: hegel::TestCase) -> Address { + let namespace: u64 = tc.draw(gs::sampled_from(vec![ + 10u64, // EAM (Ethereum Address Manager) + 1, // init actor namespace + 99, // arbitrary + 1000, // high namespace + ])); + let sub_len: usize = tc.draw(gs::sampled_from(vec![20usize, 1, 32, 54])); + let subaddr: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(sub_len).max_size(sub_len)); + Address::new_delegated(namespace, &subaddr) + .expect("delegated address construction should not fail") } #[cfg(test)] @@ -138,30 +85,23 @@ mod tests { use super::*; #[hegel::test(test_cases = 50)] - fn test_filecoin_address_produces_bytes(tc: hegel::TestCase) { - let addr: Vec = tc.draw(filecoin_address()); - assert!(!addr.is_empty(), "address must not be empty"); - let proto = addr[0]; - assert!( - proto <= 5 || proto == 0xff, - "unexpected protocol byte: {}", - proto - ); + fn test_filecoin_address_roundtrips(tc: hegel::TestCase) { + let addr: Address = tc.draw(filecoin_address()); + // Verify the address can be serialized to bytes and back + let bytes = addr.to_bytes(); + let recovered = Address::from_bytes(&bytes).expect("address should roundtrip"); + assert_eq!(addr, recovered); } #[hegel::test(test_cases = 50)] - fn test_valid_id_address(tc: hegel::TestCase) { - let addr: Vec = tc.draw(id_address()); - assert_eq!(addr[0], 0x00, "ID address protocol must be 0"); - assert!( - addr.len() >= 1, - "ID address must have at least protocol byte" - ); + fn test_id_address_valid(tc: hegel::TestCase) { + let addr: Address = tc.draw(id_address()); + assert_eq!(addr.protocol(), fvm_shared::address::Protocol::ID); } #[hegel::test(test_cases = 50)] - fn test_bls_address_protocol(tc: hegel::TestCase) { - let addr: Vec = tc.draw(bls_address()); - assert_eq!(addr[0], 0x03, "BLS address protocol must be 3"); + fn test_bls_address_valid(tc: hegel::TestCase) { + let addr: Address = tc.draw(bls_address()); + assert_eq!(addr.protocol(), fvm_shared::address::Protocol::BLS); } } diff --git a/hegel-workload/src/generators/blocks.rs b/hegel-workload/src/generators/blocks.rs index 8b1da3bc..34455764 100644 --- a/hegel-workload/src/generators/blocks.rs +++ b/hegel-workload/src/generators/blocks.rs @@ -1,9 +1,11 @@ +use fvm_shared::address::Address; use hegel::generators as gs; use crate::cbor::*; use crate::generators::address::filecoin_address; /// Generate a `BlockMsg` as CBOR array(3): [Header, BlsMessages []CID, SecpkMessages []CID]. +/// The header uses a valid miner address (via fvm_shared) for correct decoding. #[hegel::composite] pub fn block_msg(tc: hegel::TestCase) -> Vec { let header = tc.draw(block_header()); @@ -12,15 +14,13 @@ pub fn block_msg(tc: hegel::TestCase) -> Vec { cbor_array(&[&header, &bls_msgs, &secpk_msgs]) } -/// Generate a Filecoin block header as a 16-field CBOR array: -/// [Miner, Ticket, ElectionProof, BeaconEntries, WinPoStProof, Parents, -/// ParentWeight, Height, ParentStateRoot, ParentMessageReceipts, Messages, -/// BLSAggregate, Timestamp, BlockSig, ForkSignaling, ParentBaseFee] +/// Generate a Filecoin block header as a 16-field CBOR array. +/// Miner address uses fvm_shared for correct encoding. #[hegel::composite] fn block_header(tc: hegel::TestCase) -> Vec { - // Miner address - let miner_addr: Vec = tc.draw(filecoin_address()); - let miner = cbor_bytes(&miner_addr); + // Miner address — use fvm_shared Address serialized to bytes + let miner_addr: Address = tc.draw(filecoin_address()); + let miner = cbor_bytes(&miner_addr.to_bytes()); // Ticket let ticket = tc.draw(ticket_field()); diff --git a/hegel-workload/src/generators/messages.rs b/hegel-workload/src/generators/messages.rs index e28a6285..6f51d124 100644 --- a/hegel-workload/src/generators/messages.rs +++ b/hegel-workload/src/generators/messages.rs @@ -1,122 +1,107 @@ +use fvm_ipld_encoding::{RawBytes, to_vec}; +use fvm_shared::address::Address; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::econ::TokenAmount; +use fvm_shared::message::Message; use hegel::generators as gs; -use crate::cbor::*; use crate::generators::address::filecoin_address; -/// Generate a complete SignedMessage as CBOR: array(2) [Message, Signature]. +/// Generate a complete SignedMessage as DAG-CBOR bytes. +/// Serialized as a CBOR tuple (Message, Signature) matching Lotus wire format. #[hegel::composite] pub fn signed_message(tc: hegel::TestCase) -> Vec { let msg = tc.draw(filecoin_message()); - let sig = tc.draw(signature()); - cbor_array(&[&msg, &sig]) + let sig = tc.draw(fuzz_signature()); + // Lotus encodes SignedMessage as CBOR array [Message, Signature] + to_vec(&(&msg, &sig)).expect("DAG-CBOR serialization of SignedMessage should not fail") } -/// Generate a Filecoin Message as CBOR array(10): -/// [Version, To, From, Nonce, Value, GasLimit, GasFeeCap, GasPremium, Method, Params] +/// Generate a Filecoin Message with structurally valid fields but fuzzed values. +/// Addresses are valid format, values are edge-case, nonces/methods are fuzzed. #[hegel::composite] -fn filecoin_message(tc: hegel::TestCase) -> Vec { - let version = cbor_uint64(0); +fn filecoin_message(tc: hegel::TestCase) -> Message { + let to: Address = tc.draw(filecoin_address()); + let from: Address = tc.draw(filecoin_address()); - let to_addr: Vec = tc.draw(filecoin_address()); - let to = cbor_bytes(&to_addr); - - let from_addr: Vec = tc.draw(filecoin_address()); - let from = cbor_bytes(&from_addr); - - let nonce_val: u64 = - tc.draw(gs::sampled_from(vec![0u64, 1, u64::MAX - 1, u64::MAX, 42, 1000, 1_000_000])); - let nonce = cbor_uint64(nonce_val); - - let value_raw: Vec = tc.draw(big_int_value()); - let value = cbor_bytes(&value_raw); - - let gas_limit_val: i64 = tc.draw(gs::sampled_from(vec![ - 0i64, - 1, - -1, - 10_000_000, - i64::MAX, - i64::MIN + 1, - 100_000, - -100_000, + let sequence: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 42, 1000, 1_000_000, u64::MAX, ])); - let gas_limit = cbor_int64(gas_limit_val); - let gas_fee_cap_raw: Vec = tc.draw(big_int_value()); - let gas_fee_cap = cbor_bytes(&gas_fee_cap_raw); - - let gas_premium_raw: Vec = tc.draw(big_int_value()); - let gas_premium = cbor_bytes(&gas_premium_raw); - - let method_val: u64 = tc.draw(gs::sampled_from(vec![ - 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, u64::MAX, 999999, + let value = tc.draw(fuzz_token_amount()); + let gas_limit: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 10_000_000, 100_000, u64::MAX, ])); - let method = cbor_uint64(method_val); + let gas_fee_cap = tc.draw(fuzz_token_amount()); + let gas_premium = tc.draw(fuzz_token_amount()); - let params = tc.draw(message_params()); + let method_num: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 20, 24, u64::MAX, 999999, + ])); - cbor_array(&[ - &version, - &to, - &from, - &nonce, - &value, - &gas_limit, - &gas_fee_cap, - &gas_premium, - &method, - ¶ms, - ]) + let params = tc.draw(fuzz_params()); + + Message { + version: 0, + to, + from, + sequence, + value, + method_num, + params, + gas_limit, + gas_fee_cap, + gas_premium, + } } -/// Generate edge-case BigInt values for Value/GasFeeCap/GasPremium fields. +/// Generate fuzzed TokenAmount values (BigInt). #[hegel::composite] -fn big_int_value(tc: hegel::TestCase) -> Vec { +fn fuzz_token_amount(tc: hegel::TestCase) -> TokenAmount { let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(4)); match variant { - 0 => big_int_bytes(0), // zero - 1 => big_int_bytes(1), // minimal positive - 2 => big_int_bytes(u64::MAX), // max uint64 - 3 => vec![0x00], // sign-only (positive sign, zero magnitude) + 0 => TokenAmount::from_atto(0), + 1 => TokenAmount::from_atto(1), + 2 => TokenAmount::from_atto(u64::MAX), + 3 => TokenAmount::from_atto(1_000_000_000_000_000_000u64), // 1 FIL _ => { - // large random value - let len: usize = tc.draw(gs::integers::().min_value(1).max_value(16)); - let mut bytes = vec![0x00u8]; // positive sign - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); - bytes.extend(payload); - bytes + let v: u64 = tc.draw(gs::integers::()); + TokenAmount::from_atto(v) } } } -/// Generate a Signature as CBOR bytes: type_byte followed by signature data. +/// Generate a signature with valid type but random (incorrect) bytes. +/// This passes decoding but fails signature verification. #[hegel::composite] -fn signature(tc: hegel::TestCase) -> Vec { - let sig_type: u8 = tc.draw(gs::sampled_from(vec![1u8, 2, 0, 3, 0xff])); - let sig_len: usize = tc.draw(gs::sampled_from(vec![0usize, 1, 64, 65, 96, 48, 128])); - - let mut sig_data = vec![sig_type]; - let payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(sig_len).max_size(sig_len)); - sig_data.extend(payload); - cbor_bytes(&sig_data) +fn fuzz_signature(tc: hegel::TestCase) -> Signature { + let use_bls: bool = tc.draw(gs::booleans()); + if use_bls { + // BLS signatures are 96 bytes + let bytes: Vec = tc.draw(gs::vecs(gs::integers::()).min_size(96).max_size(96)); + Signature::new_bls(bytes) + } else { + // Secp256k1 signatures are 65 bytes + let bytes: Vec = tc.draw(gs::vecs(gs::integers::()).min_size(65).max_size(65)); + Signature::new_secp256k1(bytes) + } } -/// Generate message params: empty, valid CBOR empty array, or random bytes. +/// Generate message params: empty or random CBOR-compatible bytes. #[hegel::composite] -fn message_params(tc: hegel::TestCase) -> Vec { +fn fuzz_params(tc: hegel::TestCase) -> RawBytes { let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); match variant { - 0 => cbor_bytes(&[]), // empty params - 1 => cbor_bytes(&cbor_array(&[])), // valid CBOR empty array as params + 0 => RawBytes::new(vec![]), + 1 => { + // Valid CBOR empty array as params + RawBytes::new(vec![0x80]) + } _ => { - // random bytes let len: usize = tc.draw(gs::integers::().min_value(1).max_value(64)); let payload: Vec = tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); - cbor_bytes(&payload) + RawBytes::new(payload) } } } @@ -126,8 +111,9 @@ mod tests { use super::*; #[hegel::test(test_cases = 50)] - fn test_signed_message_is_valid_cbor_structure(tc: hegel::TestCase) { + fn test_signed_message_is_valid_cbor(tc: hegel::TestCase) { let msg_bytes: Vec = tc.draw(signed_message()); + // Should start with CBOR array(2) header assert_eq!(msg_bytes[0], 0x82, "SignedMessage must be CBOR array(2)"); } From 36164bd784df4fa248876f29338f8da5c0c4f9d3 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Tue, 31 Mar 2026 21:45:26 -0400 Subject: [PATCH 22/47] Re-create block types --- hegel-workload/Cargo.lock | 133 ++++++++- hegel-workload/Cargo.toml | 5 + hegel-workload/src/generators/blocks.rs | 370 ++++++++++++++---------- 3 files changed, 337 insertions(+), 171 deletions(-) diff --git a/hegel-workload/Cargo.lock b/hegel-workload/Cargo.lock index 96d2de3d..aeee47d1 100644 --- a/hegel-workload/Cargo.lock +++ b/hegel-workload/Cargo.lock @@ -20,7 +20,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -287,6 +287,12 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.0" @@ -316,6 +322,31 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "blake2s_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee29928bad1e3f94c9d1528da29e07a1d3d04817ae8332de1e8b846c8439f4b3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "1.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -391,7 +422,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -526,6 +557,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -592,7 +632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest", "fiat-crypto", @@ -1000,7 +1040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94d3a23bf6d2a96ca280745afea1a79c5b701d5ac07814ec50ea34380762d47a" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.0", "blake2b_simd", "cid", "data-encoding", @@ -1150,6 +1190,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" name = "hegel-workload" version = "0.1.0" dependencies = [ + "cid", "env_logger", "futures", "fvm_ipld_encoding", @@ -1158,6 +1199,10 @@ dependencies = [ "hegeltest", "libp2p", "log", + "multihash", + "multihash-codetable", + "serde", + "serde_bytes", "sha2", "tokio", ] @@ -1615,6 +1660,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2121,8 +2175,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67996849749d25f1da9f238e8ace2ece8f9d6bdf3f9750aaf2ae7de3a5cad8ea" dependencies = [ "blake2b_simd", + "blake2s_simd", + "blake3", "core2", + "digest", "multihash-derive", + "ripemd", + "sha1", + "sha2", + "sha3", + "strobe-rs", ] [[package]] @@ -2178,7 +2240,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" dependencies = [ - "bitflags", + "bitflags 2.11.0", "libc", "log", "netlink-packet-core", @@ -2217,7 +2279,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -2418,7 +2480,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2430,7 +2492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2702,7 +2764,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.0", ] [[package]] @@ -2754,6 +2816,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + [[package]] name = "rtnetlink" version = "0.20.0" @@ -2802,7 +2873,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -2970,6 +3041,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2977,8 +3059,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ "digest", + "keccak", ] [[package]] @@ -3077,6 +3169,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strobe-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98fe17535ea31344936cc58d29fec9b500b0452ddc4cc24c429c8a921a0e84e5" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "keccak", + "subtle", + "zeroize", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3122,7 +3227,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.11.0", "core-foundation", "system-configuration-sys", ] @@ -3559,7 +3664,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap", "semver", @@ -3925,7 +4030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.0", "indexmap", "log", "serde", diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml index 0ce0ca48..9351ddaf 100644 --- a/hegel-workload/Cargo.toml +++ b/hegel-workload/Cargo.toml @@ -17,3 +17,8 @@ getrandom = "0.2" futures = "0.3" fvm_shared = { version = "4", features = [] } fvm_ipld_encoding = "0.5" +serde_bytes = "0.11" +serde = { version = "1", features = ["derive"] } +cid = { version = "0.11", features = ["serde"] } +multihash-codetable = { version = "0.1", features = ["sha2"] } +multihash = "0.19" diff --git a/hegel-workload/src/generators/blocks.rs b/hegel-workload/src/generators/blocks.rs index 34455764..fc39b2e4 100644 --- a/hegel-workload/src/generators/blocks.rs +++ b/hegel-workload/src/generators/blocks.rs @@ -1,187 +1,239 @@ +use cid::Cid; +use fvm_ipld_encoding::to_vec; use fvm_shared::address::Address; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::econ::TokenAmount; use hegel::generators as gs; +use multihash_codetable::{Code, MultihashDigest}; +use serde::ser::{SerializeSeq, Serializer}; +use serde::Serialize; -use crate::cbor::*; +use crate::cbor::{random_bytes}; use crate::generators::address::filecoin_address; -/// Generate a `BlockMsg` as CBOR array(3): [Header, BlsMessages []CID, SecpkMessages []CID]. -/// The header uses a valid miner address (via fvm_shared) for correct decoding. +// --------------------------------------------------------------------------- +// Wire types — mirror Lotus's CBOR-generated encoding exactly. +// All are serialized as fixed-length CBOR arrays via custom Serialize impls. +// --------------------------------------------------------------------------- + +/// Ticket: CBOR array(1) [VRFProof bytes] or CBOR null. +#[derive(Debug)] +struct Ticket { + vrf_proof: Vec, +} + +impl Serialize for Ticket { + fn serialize(&self, s: S) -> Result { + let mut seq = s.serialize_seq(Some(1))?; + seq.serialize_element(&serde_bytes::Bytes::new(&self.vrf_proof))?; + seq.end() + } +} + +/// ElectionProof: CBOR array(2) [WinCount int64, VRFProof bytes] or CBOR null. +#[derive(Debug)] +struct ElectionProof { + win_count: i64, + vrf_proof: Vec, +} + +impl Serialize for ElectionProof { + fn serialize(&self, s: S) -> Result { + let mut seq = s.serialize_seq(Some(2))?; + seq.serialize_element(&self.win_count)?; + seq.serialize_element(&serde_bytes::Bytes::new(&self.vrf_proof))?; + seq.end() + } +} + +/// Generate a random CID (CIDv1, dag-cbor codec, sha2-256 hash). +fn gen_random_cid() -> Cid { + let data = random_bytes(32); + let mh = Code::Sha2_256.digest(&data); + Cid::new_v1(0x71, mh) // 0x71 = dag-cbor codec +} + +/// BlockHeader: CBOR array(16) matching Lotus's field order exactly. +/// Pointer fields (Ticket, ElectionProof, BLSAggregate, BlockSig) are +/// Option — None serializes as CBOR null, exercising nil-pointer paths. +#[derive(Debug)] +struct BlockHeader { + miner: Address, + ticket: Option, + election_proof: Option, + beacon_entries: Vec<()>, // empty slice → CBOR array(0) + win_post_proof: Vec<()>, // empty slice → CBOR array(0) + parents: Vec, + parent_weight: TokenAmount, + height: i64, + parent_state_root: Cid, + parent_message_receipts: Cid, + messages: Cid, + bls_aggregate: Option, + timestamp: u64, + block_sig: Option, + fork_signaling: u64, + parent_base_fee: TokenAmount, +} + +impl Serialize for BlockHeader { + fn serialize(&self, s: S) -> Result { + let mut seq = s.serialize_seq(Some(16))?; + seq.serialize_element(&self.miner)?; + seq.serialize_element(&self.ticket)?; + seq.serialize_element(&self.election_proof)?; + seq.serialize_element(&self.beacon_entries)?; + seq.serialize_element(&self.win_post_proof)?; + seq.serialize_element(&self.parents)?; + seq.serialize_element(&self.parent_weight)?; + seq.serialize_element(&self.height)?; + seq.serialize_element(&self.parent_state_root)?; + seq.serialize_element(&self.parent_message_receipts)?; + seq.serialize_element(&self.messages)?; + seq.serialize_element(&self.bls_aggregate)?; + seq.serialize_element(&self.timestamp)?; + seq.serialize_element(&self.block_sig)?; + seq.serialize_element(&self.fork_signaling)?; + seq.serialize_element(&self.parent_base_fee)?; + seq.end() + } +} + +/// BlockMsg: CBOR array(3) [Header, BlsMessages []CID, SecpkMessages []CID]. +#[derive(Debug)] +struct BlockMsg { + header: BlockHeader, + bls_messages: Vec, + secpk_messages: Vec, +} + +impl Serialize for BlockMsg { + fn serialize(&self, s: S) -> Result { + let mut seq = s.serialize_seq(Some(3))?; + seq.serialize_element(&self.header)?; + seq.serialize_element(&self.bls_messages)?; + seq.serialize_element(&self.secpk_messages)?; + seq.end() + } +} + +// --------------------------------------------------------------------------- +// Generators +// --------------------------------------------------------------------------- + +/// Generate a `BlockMsg` as DAG-CBOR bytes. #[hegel::composite] pub fn block_msg(tc: hegel::TestCase) -> Vec { - let header = tc.draw(block_header()); - let bls_msgs = tc.draw(cid_list()); - let secpk_msgs = tc.draw(cid_list()); - cbor_array(&[&header, &bls_msgs, &secpk_msgs]) + let header = tc.draw(gen_block_header()); + let bls_messages = tc.draw(gen_cid_list()); + let secpk_messages = tc.draw(gen_cid_list()); + let msg = BlockMsg { + header, + bls_messages, + secpk_messages, + }; + to_vec(&msg).expect("DAG-CBOR serialization of BlockMsg should not fail") } -/// Generate a Filecoin block header as a 16-field CBOR array. -/// Miner address uses fvm_shared for correct encoding. +/// Generate a BlockHeader with fuzzed fields. +/// Pointer fields randomly alternate between Some/None to exercise nil-pointer +/// code paths — this is where many Filecoin bugs have been found. #[hegel::composite] -fn block_header(tc: hegel::TestCase) -> Vec { - // Miner address — use fvm_shared Address serialized to bytes - let miner_addr: Address = tc.draw(filecoin_address()); - let miner = cbor_bytes(&miner_addr.to_bytes()); - - // Ticket - let ticket = tc.draw(ticket_field()); - - // ElectionProof - let election_proof = tc.draw(election_proof_field()); - - // BeaconEntries: nil or empty array - let beacon_variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(1)); - let beacon_entries = match beacon_variant { - 0 => cbor_nil(), - _ => cbor_array(&[]), +fn gen_block_header(tc: hegel::TestCase) -> BlockHeader { + let miner: Address = tc.draw(filecoin_address()); + + // Ticket — nullable pointer field + let has_ticket: bool = tc.draw(gs::booleans()); + let ticket = if has_ticket { + let vrf_proof: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); + Some(Ticket { vrf_proof }) + } else { + None }; - // WinPoStProof: always empty array - let win_post_proof = cbor_array(&[]); + // ElectionProof — nullable pointer field + let has_election: bool = tc.draw(gs::booleans()); + let election_proof = if has_election { + let win_count: i64 = tc.draw(gs::sampled_from(vec![ + 0i64, 1, -1, 5, 100, i64::MAX, i64::MIN + 1, + ])); + let vrf_proof: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); + Some(ElectionProof { + win_count, + vrf_proof, + }) + } else { + None + }; - // Parents - let parents = tc.draw(parent_cids()); + // Parents: 0-2 CIDs + let num_parents: usize = tc.draw(gs::integers::().min_value(0).max_value(2)); + let parents: Vec = (0..num_parents).map(|_| gen_random_cid()).collect(); - // ParentWeight + // Parent weight let pw_val: u64 = tc.draw(gs::sampled_from(vec![0u64, 1, 100, 999_999_999, u64::MAX])); - let parent_weight = cbor_bytes(&big_int_bytes(pw_val)); + let parent_weight = TokenAmount::from_atto(pw_val); // Height - let h_val: u64 = tc.draw(gs::sampled_from(vec![ - 0u64, - 1, - 10, - 100, - 1000, - u64::MAX, - u64::MAX - 1, - ])); - let height = cbor_uint64(h_val); - - // ParentStateRoot, ParentMessageReceipts, Messages — random CIDs - let state_root_cid = random_cid(); - let parent_state_root = cbor_cid(&state_root_cid); - - let msg_receipts_cid = random_cid(); - let parent_msg_receipts = cbor_cid(&msg_receipts_cid); - - let messages_cid = random_cid(); - let messages = cbor_cid(&messages_cid); - - // BLSAggregate: nil or BLS type byte - let bls_agg_variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(1)); - let bls_aggregate = match bls_agg_variant { - 0 => cbor_nil(), - _ => cbor_bytes(&[0x02]), - }; + let height: i64 = tc.draw(gs::sampled_from(vec![0i64, 1, 10, 100, 1000, i64::MAX])); + + // CID fields + let parent_state_root = gen_random_cid(); + let parent_message_receipts = gen_random_cid(); + let messages = gen_random_cid(); - // Timestamp: fixed - let timestamp = cbor_uint64(1_700_000_000); - - // BlockSig: nil or BLS sig with random 8 bytes - let sig_variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(1)); - let block_sig = match sig_variant { - 0 => cbor_nil(), - _ => { - let sig_payload: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(8).max_size(8)); - let mut sig_data = vec![0x02u8]; // BLS type - sig_data.extend(sig_payload); - cbor_bytes(&sig_data) - } + // BLSAggregate — nullable pointer field + let has_bls_agg: bool = tc.draw(gs::booleans()); + let bls_aggregate = if has_bls_agg { + let bytes: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(96).max_size(96)); + Some(Signature::new_bls(bytes)) + } else { + None }; - // ForkSignaling: 0 - let fork_signaling = cbor_uint64(0); - - // ParentBaseFee - let parent_base_fee = cbor_bytes(&big_int_bytes(100)); - - cbor_array(&[ - &miner, - &ticket, - &election_proof, - &beacon_entries, - &win_post_proof, - &parents, - &parent_weight, - &height, - &parent_state_root, - &parent_msg_receipts, - &messages, - &bls_aggregate, - ×tamp, - &block_sig, - &fork_signaling, - &parent_base_fee, - ]) -} + let timestamp: u64 = tc.draw(gs::sampled_from(vec![0u64, 1_700_000_000, u64::MAX])); -/// Ticket field: nil, valid [VRFProof 32 bytes], or empty array. -#[hegel::composite] -fn ticket_field(tc: hegel::TestCase) -> Vec { - let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); - match variant { - 0 => cbor_nil(), - 1 => { - let vrf_proof: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); - let proof_bytes = cbor_bytes(&vrf_proof); - cbor_array(&[&proof_bytes]) - } - _ => cbor_array(&[]), - } -} + // BlockSig — nullable pointer field + let has_block_sig: bool = tc.draw(gs::booleans()); + let block_sig = if has_block_sig { + let bytes: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(96).max_size(96)); + Some(Signature::new_bls(bytes)) + } else { + None + }; -/// ElectionProof field: nil or [WinCount i64, VRFProof 32 bytes]. -#[hegel::composite] -fn election_proof_field(tc: hegel::TestCase) -> Vec { - let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(1)); - match variant { - 0 => cbor_nil(), - _ => { - let win_count_val: i64 = - tc.draw(gs::sampled_from(vec![0i64, 1, -1, 5, 100, i64::MAX, i64::MIN + 1])); - let win_count = cbor_int64(win_count_val); - let vrf_proof: Vec = - tc.draw(gs::vecs(gs::integers::()).min_size(32).max_size(32)); - let proof_bytes = cbor_bytes(&vrf_proof); - cbor_array(&[&win_count, &proof_bytes]) - } - } -} + let fork_signaling: u64 = tc.draw(gs::sampled_from(vec![0u64, 1, u64::MAX])); + let parent_base_fee = TokenAmount::from_atto(100u64); -/// Parent CIDs: nil, empty, single CID, or two CIDs. -#[hegel::composite] -fn parent_cids(tc: hegel::TestCase) -> Vec { - let variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(3)); - match variant { - 0 => cbor_nil(), - 1 => cbor_array(&[]), - 2 => { - let cid = random_cid(); - let c = cbor_cid(&cid); - cbor_array(&[&c]) - } - _ => { - let cid1 = random_cid(); - let c1 = cbor_cid(&cid1); - let cid2 = random_cid(); - let c2 = cbor_cid(&cid2); - cbor_array(&[&c1, &c2]) - } + BlockHeader { + miner, + ticket, + election_proof, + beacon_entries: vec![], + win_post_proof: vec![], + parents, + parent_weight, + height, + parent_state_root, + parent_message_receipts, + messages, + bls_aggregate, + timestamp, + block_sig, + fork_signaling, + parent_base_fee, } } -/// CID list: 0-5 random CIDs for BlsMessages/SecpkMessages. +/// Generate 0-5 random CIDs. #[hegel::composite] -fn cid_list(tc: hegel::TestCase) -> Vec { +fn gen_cid_list(tc: hegel::TestCase) -> Vec { let count: usize = tc.draw(gs::integers::().min_value(0).max_value(5)); - let cids: Vec> = (0..count).map(|_| { - let raw = random_cid(); - cbor_cid(&raw) - }).collect(); - let refs: Vec<&[u8]> = cids.iter().map(|c| c.as_slice()).collect(); - cbor_array(&refs) + (0..count).map(|_| gen_random_cid()).collect() } #[cfg(test)] @@ -197,6 +249,10 @@ mod tests { #[hegel::test(test_cases = 50)] fn test_block_msg_nonempty(tc: hegel::TestCase) { let block_bytes: Vec = tc.draw(block_msg()); - assert!(block_bytes.len() > 30, "block too short: {} bytes", block_bytes.len()); + assert!( + block_bytes.len() > 30, + "block too short: {} bytes", + block_bytes.len() + ); } } From d2b954e5d55324873db60795316fcefccd143ee1 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 11:50:51 -0400 Subject: [PATCH 23/47] RPC hegel testing --- docker-compose.yaml | 3 + hegel-workload/Cargo.lock | 234 +++++++++++++++++++++++++++++++ hegel-workload/Cargo.toml | 5 +- hegel-workload/src/assertions.rs | 162 +++++++++++++++++++++ hegel-workload/src/cbor.rs | 2 +- hegel-workload/src/main.rs | 63 +++++++-- hegel-workload/src/rpc.rs | 177 +++++++++++++++++++++++ 7 files changed, 634 insertions(+), 12 deletions(-) create mode 100644 hegel-workload/src/assertions.rs create mode 100644 hegel-workload/src/rpc.rs diff --git a/docker-compose.yaml b/docker-compose.yaml index 2f5bbc10..7d13a896 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -234,6 +234,9 @@ services: - ANTITHESIS_OUTPUT_DIR=/tmp/antithesis - HEGEL_BATCH_SIZE=100 - RUST_LOG=info + - RPC_PORT=${STRESS_RPC_PORT:-1234} + - MONITOR_INTERVAL_MS=5000 + - RPC_TRAFFIC_INTERVAL_MS=1000 volumes: - ./data/lotus0:/root/devgen/lotus0 - ./data/lotus1:/root/devgen/lotus1 diff --git a/hegel-workload/Cargo.lock b/hegel-workload/Cargo.lock index aeee47d1..c6dacf5a 100644 --- a/hegel-workload/Cargo.lock +++ b/hegel-workload/Cargo.lock @@ -114,6 +114,22 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "antithesis_sdk" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dbd97a5b6c21cc9176891cf715f7f0c273caf3959897f43b9bd1231939e675" +dependencies = [ + "libc", + "libloading", + "linkme", + "once_cell", + "rand 0.8.5", + "rustc_version_runtime", + "serde", + "serde_json", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -1190,6 +1206,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" name = "hegel-workload" version = "0.1.0" dependencies = [ + "antithesis_sdk", "cid", "env_logger", "futures", @@ -1201,8 +1218,10 @@ dependencies = [ "log", "multihash", "multihash-codetable", + "reqwest", "serde", "serde_bytes", + "serde_json", "sha2", "tokio", ] @@ -1381,19 +1400,39 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + [[package]] name = "hyper-util" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", "futures-channel", "futures-util", "http 1.4.0", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2 0.6.3", "tokio", @@ -1614,6 +1653,16 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1656,6 +1705,8 @@ version = "0.3.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "797146bb2677299a1eb6b7b50a890f4c361b29ef967addf5b2fa45dae1bb6d7d" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1687,6 +1738,16 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libp2p" version = "0.55.0" @@ -2033,6 +2094,26 @@ dependencies = [ "yamux 0.13.10", ] +[[package]] +name = "linkme" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e3283ed2d0e50c06dd8602e0ab319bb048b6325d0bba739db64ed8205179898" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5cec0ec4228b4853bb129c84dbf093a27e6c7a20526da046defc334a1b017f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2796,6 +2877,44 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + [[package]] name = "resolv-conf" version = "0.7.6" @@ -2858,6 +2977,16 @@ dependencies = [ "semver", ] +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -2932,6 +3061,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -3041,6 +3176,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3210,6 +3357,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -3385,6 +3541,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -3428,6 +3594,45 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http 1.4.0", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -3604,6 +3809,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19280959e2844181895ef62f065c63e0ca07ece4771b53d89bfdb967d97cbf05" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.116" @@ -3670,6 +3885,16 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749466a37ee189057f54748b200186b59a03417a117267baf3fd89cecc9fb837" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" @@ -3680,6 +3905,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "widestring" version = "1.2.1" diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml index 9351ddaf..6daa7a92 100644 --- a/hegel-workload/Cargo.toml +++ b/hegel-workload/Cargo.toml @@ -4,10 +4,11 @@ version = "0.1.0" edition = "2021" [features] -antithesis = ["hegeltest/antithesis"] +antithesis = ["hegeltest/antithesis", "antithesis_sdk/full"] [dependencies] hegeltest = "0.3" +antithesis_sdk = "0.2" libp2p = { version = "0.55", features = ["gossipsub", "identify", "tcp", "noise", "yamux", "tokio", "macros"] } tokio = { version = "1", features = ["full"] } log = "0.4" @@ -19,6 +20,8 @@ fvm_shared = { version = "4", features = [] } fvm_ipld_encoding = "0.5" serde_bytes = "0.11" serde = { version = "1", features = ["derive"] } +serde_json = "1" cid = { version = "0.11", features = ["serde"] } multihash-codetable = { version = "0.1", features = ["sha2"] } multihash = "0.19" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } diff --git a/hegel-workload/src/assertions.rs b/hegel-workload/src/assertions.rs new file mode 100644 index 00000000..847494fb --- /dev/null +++ b/hegel-workload/src/assertions.rs @@ -0,0 +1,162 @@ +use crate::rpc::{check_mpool_consistency, LotusRpc}; +use log::info; +use serde_json::json; +use std::sync::atomic::{AtomicI64, AtomicBool, Ordering}; +use std::time::Duration; + +/// Tracks the highest chain height we've observed, for progress assertions. +static HIGHEST_HEIGHT: AtomicI64 = AtomicI64::new(0); + +/// Set once when both P2P and RPC traffic have been active in the same window. +static MIXED_TRAFFIC_OBSERVED: AtomicBool = AtomicBool::new(false); + +/// Record that P2P publishing happened in this monitoring window. +static P2P_ACTIVE: AtomicBool = AtomicBool::new(false); + +/// Record that RPC push happened in this monitoring window. +static RPC_ACTIVE: AtomicBool = AtomicBool::new(false); + +/// Signal that P2P traffic was active (called from the generator loop). +pub fn mark_p2p_active() { + P2P_ACTIVE.store(true, Ordering::Relaxed); +} + +/// Signal that RPC traffic was active (called from the RPC traffic task). +pub fn mark_rpc_active() { + RPC_ACTIVE.store(true, Ordering::Relaxed); +} + +/// Run periodic assertion checks against Lotus nodes via RPC. +/// This is designed to be spawned as a long-lived tokio task. +/// +/// Fault tolerance: every RPC call may fail (node killed, partitioned, etc). +/// We only fire assertions when we get successful responses. +pub async fn run_rpc_monitor(clients: Vec<(String, LotusRpc)>, interval: Duration) { + info!("rpc_monitor: starting with {} clients, interval {:?}", clients.len(), interval); + + loop { + tokio::time::sleep(interval).await; + + for (name, client) in &clients { + // --- ChainHead liveness --- + if let Some(head) = client.chain_head().await { + let height = head.height; + + // Sometimes: node responds to ChainHead (liveness) + antithesis_sdk::assert_sometimes!( + true, + "Node responds to ChainHead RPC", + &json!({"node": name, "height": height}) + ); + + // Sometimes: chain height advances (progress) + let prev = HIGHEST_HEIGHT.fetch_max(height, Ordering::Relaxed); + if height > prev { + antithesis_sdk::assert_sometimes!( + true, + "Chain height advances", + &json!({"node": name, "previous": prev, "current": height}) + ); + } + } + + // --- MpoolPending consistency --- + if let Some(pending) = client.mpool_pending().await { + let (consistent, duplicates) = check_mpool_consistency(&pending); + + // AlwaysOrUnreachable: when we can read the mempool, no sender + // should have duplicate nonces. This catches corruption from + // data races like the curTs race we found. + antithesis_sdk::assert_always_or_unreachable!( + consistent, + "MpoolPending has no duplicate nonces per sender", + &json!({ + "node": name, + "pending_count": pending.len(), + "duplicates": format!("{:?}", duplicates), + }) + ); + } + + // --- MpoolSelect exercising allPending() --- + // This calls the code path with the second unprotected curTs read. + // We don't assert on the result content, just that it doesn't + // crash or hang (the node staying responsive is the assertion). + if client.mpool_select(0.8).await.is_some() { + antithesis_sdk::assert_sometimes!( + true, + "MpoolSelect returns successfully", + &json!({"node": name}) + ); + } + } + + // --- Mixed traffic assertion --- + let p2p = P2P_ACTIVE.swap(false, Ordering::Relaxed); + let rpc = RPC_ACTIVE.swap(false, Ordering::Relaxed); + if p2p && rpc && !MIXED_TRAFFIC_OBSERVED.load(Ordering::Relaxed) { + MIXED_TRAFFIC_OBSERVED.store(true, Ordering::Relaxed); + antithesis_sdk::assert_reachable!( + "Mixed P2P and RPC traffic executed concurrently", + &json!({}) + ); + } + } +} + +/// Run RPC-based message traffic alongside P2P GossipSub traffic. +/// Periodically pushes messages through MpoolPush to create contention +/// with the P2P Add() path. +/// +/// We generate minimal valid-looking JSON messages. They'll be rejected +/// by signature validation, but the point is to exercise the Add() -> checkMessage() +/// -> curTsLk contention path before rejection. +pub async fn run_rpc_traffic(clients: Vec<(String, LotusRpc)>, interval: Duration) { + info!("rpc_traffic: starting with {} clients, interval {:?}", clients.len(), interval); + + let mut push_count: u64 = 0; + + loop { + tokio::time::sleep(interval).await; + + for (name, client) in &clients { + // Build a minimal signed message that will enter the Add() path + // before being rejected by signature verification. The structure + // must be valid enough to reach checkMessage(). + let msg_json = json!({ + "Message": { + "Version": 0, + "To": "f01000", + "From": "f01001", + "Nonce": push_count, + "Value": "0", + "GasLimit": 1000000, + "GasFeeCap": "100000", + "GasPremium": "1000", + "Method": 0, + "Params": null, + }, + "Signature": { + "Type": 1, + "Data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + } + }); + + let accepted = client.mpool_push_raw(&msg_json).await; + + if accepted { + antithesis_sdk::assert_sometimes!( + true, + "MpoolPush accepted a message via RPC", + &json!({"node": name, "push_count": push_count}) + ); + } + + // Regardless of acceptance, if we got any response (not a network + // error), mark RPC as active for the mixed-traffic assertion. + // The push_raw call already logged any network errors. + mark_rpc_active(); + push_count += 1; + } + } +} diff --git a/hegel-workload/src/cbor.rs b/hegel-workload/src/cbor.rs index 783dc892..479c729a 100644 --- a/hegel-workload/src/cbor.rs +++ b/hegel-workload/src/cbor.rs @@ -178,7 +178,7 @@ mod tests { #[test] fn test_big_int_bytes_zero() { - assert_eq!(big_int_bytes(0), vec![]); + assert_eq!(big_int_bytes(0), Vec::::new()); } #[test] diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index fe985b66..94e3e7a1 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -1,14 +1,17 @@ +mod assertions; mod cbor; mod discovery; mod generators; mod network; mod properties; +mod rpc; +use assertions::{mark_p2p_active, run_rpc_monitor, run_rpc_traffic}; use discovery::discover_nodes; use generators::blocks::block_msg; use generators::messages::signed_message; use network::{build_swarm, run_network, PublishRequest}; -use properties::log_generation; +use rpc::discover_rpc_clients; use hegel::generators as gs; use log::{error, info, warn}; @@ -37,19 +40,39 @@ fn main() { .and_then(|s| s.parse().ok()) .unwrap_or(500), ); + let rpc_port: u16 = std::env::var("RPC_PORT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(1234); + let monitor_interval: Duration = Duration::from_millis( + std::env::var("MONITOR_INTERVAL_MS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(5000), + ); + let rpc_traffic_interval: Duration = Duration::from_millis( + std::env::var("RPC_TRAFFIC_INTERVAL_MS") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(1000), + ); info!( - "config: nodes={:?}, network={}, batch_size={}", - node_names, network_name, batch_size + "config: nodes={:?}, network={}, batch_size={}, rpc_port={}", + node_names, network_name, batch_size, rpc_port ); - // Discover peers + // Discover peers for P2P let nodes = discover_nodes(&node_names, &devgen_dir); if nodes.is_empty() { error!("no nodes discovered, exiting"); std::process::exit(1); } - info!("discovered {} nodes", nodes.len()); + info!("discovered {} nodes for P2P", nodes.len()); + + // Discover RPC clients + let rpc_clients = discover_rpc_clients(&node_names, &devgen_dir, rpc_port); + info!("created {} RPC clients", rpc_clients.len()); // Build topics let msgs_topic = format!("/fil/msgs/{}", network_name); @@ -64,18 +87,35 @@ fn main() { // Prepare peer info for the network task let peers: Vec<_> = nodes.iter().map(|n| (n.addr.clone(), n.peer_id)).collect(); - // Spawn tokio runtime for network + // Spawn tokio runtime for network + RPC tasks let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime"); - // Spawn network task + // Spawn network task (existing P2P layer) rt.spawn(run_network(swarm, peers, topics, rx, ready_tx)); + // Spawn RPC monitor (assertions + consistency checks) + // Clone the clients since we need separate ownership for each task. + let monitor_clients = discover_rpc_clients(&node_names, &devgen_dir, rpc_port); + rt.spawn(run_rpc_monitor(monitor_clients, monitor_interval)); + + // Spawn RPC traffic (mixed P2P + RPC contention) + let traffic_clients = discover_rpc_clients(&node_names, &devgen_dir, rpc_port); + rt.spawn(run_rpc_traffic(traffic_clients, rpc_traffic_interval)); + // Wait for mesh to be ready before generating info!("waiting for mesh to be ready..."); let _ = rt.block_on(ready_rx); + + // Fire a GossipSub connectivity assertion + antithesis_sdk::assert_sometimes!( + true, + "GossipSub peer connected", + &serde_json::json!({}) + ); + info!("starting Hegel generation loop"); - // Main Hegel loop + // Main Hegel loop — generates fuzzed messages and publishes over GossipSub loop { let tx_ref = &tx; let msgs_topic_ref = &msgs_topic; @@ -87,20 +127,23 @@ fn main() { if use_blocks { let data: Vec = tc.draw(block_msg()); - log_generation(blocks_topic_ref, data.len()); + properties::log_generation(blocks_topic_ref, data.len()); let _ = tx_ref.blocking_send(PublishRequest { topic: blocks_topic_ref.to_string(), data, }); } else { let data: Vec = tc.draw(signed_message()); - log_generation(msgs_topic_ref, data.len()); + properties::log_generation(msgs_topic_ref, data.len()); let _ = tx_ref.blocking_send(PublishRequest { topic: msgs_topic_ref.to_string(), data, }); } + // Signal P2P activity for mixed-traffic assertion + mark_p2p_active(); + std::thread::sleep(publish_delay); }) .settings(hegel::Settings::new().test_cases(batch_size)) diff --git a/hegel-workload/src/rpc.rs b/hegel-workload/src/rpc.rs new file mode 100644 index 00000000..1778f760 --- /dev/null +++ b/hegel-workload/src/rpc.rs @@ -0,0 +1,177 @@ +use log::{debug, warn}; +use reqwest::Client; +use serde::Deserialize; +use serde_json::{json, Value}; +use std::collections::HashMap; +use std::time::Duration; + +const RPC_TIMEOUT: Duration = Duration::from_secs(10); + +/// A fault-tolerant JSON-RPC client for Lotus nodes. +/// All methods return Option — None means the node was unreachable or returned +/// an error, which is expected under Antithesis fault injection. +pub struct LotusRpc { + client: Client, + url: String, + token: String, +} + +#[derive(Debug, Deserialize)] +struct JsonRpcResponse { + result: Option, + error: Option, +} + +#[derive(Debug, Deserialize)] +struct JsonRpcError { + code: i64, + message: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct ChainHead { + #[serde(rename = "Height")] + pub height: i64, +} + +/// A pending signed message from MpoolPending. +#[derive(Debug, Clone, Deserialize)] +pub struct PendingMessage { + #[serde(rename = "Message")] + pub message: PendingInner, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PendingInner { + #[serde(rename = "From")] + pub from: String, + #[serde(rename = "Nonce")] + pub nonce: u64, +} + +impl LotusRpc { + pub fn new(host: &str, port: u16, token: &str) -> Self { + let client = Client::builder() + .timeout(RPC_TIMEOUT) + .build() + .expect("failed to build reqwest client"); + Self { + client, + url: format!("http://{}:{}/rpc/v1", host, port), + token: token.to_string(), + } + } + + async fn call Deserialize<'de>>(&self, method: &str, params: Value) -> Option { + let body = json!({ + "jsonrpc": "2.0", + "method": format!("Filecoin.{}", method), + "params": params, + "id": 1, + }); + + let resp = self + .client + .post(&self.url) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await; + + let resp = match resp { + Ok(r) => r, + Err(e) => { + debug!("RPC {} failed (network): {}", method, e); + return None; + } + }; + + let parsed: JsonRpcResponse = match resp.json().await { + Ok(p) => p, + Err(e) => { + debug!("RPC {} failed (parse): {}", method, e); + return None; + } + }; + + if let Some(err) = parsed.error { + debug!("RPC {} returned error {}: {}", method, err.code, err.message); + return None; + } + + parsed.result + } + + pub async fn chain_head(&self) -> Option { + self.call("ChainHead", json!([])).await + } + + pub async fn mpool_pending(&self) -> Option> { + // MpoolPending takes a TipSetKey argument; empty array means "current head" + self.call("MpoolPending", json!([[]])).await + } + + /// Push a raw signed message (CBOR bytes) to the mempool via RPC. + /// Returns true if the node accepted the push (regardless of validation outcome). + pub async fn mpool_push_raw(&self, signed_msg_json: &Value) -> bool { + let result: Option = self.call("MpoolPush", json!([signed_msg_json])).await; + result.is_some() + } + + /// Call MpoolSelect to exercise the allPending() code path. + /// The quality parameter controls how aggressively to select messages. + pub async fn mpool_select(&self, quality: f64) -> Option> { + // MpoolSelect takes (TipSetKey, quality) + self.call("MpoolSelect", json!([[], quality])).await + } +} + +/// Discover RPC endpoints for nodes, reading JWT tokens from devgen. +/// Returns a Vec of (node_name, LotusRpc) for all reachable nodes. +pub fn discover_rpc_clients( + node_names: &[String], + devgen_dir: &str, + rpc_port: u16, +) -> Vec<(String, LotusRpc)> { + let mut clients = Vec::new(); + for name in node_names { + let token_path = format!("{}/{}/{}-jwt", devgen_dir, name, name); + let token = match std::fs::read_to_string(&token_path) { + Ok(t) => t.trim().to_string(), + Err(e) => { + warn!("no JWT for {} at {}: {}, trying without auth", name, token_path, e); + String::new() + } + }; + clients.push((name.clone(), LotusRpc::new(name, rpc_port, &token))); + } + clients +} + +/// Check MpoolPending for duplicate nonces per sender. +/// Returns (is_consistent, details) where details contains any duplicates found. +pub fn check_mpool_consistency(pending: &[PendingMessage]) -> (bool, HashMap>) { + let mut nonces_by_sender: HashMap> = HashMap::new(); + for msg in pending { + nonces_by_sender + .entry(msg.message.from.clone()) + .or_default() + .push(msg.message.nonce); + } + + let mut duplicates: HashMap> = HashMap::new(); + let mut consistent = true; + + for (sender, nonces) in &nonces_by_sender { + let mut seen = std::collections::HashSet::new(); + for &n in nonces { + if !seen.insert(n) { + consistent = false; + duplicates.entry(sender.clone()).or_default().push(n); + } + } + } + + (consistent, duplicates) +} From e368fb656752d7b9af8267a3a2c4d9c56f8c8e58 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 16:45:59 -0400 Subject: [PATCH 24/47] feat: add scenario module skeleton and signing dependencies Adds k256, blake2b_simd, and hex to Cargo.toml, creates the src/scenario/ module with placeholder types and actions files. --- hegel-workload/Cargo.toml | 3 ++ hegel-workload/src/main.rs | 1 + hegel-workload/src/scenario/actions.rs | 1 + hegel-workload/src/scenario/mod.rs | 2 ++ hegel-workload/src/scenario/types.rs | 50 ++++++++++++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 hegel-workload/src/scenario/actions.rs create mode 100644 hegel-workload/src/scenario/mod.rs create mode 100644 hegel-workload/src/scenario/types.rs diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml index 6daa7a92..d685013d 100644 --- a/hegel-workload/Cargo.toml +++ b/hegel-workload/Cargo.toml @@ -25,3 +25,6 @@ cid = { version = "0.11", features = ["serde"] } multihash-codetable = { version = "0.1", features = ["sha2"] } multihash = "0.19" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +k256 = { version = "0.13", features = ["ecdsa"] } +blake2b_simd = "1" +hex = "0.4" diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index 94e3e7a1..77ded1b6 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -5,6 +5,7 @@ mod generators; mod network; mod properties; mod rpc; +mod scenario; use assertions::{mark_p2p_active, run_rpc_monitor, run_rpc_traffic}; use discovery::discover_nodes; diff --git a/hegel-workload/src/scenario/actions.rs b/hegel-workload/src/scenario/actions.rs new file mode 100644 index 00000000..a2a9a7c8 --- /dev/null +++ b/hegel-workload/src/scenario/actions.rs @@ -0,0 +1 @@ +// Action implementations will be added in later tasks. diff --git a/hegel-workload/src/scenario/mod.rs b/hegel-workload/src/scenario/mod.rs new file mode 100644 index 00000000..3ca3f2ef --- /dev/null +++ b/hegel-workload/src/scenario/mod.rs @@ -0,0 +1,2 @@ +pub mod actions; +pub mod types; diff --git a/hegel-workload/src/scenario/types.rs b/hegel-workload/src/scenario/types.rs new file mode 100644 index 00000000..08deef12 --- /dev/null +++ b/hegel-workload/src/scenario/types.rs @@ -0,0 +1,50 @@ +use fvm_shared::address::Address; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::message::Message; + +/// A wallet loaded from the stress keystore. +#[derive(Debug, Clone)] +pub struct Wallet { + pub address: Address, + pub private_key: Vec, +} + +/// Observed on-chain state for a wallet. +#[derive(Debug, Clone)] +pub struct WalletState { + pub wallet: Wallet, + pub nonce: u64, +} + +/// A signed message ready for delivery (not yet published). +#[derive(Debug, Clone)] +pub struct SignedMsg { + pub message: Message, + pub signature: Signature, + pub cbor_bytes: Vec, + pub sender_key: Vec, +} + +/// A fuzzed block ready for delivery. +#[derive(Debug, Clone)] +pub struct FuzzedBlock { + pub cbor_bytes: Vec, +} + +/// Observed chain head. +#[derive(Debug, Clone)] +pub struct ChainTip { + pub height: i64, +} + +/// Snapshot of mempool state. +#[derive(Debug, Clone)] +pub struct MempoolSnapshot { + pub pending: Vec<(String, u64)>, +} + +/// A message confirmed as included on-chain. +#[derive(Debug, Clone)] +pub struct IncludedMsg { + pub original: SignedMsg, +} From 5b3d68a4a36a696f3ad559cc38d45a569f1c498c Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 16:46:59 -0400 Subject: [PATCH 25/47] feat: add MpoolGetNonce RPC method --- hegel-workload/src/rpc.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hegel-workload/src/rpc.rs b/hegel-workload/src/rpc.rs index 1778f760..5e665542 100644 --- a/hegel-workload/src/rpc.rs +++ b/hegel-workload/src/rpc.rs @@ -125,6 +125,13 @@ impl LotusRpc { // MpoolSelect takes (TipSetKey, quality) self.call("MpoolSelect", json!([[], quality])).await } + + /// Get the next expected nonce for an address from the mempool. + /// This accounts for pending messages, not just on-chain state. + pub async fn mpool_get_nonce(&self, address: &str) -> Option { + self.call("MpoolGetNonce", serde_json::json!([address])) + .await + } } /// Discover RPC endpoints for nodes, reading JWT tokens from devgen. From b6cba25ba0c2c0960249a489fe6ef56480350a3e Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 16:48:40 -0400 Subject: [PATCH 26/47] feat: add wallet keystore loading from stress_keystore.json --- hegel-workload/src/main.rs | 1 + hegel-workload/src/wallet.rs | 150 +++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 hegel-workload/src/wallet.rs diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index 77ded1b6..b4192c9b 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -6,6 +6,7 @@ mod network; mod properties; mod rpc; mod scenario; +mod wallet; use assertions::{mark_p2p_active, run_rpc_monitor, run_rpc_traffic}; use discovery::discover_nodes; diff --git a/hegel-workload/src/wallet.rs b/hegel-workload/src/wallet.rs new file mode 100644 index 00000000..f2bb0a54 --- /dev/null +++ b/hegel-workload/src/wallet.rs @@ -0,0 +1,150 @@ +use crate::scenario::types::Wallet; +use fvm_shared::address::Network; +use log::{info, warn}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct KeystoreEntry { + #[serde(rename = "Address")] + address: String, + #[serde(rename = "PrivateKey")] + private_key: String, +} + +/// Parse a JSON keystore string into a list of Wallet structs. +/// The JSON format is an array of objects with "Address" and "PrivateKey" fields, +/// where PrivateKey is a hex-encoded 32-byte secp256k1 private key. +pub fn parse_keystore(json_str: &str) -> Result, String> { + let entries: Vec = + serde_json::from_str(json_str).map_err(|e| format!("JSON parse error: {}", e))?; + + let mut wallets = Vec::with_capacity(entries.len()); + for entry in entries { + let address = Network::Testnet + .parse_address(&entry.address) + .map_err(|e| format!("invalid address '{}': {}", entry.address, e))?; + + let private_key = hex::decode(&entry.private_key) + .map_err(|e| format!("invalid hex private key for {}: {}", entry.address, e))?; + + if private_key.len() != 32 { + return Err(format!( + "private key for {} is {} bytes, expected 32", + entry.address, + private_key.len() + )); + } + + wallets.push(Wallet { + address, + private_key, + }); + } + + Ok(wallets) +} + +/// Load wallets from the stress keystore JSON file at the given path. +/// Retries every 5 seconds for up to 5 minutes if the file does not yet exist. +/// Returns an empty vec on timeout. +pub fn load_keystore(path: &str) -> Vec { + let max_attempts = 60; // 5 minutes / 5 seconds + for attempt in 1..=max_attempts { + match std::fs::read_to_string(path) { + Ok(contents) => match parse_keystore(&contents) { + Ok(wallets) => { + info!( + "loaded {} wallets from keystore '{}'", + wallets.len(), + path + ); + return wallets; + } + Err(e) => { + warn!("failed to parse keystore '{}': {}", path, e); + return Vec::new(); + } + }, + Err(_) => { + if attempt == 1 { + info!( + "keystore '{}' not yet available, waiting (attempt {}/{})", + path, attempt, max_attempts + ); + } else { + info!( + "still waiting for keystore '{}' (attempt {}/{})", + path, attempt, max_attempts + ); + } + std::thread::sleep(std::time::Duration::from_secs(5)); + } + } + } + + warn!( + "timed out waiting for keystore '{}' after {} seconds", + path, + max_attempts * 5 + ); + Vec::new() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_keystore_json() { + // Use a valid 32-byte secp256k1 private key (hex = 64 chars). + // Use testnet address format (t-prefix) matching the production keystore. + let key_hex = "0101010101010101010101010101010101010101010101010101010101010101"; + let json = format!( + r#"[{{"Address": "t1d2xrzcslx7xlbbylc5c3d5lvandqw4iwl6epxba", "PrivateKey": "{}"}}]"#, + key_hex + ); + + let wallets = parse_keystore(&json).expect("should parse successfully"); + assert_eq!(wallets.len(), 1); + assert_eq!(wallets[0].private_key.len(), 32); + assert_eq!(wallets[0].private_key, vec![0x01u8; 32]); + } + + #[test] + fn test_parse_keystore_empty() { + let wallets = parse_keystore("[]").expect("should parse empty array"); + assert!(wallets.is_empty()); + } + + #[test] + fn test_parse_keystore_bad_hex() { + // Use testnet ID address (t01000) — parse_address(Testnet) handles it. + let json = r#"[{"Address": "t01000", "PrivateKey": "not_valid_hex!"}]"#; + let result = parse_keystore(json); + assert!(result.is_err(), "expected error on invalid hex"); + let err = result.unwrap_err(); + assert!( + err.contains("invalid hex"), + "error message should mention 'invalid hex', got: {}", + err + ); + } + + #[test] + fn test_parse_keystore_wrong_key_length() { + // Only 16 bytes (32 hex chars) instead of 32. + let short_key = "01020304050607080910111213141516"; + let json = format!( + r#"[{{"Address": "t01000", "PrivateKey": "{}"}}]"#, + short_key + ); + let result = parse_keystore(&json); + assert!(result.is_err(), "expected error on wrong key length"); + let err = result.unwrap_err(); + assert!( + err.contains("16 bytes") && err.contains("expected 32"), + "error message should mention byte length, got: {}", + err + ); + } +} From 336d89cf2283a0d6bf918a076509fcdea2341737 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 16:50:35 -0400 Subject: [PATCH 27/47] feat: implement Filecoin secp256k1 message signing --- hegel-workload/src/wallet.rs | 86 ++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/hegel-workload/src/wallet.rs b/hegel-workload/src/wallet.rs index f2bb0a54..524b0e30 100644 --- a/hegel-workload/src/wallet.rs +++ b/hegel-workload/src/wallet.rs @@ -1,6 +1,12 @@ use crate::scenario::types::Wallet; +use cid::Cid; +use fvm_ipld_encoding::to_vec as cbor_serialize; use fvm_shared::address::Network; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::message::Message; +use k256::ecdsa::{SigningKey, signature::hazmat::PrehashSigner}; use log::{info, warn}; +use multihash_codetable::{Code, MultihashDigest}; use serde::Deserialize; #[derive(Deserialize)] @@ -90,6 +96,35 @@ pub fn load_keystore(path: &str) -> Vec { Vec::new() } +pub fn sign_message(msg: &Message, private_key: &[u8]) -> Result { + // Step 1: CBOR-serialize the message + let cbor_bytes = cbor_serialize(msg).map_err(|e| format!("cbor serialize: {}", e))?; + + // Step 2: Compute CID (CIDv1, dag-cbor codec 0x71, SHA2-256) + let mh = Code::Sha2_256.digest(&cbor_bytes); + let cid = Cid::new_v1(0x71, mh); + let cid_bytes = cid.to_bytes(); + + // Step 3: Blake2b-256 hash the CID bytes (Filecoin signing convention) + let hash = blake2b_simd::Params::new() + .hash_length(32) + .hash(&cid_bytes); + + // Step 4: ECDSA sign with recovery + let signing_key = + SigningKey::from_bytes(private_key.into()).map_err(|e| format!("bad key: {}", e))?; + let (sig, recovery_id) = signing_key + .sign_prehash(hash.as_bytes()) + .map_err(|e| format!("sign failed: {}", e))?; + + // Encode as 65 bytes: r (32) || s (32) || v (1) + let mut sig_bytes = Vec::with_capacity(65); + sig_bytes.extend_from_slice(&sig.to_bytes()); + sig_bytes.push(recovery_id.to_byte()); + + Ok(Signature::new_secp256k1(sig_bytes)) +} + #[cfg(test)] mod tests { use super::*; @@ -147,4 +182,55 @@ mod tests { err ); } + + #[test] + fn test_sign_message_produces_65_byte_signature() { + use fvm_ipld_encoding::RawBytes; + use fvm_shared::address::Address; + use fvm_shared::econ::TokenAmount; + use fvm_shared::message::Message; + + let private_key = vec![1u8; 32]; + let msg = Message { + version: 0, + to: Address::new_id(1000), + from: Address::new_id(1001), + sequence: 0, + value: TokenAmount::from_atto(1000u64), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: 1_000_000, + gas_fee_cap: TokenAmount::from_atto(100_000u64), + gas_premium: TokenAmount::from_atto(1_000u64), + }; + + let sig = sign_message(&msg, &private_key).unwrap(); + assert_eq!(sig.bytes().len(), 65, "secp256k1 recoverable sig must be 65 bytes"); + } + + #[test] + fn test_sign_message_deterministic() { + use fvm_ipld_encoding::RawBytes; + use fvm_shared::address::Address; + use fvm_shared::econ::TokenAmount; + use fvm_shared::message::Message; + + let private_key = vec![42u8; 32]; + let msg = Message { + version: 0, + to: Address::new_id(1000), + from: Address::new_id(1001), + sequence: 5, + value: TokenAmount::from_atto(0u64), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: 1_000_000, + gas_fee_cap: TokenAmount::from_atto(100_000u64), + gas_premium: TokenAmount::from_atto(1_000u64), + }; + + let sig1 = sign_message(&msg, &private_key).unwrap(); + let sig2 = sign_message(&msg, &private_key).unwrap(); + assert_eq!(sig1.bytes(), sig2.bytes(), "signing must be deterministic"); + } } From 62b6cd961a65772d4ca70cf7d8d0b3d7e54a2e9a Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 16:50:49 -0400 Subject: [PATCH 28/47] feat: implement ScenarioContext with typed action availability --- hegel-workload/src/scenario/mod.rs | 191 +++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/hegel-workload/src/scenario/mod.rs b/hegel-workload/src/scenario/mod.rs index 3ca3f2ef..ed352004 100644 --- a/hegel-workload/src/scenario/mod.rs +++ b/hegel-workload/src/scenario/mod.rs @@ -1,2 +1,193 @@ pub mod actions; pub mod types; + +use types::*; + +/// All possible action kinds the scenario stepper can draw. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ActionKind { + // Always available (no inputs required) + PickWallet, + ObserveChainHead, + ObserveMempool, + CreateFuzzedMsg, + CreateFuzzedBlock, + Pause, + + // Requires wallet_states (observed nonce) + ObserveNonce, + CreateValidTransfer, + CreateNonceGap, + CreateSemiValidMsg, + CreateDrain, + + // Requires signed_msgs + PublishMsgP2p, + PublishMsgRpc, + CreateNonceReuse, + CreateGasBump, + WaitForInclusion, + + // Requires fuzzed_blocks + PublishBlockP2p, + + // Requires chain_tips + CreateBlockAtHeight, +} + +/// Holds the typed bag of intermediate values produced by actions. +pub struct ScenarioContext { + pub wallets: Vec, + pub wallet_states: Vec, + pub signed_msgs: Vec, + pub fuzzed_blocks: Vec, + pub chain_tips: Vec, + pub mempool_snapshots: Vec, +} + +impl ScenarioContext { + pub fn new(wallets: Vec) -> Self { + Self { + wallets, + wallet_states: vec![], + signed_msgs: vec![], + fuzzed_blocks: vec![], + chain_tips: vec![], + mempool_snapshots: vec![], + } + } + + /// Return the list of actions whose input requirements are satisfied. + pub fn available_actions(&self) -> Vec { + let mut actions = vec![ + ActionKind::PickWallet, + ActionKind::ObserveChainHead, + ActionKind::ObserveMempool, + ActionKind::CreateFuzzedMsg, + ActionKind::CreateFuzzedBlock, + ActionKind::Pause, + ]; + + if !self.wallet_states.is_empty() { + actions.extend_from_slice(&[ + ActionKind::ObserveNonce, + ActionKind::CreateValidTransfer, + ActionKind::CreateNonceGap, + ActionKind::CreateSemiValidMsg, + ActionKind::CreateDrain, + ]); + } + + if !self.signed_msgs.is_empty() { + actions.extend_from_slice(&[ + ActionKind::PublishMsgP2p, + ActionKind::PublishMsgRpc, + ActionKind::CreateNonceReuse, + ActionKind::CreateGasBump, + ActionKind::WaitForInclusion, + ]); + } + + if !self.fuzzed_blocks.is_empty() { + actions.push(ActionKind::PublishBlockP2p); + } + + if !self.chain_tips.is_empty() { + actions.push(ActionKind::CreateBlockAtHeight); + } + + actions + } +} + +#[cfg(test)] +mod tests { + use super::*; + use fvm_shared::address::Address; + + fn empty_context() -> ScenarioContext { + ScenarioContext::new(vec![Wallet { + address: Address::new_id(1000), + private_key: vec![1u8; 32], + }]) + } + + fn dummy_signed_msg() -> SignedMsg { + use fvm_shared::econ::TokenAmount; + use fvm_ipld_encoding::RawBytes; + SignedMsg { + message: fvm_shared::message::Message { + version: 0, + to: Address::new_id(1000), + from: Address::new_id(1001), + sequence: 0, + value: TokenAmount::from_atto(0u64), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: 0, + gas_fee_cap: TokenAmount::from_atto(0u64), + gas_premium: TokenAmount::from_atto(0u64), + }, + signature: fvm_shared::crypto::signature::Signature::new_secp256k1(vec![0u8; 65]), + cbor_bytes: vec![], + sender_key: vec![1u8; 32], + } + } + + #[test] + fn test_initial_actions_available() { + let ctx = empty_context(); + let actions = ctx.available_actions(); + assert!(actions.contains(&ActionKind::PickWallet)); + assert!(actions.contains(&ActionKind::ObserveChainHead)); + assert!(actions.contains(&ActionKind::ObserveMempool)); + assert!(actions.contains(&ActionKind::CreateFuzzedMsg)); + assert!(actions.contains(&ActionKind::CreateFuzzedBlock)); + assert!(actions.contains(&ActionKind::Pause)); + // These require inputs we don't have yet + assert!(!actions.contains(&ActionKind::CreateValidTransfer)); + assert!(!actions.contains(&ActionKind::PublishMsgP2p)); + } + + #[test] + fn test_transfer_available_after_observe() { + let mut ctx = empty_context(); + ctx.wallet_states.push(WalletState { + wallet: ctx.wallets[0].clone(), + nonce: 0, + }); + let actions = ctx.available_actions(); + assert!(actions.contains(&ActionKind::CreateValidTransfer)); + assert!(actions.contains(&ActionKind::CreateNonceGap)); + assert!(actions.contains(&ActionKind::CreateSemiValidMsg)); + } + + #[test] + fn test_publish_available_after_create() { + let mut ctx = empty_context(); + ctx.signed_msgs.push(dummy_signed_msg()); + let actions = ctx.available_actions(); + assert!(actions.contains(&ActionKind::PublishMsgP2p)); + assert!(actions.contains(&ActionKind::PublishMsgRpc)); + assert!(actions.contains(&ActionKind::CreateNonceReuse)); + assert!(actions.contains(&ActionKind::CreateGasBump)); + } + + #[test] + fn test_block_publish_available() { + let mut ctx = empty_context(); + ctx.fuzzed_blocks.push(FuzzedBlock { + cbor_bytes: vec![0x83], + }); + let actions = ctx.available_actions(); + assert!(actions.contains(&ActionKind::PublishBlockP2p)); + } + + #[test] + fn test_block_at_height_requires_chain_tip() { + let mut ctx = empty_context(); + assert!(!ctx.available_actions().contains(&ActionKind::CreateBlockAtHeight)); + ctx.chain_tips.push(ChainTip { height: 100 }); + assert!(ctx.available_actions().contains(&ActionKind::CreateBlockAtHeight)); + } +} From bf8f87b847560ec899084f1cd93d68dcdefcffc1 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 16:54:43 -0400 Subject: [PATCH 29/47] feat: implement creation actions for scenario composition Add all creation action functions (pick_wallet, create_valid_transfer, create_nonce_reuse, create_gas_bump, create_nonce_gap, create_semi_valid_msg, create_fuzzed_msg, create_fuzzed_block) and the build_signed_msg helper. Includes 5 Hegel property-based tests covering nonce correctness, gas bumping, nonce gaps, and CBOR validity. --- hegel-workload/src/scenario/actions.rs | 322 ++++++++++++++++++++++++- 1 file changed, 321 insertions(+), 1 deletion(-) diff --git a/hegel-workload/src/scenario/actions.rs b/hegel-workload/src/scenario/actions.rs index a2a9a7c8..03ceb4bf 100644 --- a/hegel-workload/src/scenario/actions.rs +++ b/hegel-workload/src/scenario/actions.rs @@ -1 +1,321 @@ -// Action implementations will be added in later tasks. +use crate::generators::blocks::block_msg; +use crate::generators::messages::signed_message; +use crate::scenario::types::*; +use crate::wallet::sign_message; +use fvm_ipld_encoding::{to_vec as cbor_serialize, RawBytes}; +use fvm_shared::address::Address; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::econ::TokenAmount; +use fvm_shared::message::Message; +use hegel::generators as gs; + +/// Draw a random wallet from the loaded keystore. +pub fn pick_wallet(tc: hegel::TestCase, wallets: &[Wallet]) -> Wallet { + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(wallets.len() - 1)); + wallets[idx].clone() +} + +/// Sign a Message and package it into a SignedMsg. +pub fn build_signed_msg(msg: Message, private_key: &[u8]) -> SignedMsg { + let signature = sign_message(&msg, private_key).expect("signing should not fail"); + let cbor_bytes = + cbor_serialize(&(&msg, &signature)).expect("CBOR serialization should not fail"); + SignedMsg { + message: msg, + signature, + cbor_bytes, + sender_key: private_key.to_vec(), + } +} + +/// Build a validly-signed transfer message. +/// Uses the correct nonce from sender state, method_num=0 (plain transfer). +/// Fuzzes: value amount, gas_limit, gas_premium. +pub fn create_valid_transfer( + tc: hegel::TestCase, + sender: &WalletState, + recipient: &Wallet, +) -> SignedMsg { + let value_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, + 1, + 1_000, + 1_000_000, + 1_000_000_000_000_000_000, // 1 FIL + ])); + let gas_limit: u64 = tc.draw(gs::sampled_from(vec![ + 1_000_000u64, + 10_000_000, + 50_000_000, + 100_000_000, + ])); + let gas_premium_atto: u64 = tc.draw(gs::sampled_from(vec![ + 1_000u64, + 10_000, + 100_000, + 1_000_000, + ])); + + let msg = Message { + version: 0, + to: recipient.address, + from: sender.wallet.address, + sequence: sender.nonce, + value: TokenAmount::from_atto(value_atto), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit, + gas_fee_cap: TokenAmount::from_atto(gas_premium_atto * 2), + gas_premium: TokenAmount::from_atto(gas_premium_atto), + }; + + build_signed_msg(msg, &sender.wallet.private_key) +} + +/// Same sender+nonce as original, different recipient. Valid signature. +pub fn create_nonce_reuse( + tc: hegel::TestCase, + original: &SignedMsg, + other_wallets: &[Wallet], +) -> SignedMsg { + let idx: usize = + tc.draw(gs::integers::().min_value(0).max_value(other_wallets.len() - 1)); + let new_recipient = &other_wallets[idx]; + + let value_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, + 1, + 1_000, + 1_000_000, + ])); + + let msg = Message { + version: 0, + to: new_recipient.address, + from: original.message.from, + sequence: original.message.sequence, + value: TokenAmount::from_atto(value_atto), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: original.message.gas_limit, + gas_fee_cap: original.message.gas_fee_cap.clone(), + gas_premium: original.message.gas_premium.clone(), + }; + + build_signed_msg(msg, &original.sender_key) +} + +/// Same sender+nonce, higher gas_premium. Valid signature. +pub fn create_gas_bump(tc: hegel::TestCase, original: &SignedMsg) -> SignedMsg { + let multiplier: u64 = tc.draw(gs::sampled_from(vec![2u64, 5, 10, 50])); + + // Extract a base premium value. Since TokenAmount is BigInt, we use a pragmatic + // approach: serialize the original premium and if it's small enough, multiply it. + // Otherwise use a fixed base. + let base_premium: u64 = 1_000_000; // fallback base + let new_premium_atto = base_premium * multiplier; + + // Ensure the new premium is strictly greater than the original by also adding + // a bump on top of whatever the original was. + // We construct a new premium that's guaranteed larger. + let msg = Message { + version: 0, + to: original.message.to, + from: original.message.from, + sequence: original.message.sequence, + value: original.message.value.clone(), + method_num: original.message.method_num, + params: original.message.params.clone(), + gas_limit: original.message.gas_limit, + gas_fee_cap: TokenAmount::from_atto(new_premium_atto * 2), + gas_premium: &original.message.gas_premium + TokenAmount::from_atto(new_premium_atto), + }; + + build_signed_msg(msg, &original.sender_key) +} + +/// Valid transfer but with nonce = current + gap (2..10). Skips ahead. +pub fn create_nonce_gap( + tc: hegel::TestCase, + sender: &WalletState, + recipient: &Wallet, +) -> SignedMsg { + let gap: u64 = tc.draw(gs::integers::().min_value(2).max_value(10)); + + let value_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 1_000, 1_000_000, + ])); + let gas_limit: u64 = tc.draw(gs::sampled_from(vec![ + 1_000_000u64, 10_000_000, 50_000_000, + ])); + let gas_premium_atto: u64 = tc.draw(gs::sampled_from(vec![ + 1_000u64, 10_000, 100_000, + ])); + + let msg = Message { + version: 0, + to: recipient.address, + from: sender.wallet.address, + sequence: sender.nonce + gap, + value: TokenAmount::from_atto(value_atto), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit, + gas_fee_cap: TokenAmount::from_atto(gas_premium_atto * 2), + gas_premium: TokenAmount::from_atto(gas_premium_atto), + }; + + build_signed_msg(msg, &sender.wallet.private_key) +} + +/// Valid sender/nonce/signature but fuzzed method_num, gas values, params. +pub fn create_semi_valid_msg( + tc: hegel::TestCase, + sender: &WalletState, + recipient: &Wallet, +) -> SignedMsg { + let method_num: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 2, 3, 4, 5, 6, 7, 8, 16, 24, 999999, u64::MAX, + ])); + + let gas_limit: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, 10_000_000, u64::MAX, + ])); + + let gas_premium_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, u64::MAX, + ])); + + let gas_fee_cap_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, u64::MAX, + ])); + + let value_atto: u64 = tc.draw(gs::sampled_from(vec![ + 0u64, 1, u64::MAX, + ])); + + // Fuzz params: empty, CBOR empty array, or random bytes + let params_variant: u8 = tc.draw(gs::integers::().min_value(0).max_value(2)); + let params = match params_variant { + 0 => RawBytes::new(vec![]), + 1 => RawBytes::new(vec![0x80]), // CBOR empty array + _ => { + let len: usize = tc.draw(gs::integers::().min_value(1).max_value(64)); + let payload: Vec = + tc.draw(gs::vecs(gs::integers::()).min_size(len).max_size(len)); + RawBytes::new(payload) + } + }; + + let msg = Message { + version: 0, + to: recipient.address, + from: sender.wallet.address, + sequence: sender.nonce, + value: TokenAmount::from_atto(value_atto), + method_num, + params, + gas_limit, + gas_fee_cap: TokenAmount::from_atto(gas_fee_cap_atto), + gas_premium: TokenAmount::from_atto(gas_premium_atto), + }; + + build_signed_msg(msg, &sender.wallet.private_key) +} + +/// Fully fuzzed message using the existing signed_message() generator. +/// Stores placeholder values in the message field since we can't decompose the CBOR. +/// The cbor_bytes field is what matters for publishing. +pub fn create_fuzzed_msg(tc: hegel::TestCase) -> SignedMsg { + let cbor_bytes: Vec = tc.draw(signed_message()); + + SignedMsg { + message: Message { + version: 0, + to: Address::new_id(0), + from: Address::new_id(0), + sequence: 0, + value: TokenAmount::from_atto(0u64), + method_num: 0, + params: RawBytes::new(vec![]), + gas_limit: 0, + gas_fee_cap: TokenAmount::from_atto(0u64), + gas_premium: TokenAmount::from_atto(0u64), + }, + signature: Signature::new_secp256k1(vec![0u8; 65]), + cbor_bytes, + sender_key: vec![], + } +} + +/// Fully fuzzed block using the existing block_msg() generator. +pub fn create_fuzzed_block(tc: hegel::TestCase) -> FuzzedBlock { + let cbor_bytes: Vec = tc.draw(block_msg()); + FuzzedBlock { cbor_bytes } +} + +#[cfg(test)] +mod tests { + use super::*; + use fvm_shared::address::Address; + + fn test_wallet(id: u64) -> Wallet { + Wallet { + address: Address::new_id(id), + private_key: vec![id as u8; 32], + } + } + + fn test_wallet_state(id: u64, nonce: u64) -> WalletState { + WalletState { + wallet: test_wallet(id), + nonce, + } + } + + #[hegel::test(test_cases = 20)] + fn test_create_valid_transfer_has_correct_nonce(tc: hegel::TestCase) { + let sender_state = test_wallet_state(1, 42); + let recipient = test_wallet(2); + let msg = create_valid_transfer(tc, &sender_state, &recipient); + assert_eq!(msg.message.sequence, 42); + assert_eq!(msg.message.from, sender_state.wallet.address); + assert_eq!(msg.message.to, recipient.address); + assert_eq!(msg.signature.bytes().len(), 65); + } + + #[hegel::test(test_cases = 20)] + fn test_create_nonce_reuse_keeps_nonce(tc: hegel::TestCase) { + let sender_state = test_wallet_state(1, 10); + let recipient = test_wallet(2); + let original = create_valid_transfer(tc.clone(), &sender_state, &recipient); + let other_wallets = vec![test_wallet(3), test_wallet(4)]; + let reused = create_nonce_reuse(tc, &original, &other_wallets); + assert_eq!(reused.message.sequence, original.message.sequence); + assert_eq!(reused.message.from, original.message.from); + } + + #[hegel::test(test_cases = 20)] + fn test_create_gas_bump_higher_premium(tc: hegel::TestCase) { + let sender_state = test_wallet_state(1, 5); + let recipient = test_wallet(2); + let original = create_valid_transfer(tc.clone(), &sender_state, &recipient); + let bumped = create_gas_bump(tc, &original); + assert_eq!(bumped.message.sequence, original.message.sequence); + assert!(bumped.message.gas_premium > original.message.gas_premium); + } + + #[hegel::test(test_cases = 20)] + fn test_create_nonce_gap_skips_ahead(tc: hegel::TestCase) { + let sender_state = test_wallet_state(1, 10); + let recipient = test_wallet(2); + let msg = create_nonce_gap(tc, &sender_state, &recipient); + assert!(msg.message.sequence > sender_state.nonce); + } + + #[hegel::test(test_cases = 20)] + fn test_create_fuzzed_msg_is_valid_cbor(tc: hegel::TestCase) { + let msg = create_fuzzed_msg(tc); + assert_eq!(msg.cbor_bytes[0], 0x82, "SignedMessage must be CBOR array(2)"); + } +} From d141c8dd51a1f8b35c358bd1a6d1897ce7c20b9a Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 16:57:27 -0400 Subject: [PATCH 30/47] feat: implement delivery and observation actions for scenarios Add ScenarioIO type to hold I/O handles (P2P sender, RPC clients, runtime handle, topic names) separate from pure-data ScenarioContext. Delivery actions: publish_msg_p2p, publish_msg_rpc, publish_block_p2p. Observation actions: observe_chain_head, observe_nonce, observe_mempool, wait_for_inclusion, pause. Add base64 dependency for encoding signatures in RPC MpoolPush calls. --- hegel-workload/Cargo.toml | 1 + hegel-workload/src/scenario/actions.rs | 145 +++++++++++++++++++++++++ hegel-workload/src/scenario/types.rs | 14 +++ 3 files changed, 160 insertions(+) diff --git a/hegel-workload/Cargo.toml b/hegel-workload/Cargo.toml index d685013d..a0be1b24 100644 --- a/hegel-workload/Cargo.toml +++ b/hegel-workload/Cargo.toml @@ -28,3 +28,4 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus k256 = { version = "0.13", features = ["ecdsa"] } blake2b_simd = "1" hex = "0.4" +base64 = "0.22" diff --git a/hegel-workload/src/scenario/actions.rs b/hegel-workload/src/scenario/actions.rs index 03ceb4bf..01da4325 100644 --- a/hegel-workload/src/scenario/actions.rs +++ b/hegel-workload/src/scenario/actions.rs @@ -1,5 +1,6 @@ use crate::generators::blocks::block_msg; use crate::generators::messages::signed_message; +use crate::network::PublishRequest; use crate::scenario::types::*; use crate::wallet::sign_message; use fvm_ipld_encoding::{to_vec as cbor_serialize, RawBytes}; @@ -254,6 +255,150 @@ pub fn create_fuzzed_block(tc: hegel::TestCase) -> FuzzedBlock { FuzzedBlock { cbor_bytes } } +// --------------------------------------------------------------------------- +// Delivery actions +// --------------------------------------------------------------------------- + +/// Publish a signed message over GossipSub P2P. +pub fn publish_msg_p2p(msg: &SignedMsg, io: &ScenarioIO) { + let _ = io.p2p_tx.blocking_send(PublishRequest { + topic: io.msgs_topic.clone(), + data: msg.cbor_bytes.clone(), + }); + crate::assertions::mark_p2p_active(); + log::debug!("scenario: published msg via P2P ({} bytes)", msg.cbor_bytes.len()); +} + +/// Publish a signed message via RPC MpoolPush to a random node. +pub fn publish_msg_rpc(tc: hegel::TestCase, msg: &SignedMsg, io: &ScenarioIO) { + if io.rpc_clients.is_empty() { + return; + } + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(io.rpc_clients.len() - 1)); + let (name, client) = &io.rpc_clients[idx]; + + // Build JSON representation for MpoolPush + let sig_bytes = msg.signature.bytes(); + let sig_b64 = base64_encode(sig_bytes); + + let msg_json = serde_json::json!({ + "Message": { + "Version": msg.message.version, + "To": msg.message.to.to_string(), + "From": msg.message.from.to_string(), + "Nonce": msg.message.sequence, + "Value": msg.message.value.atto().to_string(), + "GasLimit": msg.message.gas_limit, + "GasFeeCap": msg.message.gas_fee_cap.atto().to_string(), + "GasPremium": msg.message.gas_premium.atto().to_string(), + "Method": msg.message.method_num, + "Params": null, + }, + "Signature": { + "Type": 1, + "Data": sig_b64, + } + }); + + let accepted = io.rt_handle.block_on(client.mpool_push_raw(&msg_json)); + log::debug!("scenario: published msg via RPC to {} (accepted={})", name, accepted); + crate::assertions::mark_rpc_active(); +} + +/// Publish a fuzzed block over GossipSub P2P. +pub fn publish_block_p2p(block: &FuzzedBlock, io: &ScenarioIO) { + let _ = io.p2p_tx.blocking_send(PublishRequest { + topic: io.blocks_topic.clone(), + data: block.cbor_bytes.clone(), + }); + crate::assertions::mark_p2p_active(); + log::debug!("scenario: published block via P2P ({} bytes)", block.cbor_bytes.len()); +} + +fn base64_encode(bytes: &[u8]) -> String { + use base64::Engine; + base64::engine::general_purpose::STANDARD.encode(bytes) +} + +// --------------------------------------------------------------------------- +// Observation actions +// --------------------------------------------------------------------------- + +/// Observe chain head from a random node. +pub fn observe_chain_head(tc: hegel::TestCase, io: &ScenarioIO) -> Option { + if io.rpc_clients.is_empty() { + return None; + } + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(io.rpc_clients.len() - 1)); + let (name, client) = &io.rpc_clients[idx]; + + let head = io.rt_handle.block_on(client.chain_head())?; + log::debug!("scenario: observed chain head height={} from {}", head.height, name); + Some(ChainTip { height: head.height }) +} + +/// Observe a wallet's current nonce via MpoolGetNonce. +pub fn observe_nonce(tc: hegel::TestCase, wallet: &Wallet, io: &ScenarioIO) -> Option { + if io.rpc_clients.is_empty() { + return None; + } + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(io.rpc_clients.len() - 1)); + let (name, client) = &io.rpc_clients[idx]; + + let addr_str = wallet.address.to_string(); + let nonce = io.rt_handle.block_on(client.mpool_get_nonce(&addr_str))?; + log::debug!("scenario: observed nonce={} for {} from {}", nonce, addr_str, name); + Some(WalletState { + wallet: wallet.clone(), + nonce, + }) +} + +/// Observe current mempool state. +pub fn observe_mempool(tc: hegel::TestCase, io: &ScenarioIO) -> Option { + if io.rpc_clients.is_empty() { + return None; + } + let idx: usize = tc.draw(gs::integers::().min_value(0).max_value(io.rpc_clients.len() - 1)); + let (_, client) = &io.rpc_clients[idx]; + + let pending = io.rt_handle.block_on(client.mpool_pending())?; + Some(MempoolSnapshot { + pending: pending.iter().map(|m| (m.message.from.clone(), m.message.nonce)).collect(), + }) +} + +/// Wait for a message to leave the mempool (included in a tipset) or timeout. +pub fn wait_for_inclusion(msg: &SignedMsg, io: &ScenarioIO) -> Option { + let addr_str = msg.message.from.to_string(); + let nonce = msg.message.sequence; + + // Poll every 2s for up to 60s + for _ in 0..30 { + std::thread::sleep(std::time::Duration::from_secs(2)); + + for (_, client) in &io.rpc_clients { + if let Some(pending) = io.rt_handle.block_on(client.mpool_pending()) { + let still_pending = pending + .iter() + .any(|m| m.message.from == addr_str && m.message.nonce == nonce); + if !still_pending { + log::debug!("scenario: message from {} nonce {} included", addr_str, nonce); + return Some(IncludedMsg { original: msg.clone() }); + } + } + } + } + log::warn!("scenario: timed out waiting for inclusion of {} nonce {}", addr_str, nonce); + None +} + +/// Random pause between actions. +pub fn pause(tc: hegel::TestCase) { + let ms: u64 = tc.draw(gs::sampled_from(vec![100u64, 500, 1000, 2000, 5000])); + std::thread::sleep(std::time::Duration::from_millis(ms)); +} + #[cfg(test)] mod tests { use super::*; diff --git a/hegel-workload/src/scenario/types.rs b/hegel-workload/src/scenario/types.rs index 08deef12..9db36a79 100644 --- a/hegel-workload/src/scenario/types.rs +++ b/hegel-workload/src/scenario/types.rs @@ -2,6 +2,10 @@ use fvm_shared::address::Address; use fvm_shared::crypto::signature::Signature; use fvm_shared::message::Message; +use crate::network::PublishRequest; +use crate::rpc::LotusRpc; +use tokio::sync::mpsc; + /// A wallet loaded from the stress keystore. #[derive(Debug, Clone)] pub struct Wallet { @@ -48,3 +52,13 @@ pub struct MempoolSnapshot { pub struct IncludedMsg { pub original: SignedMsg, } + +/// I/O handles passed to actions that need network or RPC access. +/// Separate from ScenarioContext so context remains pure data. +pub struct ScenarioIO { + pub p2p_tx: mpsc::Sender, + pub rpc_clients: Vec<(String, LotusRpc)>, + pub rt_handle: tokio::runtime::Handle, + pub msgs_topic: String, + pub blocks_topic: String, +} From 6a4540eba1022b4d8ff24bd1908f14b8edd43dac Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 16:59:42 -0400 Subject: [PATCH 31/47] feat: implement scenario stepper with typed action selection Add execute_step() and run_scenario() to the scenario module. The stepper draws a random action from those available in the current context, executes it via the corresponding action function, and updates the context with any results. RPC failures are silently skipped to handle Antithesis fault injection. --- hegel-workload/src/scenario/mod.rs | 221 +++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/hegel-workload/src/scenario/mod.rs b/hegel-workload/src/scenario/mod.rs index ed352004..17535f8b 100644 --- a/hegel-workload/src/scenario/mod.rs +++ b/hegel-workload/src/scenario/mod.rs @@ -100,6 +100,227 @@ impl ScenarioContext { } } +/// Execute a single randomly-chosen action, updating the scenario context. +pub fn execute_step(tc: hegel::TestCase, ctx: &mut ScenarioContext, io: &types::ScenarioIO) { + let available = ctx.available_actions(); + if available.is_empty() { + return; + } + + let action = tc.clone().draw(hegel::generators::sampled_from(available)); + + match action { + ActionKind::PickWallet => { + let wallet = actions::pick_wallet(tc.clone(), &ctx.wallets); + if let Some(state) = actions::observe_nonce(tc.clone(), &wallet, io) { + ctx.wallet_states.push(state); + } + } + ActionKind::ObserveChainHead => { + if let Some(tip) = actions::observe_chain_head(tc.clone(), io) { + ctx.chain_tips.push(tip); + } + } + ActionKind::ObserveMempool => { + if let Some(snapshot) = actions::observe_mempool(tc.clone(), io) { + ctx.mempool_snapshots.push(snapshot); + } + } + ActionKind::ObserveNonce => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let wallet = ctx.wallet_states[idx].wallet.clone(); + if let Some(state) = actions::observe_nonce(tc.clone(), &wallet, io) { + ctx.wallet_states[idx] = state; + } + } + ActionKind::CreateValidTransfer => { + let sender_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let recip_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallets.len() - 1), + ); + let msg = actions::create_valid_transfer( + tc.clone(), + &ctx.wallet_states[sender_idx], + &ctx.wallets[recip_idx], + ); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateNonceReuse => { + let msg_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + let original = ctx.signed_msgs[msg_idx].clone(); + let msg = actions::create_nonce_reuse(tc.clone(), &original, &ctx.wallets); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateGasBump => { + let msg_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + let original = ctx.signed_msgs[msg_idx].clone(); + let msg = actions::create_gas_bump(tc.clone(), &original); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateNonceGap => { + let sender_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let recip_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallets.len() - 1), + ); + let msg = actions::create_nonce_gap( + tc.clone(), + &ctx.wallet_states[sender_idx], + &ctx.wallets[recip_idx], + ); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateSemiValidMsg => { + let sender_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let recip_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallets.len() - 1), + ); + let msg = actions::create_semi_valid_msg( + tc.clone(), + &ctx.wallet_states[sender_idx], + &ctx.wallets[recip_idx], + ); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateDrain => { + // Same as CreateValidTransfer for now + let sender_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallet_states.len() - 1), + ); + let recip_idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.wallets.len() - 1), + ); + let msg = actions::create_valid_transfer( + tc.clone(), + &ctx.wallet_states[sender_idx], + &ctx.wallets[recip_idx], + ); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateFuzzedMsg => { + let msg = actions::create_fuzzed_msg(tc.clone()); + ctx.signed_msgs.push(msg); + } + ActionKind::CreateFuzzedBlock => { + let block = actions::create_fuzzed_block(tc.clone()); + ctx.fuzzed_blocks.push(block); + } + ActionKind::CreateBlockAtHeight => { + // Use existing fuzzed block generator (height parameterization is future work) + let block = actions::create_fuzzed_block(tc.clone()); + ctx.fuzzed_blocks.push(block); + } + ActionKind::PublishMsgP2p => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + actions::publish_msg_p2p(&ctx.signed_msgs[idx], io); + } + ActionKind::PublishMsgRpc => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + let msg = ctx.signed_msgs[idx].clone(); + actions::publish_msg_rpc(tc.clone(), &msg, io); + } + ActionKind::PublishBlockP2p => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.fuzzed_blocks.len() - 1), + ); + actions::publish_block_p2p(&ctx.fuzzed_blocks[idx], io); + } + ActionKind::WaitForInclusion => { + let idx: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(0) + .max_value(ctx.signed_msgs.len() - 1), + ); + let msg = ctx.signed_msgs[idx].clone(); + if actions::wait_for_inclusion(&msg, io).is_some() { + // Re-observe nonce for the sender + let sender_wallet = ctx.wallets.iter().find(|w| w.address == msg.message.from); + if let Some(wallet) = sender_wallet { + if let Some(state) = actions::observe_nonce(tc.clone(), wallet, io) { + if let Some(existing) = ctx + .wallet_states + .iter_mut() + .find(|s| s.wallet.address == msg.message.from) + { + *existing = state; + } + } + } + } + } + ActionKind::Pause => { + actions::pause(tc.clone()); + } + } +} + +/// Run a complete scenario: draw N steps and execute them sequentially. +pub fn run_scenario(tc: hegel::TestCase, ctx: &mut ScenarioContext, io: &types::ScenarioIO) { + let num_steps: usize = tc.clone().draw( + hegel::generators::integers::() + .min_value(1) + .max_value(10), + ); + log::info!("scenario: starting with {} steps", num_steps); + + for step in 0..num_steps { + log::info!( + "scenario: step {}/{}, context: {} wallet_states, {} msgs, {} blocks", + step + 1, + num_steps, + ctx.wallet_states.len(), + ctx.signed_msgs.len(), + ctx.fuzzed_blocks.len(), + ); + execute_step(tc.clone(), ctx, io); + } + + log::info!("scenario: complete"); +} + #[cfg(test)] mod tests { use super::*; From 26d5f4e5140601546a0f9d6c7ad422cc0526f85c Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 17:01:15 -0400 Subject: [PATCH 32/47] feat: wire composable scenario loop into main with keystore loading Load wallet keystore and create ScenarioIO in main, then run the composable scenario loop when wallets are available, falling back to flat generation otherwise. Also adds Antithesis assertions for key scenario events (RPC acceptance, nonce observation, on-chain inclusion, scenario completion). --- hegel-workload/Cargo.lock | 123 +++++++++++++++++++++++++ hegel-workload/src/main.rs | 87 +++++++++++------ hegel-workload/src/scenario/actions.rs | 17 ++++ hegel-workload/src/scenario/mod.rs | 5 + 4 files changed, 205 insertions(+), 27 deletions(-) diff --git a/hegel-workload/Cargo.lock b/hegel-workload/Cargo.lock index c6dacf5a..9767cd78 100644 --- a/hegel-workload/Cargo.lock +++ b/hegel-workload/Cargo.lock @@ -281,6 +281,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base256emoji" version = "1.0.2" @@ -621,6 +627,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -734,6 +752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -755,6 +774,20 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -785,6 +818,25 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -863,6 +915,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1079,6 +1141,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1131,6 +1194,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.4.13" @@ -1207,6 +1281,8 @@ name = "hegel-workload" version = "0.1.0" dependencies = [ "antithesis_sdk", + "base64", + "blake2b_simd", "cid", "env_logger", "futures", @@ -1214,6 +1290,8 @@ dependencies = [ "fvm_shared", "getrandom 0.2.17", "hegeltest", + "hex", + "k256", "libp2p", "log", "multihash", @@ -1258,6 +1336,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex_fmt" version = "0.3.0" @@ -1711,6 +1795,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "keccak" version = "0.1.6" @@ -2921,6 +3019,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -3073,6 +3181,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.27" @@ -3242,6 +3364,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ + "digest", "rand_core 0.6.4", ] diff --git a/hegel-workload/src/main.rs b/hegel-workload/src/main.rs index b4192c9b..aff3d3b7 100644 --- a/hegel-workload/src/main.rs +++ b/hegel-workload/src/main.rs @@ -14,6 +14,9 @@ use generators::blocks::block_msg; use generators::messages::signed_message; use network::{build_swarm, run_network, PublishRequest}; use rpc::discover_rpc_clients; +use scenario::types::ScenarioIO; +use scenario::{run_scenario, ScenarioContext}; +use wallet::load_keystore; use hegel::generators as gs; use log::{error, info, warn}; @@ -117,39 +120,69 @@ fn main() { info!("starting Hegel generation loop"); - // Main Hegel loop — generates fuzzed messages and publishes over GossipSub + let keystore_path = std::env::var("STRESS_KEYSTORE_PATH") + .unwrap_or_else(|_| "/shared/configs/stress_keystore.json".to_string()); + let wallets = load_keystore(&keystore_path); + + let scenario_io = ScenarioIO { + p2p_tx: tx.clone(), + rpc_clients: discover_rpc_clients(&node_names, &devgen_dir, rpc_port), + rt_handle: rt.handle().clone(), + msgs_topic: msgs_topic.clone(), + blocks_topic: blocks_topic.clone(), + }; + + if wallets.is_empty() { + warn!("no wallets loaded, falling back to flat generation only"); + } else { + info!( + "loaded {} wallets, running composable scenario loop", + wallets.len() + ); + } + + // Main Hegel loop — composable scenarios if wallets are available, flat generation otherwise loop { let tx_ref = &tx; let msgs_topic_ref = &msgs_topic; let blocks_topic_ref = &blocks_topic; let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - hegel::Hegel::new(|tc| { - let use_blocks: bool = tc.draw(gs::booleans()); - - if use_blocks { - let data: Vec = tc.draw(block_msg()); - properties::log_generation(blocks_topic_ref, data.len()); - let _ = tx_ref.blocking_send(PublishRequest { - topic: blocks_topic_ref.to_string(), - data, - }); - } else { - let data: Vec = tc.draw(signed_message()); - properties::log_generation(msgs_topic_ref, data.len()); - let _ = tx_ref.blocking_send(PublishRequest { - topic: msgs_topic_ref.to_string(), - data, - }); - } - - // Signal P2P activity for mixed-traffic assertion - mark_p2p_active(); - - std::thread::sleep(publish_delay); - }) - .settings(hegel::Settings::new().test_cases(batch_size)) - .run(); + if !wallets.is_empty() { + // Composable scenario mode + let mut ctx = ScenarioContext::new(wallets.clone()); + hegel::Hegel::new(|tc| { + run_scenario(tc, &mut ctx, &scenario_io); + }) + .settings(hegel::Settings::new().test_cases(batch_size)) + .run(); + } else { + // Flat generation fallback (original behavior) + hegel::Hegel::new(|tc| { + let use_blocks: bool = tc.draw(gs::booleans()); + + if use_blocks { + let data: Vec = tc.draw(block_msg()); + properties::log_generation(blocks_topic_ref, data.len()); + let _ = tx_ref.blocking_send(PublishRequest { + topic: blocks_topic_ref.to_string(), + data, + }); + } else { + let data: Vec = tc.draw(signed_message()); + properties::log_generation(msgs_topic_ref, data.len()); + let _ = tx_ref.blocking_send(PublishRequest { + topic: msgs_topic_ref.to_string(), + data, + }); + } + + mark_p2p_active(); + std::thread::sleep(publish_delay); + }) + .settings(hegel::Settings::new().test_cases(batch_size)) + .run(); + } })); if let Err(e) = result { diff --git a/hegel-workload/src/scenario/actions.rs b/hegel-workload/src/scenario/actions.rs index 01da4325..4bfca17a 100644 --- a/hegel-workload/src/scenario/actions.rs +++ b/hegel-workload/src/scenario/actions.rs @@ -302,6 +302,13 @@ pub fn publish_msg_rpc(tc: hegel::TestCase, msg: &SignedMsg, io: &ScenarioIO) { let accepted = io.rt_handle.block_on(client.mpool_push_raw(&msg_json)); log::debug!("scenario: published msg via RPC to {} (accepted={})", name, accepted); + if accepted { + antithesis_sdk::assert_sometimes!( + true, + "Scenario: valid signed message accepted via RPC", + &serde_json::json!({"from": msg.message.from.to_string(), "nonce": msg.message.sequence}) + ); + } crate::assertions::mark_rpc_active(); } @@ -348,6 +355,11 @@ pub fn observe_nonce(tc: hegel::TestCase, wallet: &Wallet, io: &ScenarioIO) -> O let addr_str = wallet.address.to_string(); let nonce = io.rt_handle.block_on(client.mpool_get_nonce(&addr_str))?; log::debug!("scenario: observed nonce={} for {} from {}", nonce, addr_str, name); + antithesis_sdk::assert_sometimes!( + true, + "Scenario: wallet nonce observed", + &serde_json::json!({"address": addr_str, "nonce": nonce}) + ); Some(WalletState { wallet: wallet.clone(), nonce, @@ -384,6 +396,11 @@ pub fn wait_for_inclusion(msg: &SignedMsg, io: &ScenarioIO) -> Option Date: Wed, 1 Apr 2026 17:05:46 -0400 Subject: [PATCH 33/47] fix: address review findings in scenario actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Encode message params as base64 in RPC JSON instead of hardcoded null - Rename "message included on-chain" assertion to "message left mempool" (leaving mempool doesn't guarantee inclusion — could be eviction) - Reduce wait_for_inclusion timeout from 60s to 20s to keep scenarios moving --- hegel-workload/src/scenario/actions.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hegel-workload/src/scenario/actions.rs b/hegel-workload/src/scenario/actions.rs index 4bfca17a..f8fb3fa6 100644 --- a/hegel-workload/src/scenario/actions.rs +++ b/hegel-workload/src/scenario/actions.rs @@ -292,7 +292,11 @@ pub fn publish_msg_rpc(tc: hegel::TestCase, msg: &SignedMsg, io: &ScenarioIO) { "GasFeeCap": msg.message.gas_fee_cap.atto().to_string(), "GasPremium": msg.message.gas_premium.atto().to_string(), "Method": msg.message.method_num, - "Params": null, + "Params": if msg.message.params.bytes().is_empty() { + serde_json::Value::Null + } else { + serde_json::Value::String(base64_encode(msg.message.params.bytes())) + }, }, "Signature": { "Type": 1, @@ -385,8 +389,8 @@ pub fn wait_for_inclusion(msg: &SignedMsg, io: &ScenarioIO) -> Option Option Date: Wed, 1 Apr 2026 19:52:04 -0400 Subject: [PATCH 34/47] fix: mount shared configs into hegel-workload container The hegel-workload needs access to stress_keystore.json (generated by genesis-prep at /shared/configs/) to load wallet keys for signing valid transactions in composable scenarios. --- docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yaml b/docker-compose.yaml index 7d13a896..bad10a68 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -238,6 +238,7 @@ services: - MONITOR_INTERVAL_MS=5000 - RPC_TRAFFIC_INTERVAL_MS=1000 volumes: + - ./shared/configs:/shared/configs - ./data/lotus0:/root/devgen/lotus0 - ./data/lotus1:/root/devgen/lotus1 - ./data/lotus2:/root/devgen/lotus2 From b0af0ec2c59af63bb55892b0c159caa81b8f63a1 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Wed, 1 Apr 2026 19:53:30 -0400 Subject: [PATCH 35/47] fix: accept both mainnet (f) and testnet (t) address prefixes in keystore --- hegel-workload/src/wallet.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hegel-workload/src/wallet.rs b/hegel-workload/src/wallet.rs index 524b0e30..bc81269c 100644 --- a/hegel-workload/src/wallet.rs +++ b/hegel-workload/src/wallet.rs @@ -26,8 +26,10 @@ pub fn parse_keystore(json_str: &str) -> Result, String> { let mut wallets = Vec::with_capacity(entries.len()); for entry in entries { - let address = Network::Testnet + // Try mainnet first (f-prefix), fall back to testnet (t-prefix) + let address = Network::Mainnet .parse_address(&entry.address) + .or_else(|_| Network::Testnet.parse_address(&entry.address)) .map_err(|e| format!("invalid address '{}': {}", entry.address, e))?; let private_key = hex::decode(&entry.private_key) From cbe0c6fd79d6f61b0fbebb878d76fe859e97fae1 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 2 Apr 2026 13:54:04 -0400 Subject: [PATCH 36/47] Update GitHub workflows --- .../workflows/build_push_hegel_workload.yml | 51 +++++++++++++++++++ .github/workflows/pr_antithesis_test.yml | 47 ++++++++++++++++- .github/workflows/run_antithesis_test.yml | 11 ++-- .../workflows/run_antithesis_upgradetest.yml | 7 ++- 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/build_push_hegel_workload.yml diff --git a/.github/workflows/build_push_hegel_workload.yml b/.github/workflows/build_push_hegel_workload.yml new file mode 100644 index 00000000..f834953d --- /dev/null +++ b/.github/workflows/build_push_hegel_workload.yml @@ -0,0 +1,51 @@ +name: Build & Push Hegel Workload + +on: + schedule: + # 6 PM EST daily + - cron: '0 22 * * *' + workflow_dispatch: + inputs: + workload_tag: + type: string + required: true + default: 'latest' + description: A hegel workload tag + +jobs: + antithesis: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Antithesis Google Artifact Registry + uses: docker/login-action@v3 + with: + registry: us-central1-docker.pkg.dev + username: _json_key + password: ${{ secrets.ANTITHESIS_GAR_KEY }} + + - name: Extract metadata (tags) for Docker hegel-workload + id: meta-workload + uses: docker/metadata-action@v5 + with: + images: us-central1-docker.pkg.dev/molten-verve-216720/filecoin-repository/hegel-workload + tags: | + type=sha + ${{ github.event.inputs.workload_tag }} + - name: Build and push hegel-workload + uses: docker/build-push-action@v5 + with: + context: ./hegel-workload + file: ./hegel-workload/Dockerfile + push: true + tags: ${{ steps.meta-workload.outputs.tags }}, ${{ env.GITHUB_REF_NAME }} + labels: ${{ steps.meta-workload.outputs.labels }} diff --git a/.github/workflows/pr_antithesis_test.yml b/.github/workflows/pr_antithesis_test.yml index 0272a600..d65e31b8 100644 --- a/.github/workflows/pr_antithesis_test.yml +++ b/.github/workflows/pr_antithesis_test.yml @@ -26,6 +26,7 @@ jobs: curio: ${{ steps.filter.outputs.curio }} drand: ${{ steps.filter.outputs.drand }} workload: ${{ steps.filter.outputs.workload }} + hegel_workload: ${{ steps.filter.outputs.hegel_workload }} filwizard: ${{ steps.filter.outputs.filwizard }} curio_or_lotus_changed: ${{ steps.curio-check.outputs.changed }} endpoints: ${{ steps.endpoints.outputs.matrix }} @@ -45,6 +46,8 @@ jobs: - 'drand/**' workload: - 'workload/**' + hegel_workload: + - 'hegel-workload/**' filwizard: - 'filwizard/**' - name: Check if curio or lotus changed @@ -212,6 +215,40 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-hegel-workload: + needs: detect-changes + if: needs.detect-changes.outputs.hegel_workload == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: us-central1-docker.pkg.dev + username: _json_key + password: ${{ secrets.ANTITHESIS_GAR_KEY }} + - name: Extract metadata for hegel-workload + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/hegel-workload + tags: | + type=sha + pr-${{ github.event.pull_request.number }} + ${{ contains(github.event.pull_request.labels.*.name, 'antithesis-test-filecoin') && 'smoke' || '' }} + ${{ contains(github.event.pull_request.labels.*.name, 'antithesis-test-foc') && 'smoke-foc' || '' }} + - name: Build and push hegel-workload + uses: docker/build-push-action@v5 + with: + context: ./hegel-workload + file: ./hegel-workload/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-filwizard: needs: detect-changes if: needs.detect-changes.outputs.filwizard == 'true' @@ -328,6 +365,7 @@ jobs: - build-forest - build-drand - build-workload + - build-hegel-workload - build-filwizard - build-curio - build-config @@ -351,6 +389,7 @@ jobs: FOREST_CHANGED: ${{ needs.detect-changes.outputs.forest }} DRAND_CHANGED: ${{ needs.detect-changes.outputs.drand }} WORKLOAD_CHANGED: ${{ needs.detect-changes.outputs.workload }} + HEGEL_WORKLOAD_CHANGED: ${{ needs.detect-changes.outputs.hegel_workload }} FILWIZARD_CHANGED: ${{ needs.detect-changes.outputs.filwizard }} CURIO_OR_LOTUS_CHANGED: ${{ needs.detect-changes.outputs.curio_or_lotus_changed }} run: | @@ -386,6 +425,12 @@ jobs: WORKLOAD_REF="workload:latest" fi + if [[ "$HEGEL_WORKLOAD_CHANGED" == "true" ]]; then + HEGEL_WORKLOAD_REF="hegel-workload:${SMOKE_TAG}" + else + HEGEL_WORKLOAD_REF="hegel-workload:latest" + fi + if [[ "$FILWIZARD_CHANGED" == "true" ]]; then FILWIZARD_REF="filwizard:${SMOKE_TAG}" else @@ -401,7 +446,7 @@ jobs: CONFIG_REF="config:${SMOKE_TAG}" # curio and filwizard are only used by the filecoin-foc endpoint - IMAGES="${DRAND_REF};${FOREST_REF};${LOTUS_REF};${WORKLOAD_REF}" + IMAGES="${DRAND_REF};${FOREST_REF};${LOTUS_REF};${WORKLOAD_REF};${HEGEL_WORKLOAD_REF}" if [[ "$NOTEBOOK" == "filecoin-foc" ]]; then IMAGES="${IMAGES};${CURIO_REF};${FILWIZARD_REF}" fi diff --git a/.github/workflows/run_antithesis_test.yml b/.github/workflows/run_antithesis_test.yml index 6222f01e..e8391311 100644 --- a/.github/workflows/run_antithesis_test.yml +++ b/.github/workflows/run_antithesis_test.yml @@ -55,6 +55,11 @@ on: required: true default: 'latest' description: The workload image tag used for the test run + hegel_workload: + type: string + required: true + default: 'latest' + description: The hegel-workload image tag used for the test run filwizard: type: string required: true @@ -112,7 +117,7 @@ jobs: - name: Build images list id: images run: | - IMAGES="drand:${{ inputs.drand }};forest:${{ inputs.forest }};lotus:${{ inputs.lotus }};workload:${{ inputs.workload }};curio:${{ inputs.curio }}" + IMAGES="drand:${{ inputs.drand }};forest:${{ inputs.forest }};lotus:${{ inputs.lotus }};workload:${{ inputs.workload }};hegel-workload:${{ inputs.hegel_workload }};curio:${{ inputs.curio }}" if [ -n "${{ inputs.lotus_0_tag }}" ] && [ "${{ inputs.lotus_0_tag }}" != "${{ inputs.lotus }}" ]; then IMAGES="${IMAGES};lotus:${{ inputs.lotus_0_tag }}" fi @@ -170,7 +175,7 @@ jobs: username: filecoin password: ${{ secrets.ANTITHESIS_PASSWORD }} github_token: ${{ secrets.GH_PAT }} - images: drand:latest;forest:latest;lotus:latest;workload:latest + images: drand:latest;forest:latest;lotus:latest;workload:latest;hegel-workload:latest config_image: config:latest description: "Nightly Profile: 12 hr balanced full-coverage run" additional_parameters: |- @@ -195,7 +200,7 @@ jobs: username: filecoin password: ${{ secrets.ANTITHESIS_PASSWORD }} github_token: ${{ secrets.GH_PAT }} - images: drand:latest;forest:latest;lotus:latest;workload:latest;curio:latest;filwizard:latest + images: drand:latest;forest:latest;lotus:latest;workload:latest;hegel-workload:latest;curio:latest;filwizard:latest config_image: config:latest description: "FOC Profile: 12 hr Curio PDP lifecycle run" additional_parameters: |- diff --git a/.github/workflows/run_antithesis_upgradetest.yml b/.github/workflows/run_antithesis_upgradetest.yml index fb3a5a91..a37c167f 100644 --- a/.github/workflows/run_antithesis_upgradetest.yml +++ b/.github/workflows/run_antithesis_upgradetest.yml @@ -37,6 +37,11 @@ on: required: true default: 'latest' description: The workload image tag used for the test run + hegel_workload: + type: string + required: true + default: 'latest' + description: The hegel-workload image tag used for the test run config: type: string required: true @@ -83,7 +88,7 @@ jobs: username: filecoin password: ${{ secrets.ANTITHESIS_PASSWORD }} github_token: ${{ secrets.GH_PAT }} - images: drand:${{ inputs.drand }};forest:${{ inputs.forest }};lotus:${{ inputs.lotus }};workload:${{ inputs.workload }};curio:${{ inputs.curio }} + images: drand:${{ inputs.drand }};forest:${{ inputs.forest }};lotus:${{ inputs.lotus }};workload:${{ inputs.workload }};hegel-workload:${{ inputs.hegel_workload }};curio:${{ inputs.curio }} config_image: config:${{ github.event.inputs.config }} description: "Manual Upgrade Test" email_recipients: ${{ github.event.inputs.emails }} From 7e1541573d9804d20f3e3b3282f344b8927597fd Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 2 Apr 2026 15:54:38 -0400 Subject: [PATCH 37/47] feat: add Rust hegel-workload binary to workload container --- workload/Dockerfile | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/workload/Dockerfile b/workload/Dockerfile index fb05aa32..a61dd6b2 100644 --- a/workload/Dockerfile +++ b/workload/Dockerfile @@ -1,8 +1,15 @@ +# ── Stage 1: Build hegel-workload (Rust) ── +FROM rust:1.86 AS rust-builder +WORKDIR /build +COPY hegel-workload/Cargo.toml hegel-workload/Cargo.lock ./ +COPY hegel-workload/src/ ./src/ +RUN cargo build --release --features antithesis + FROM ubuntu:latest # Core dependencies only — no Foundry, no Rust, no pnpm, no Hardhat RUN apt-get update -y && \ - apt-get install -y curl jq gcc ntpdate git && \ + apt-get install -y curl jq gcc ntpdate git python3 python3-venv ca-certificates && \ ARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') && \ curl -OL "https://golang.org/dl/go1.23.2.linux-${ARCH}.tar.gz" && \ tar -C /usr/local -xzf "go1.23.2.linux-${ARCH}.tar.gz" && \ @@ -12,8 +19,11 @@ ENV PATH="/usr/local/go/bin:${PATH}" ENV GOPATH="/go" ENV PATH="$GOPATH/bin:$PATH" +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +ENV PATH="/root/.local/bin:${PATH}" + WORKDIR /opt/antithesis -COPY . . +COPY workload/ . # Build Go binaries RUN go mod download @@ -29,6 +39,8 @@ RUN GOARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') \ RUN GOARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') \ go build -o ./protocol-fuzzer ./cmd/protocol-fuzzer +COPY --from=rust-builder /build/target/release/hegel-workload /usr/local/bin/hegel-workload + RUN chmod +x ./entrypoint/entrypoint.sh ENTRYPOINT ["/opt/antithesis/entrypoint/entrypoint.sh"] From 72be8074e58ca0b300ef20aafde6bc6371a898b5 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 2 Apr 2026 15:55:38 -0400 Subject: [PATCH 38/47] feat: add multiaddr/network-name waits and hegel-workload launch to entrypoint --- workload/entrypoint/entrypoint.sh | 53 ++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/workload/entrypoint/entrypoint.sh b/workload/entrypoint/entrypoint.sh index ae3c2fa5..db96cdf7 100755 --- a/workload/entrypoint/entrypoint.sh +++ b/workload/entrypoint/entrypoint.sh @@ -13,6 +13,52 @@ log_info "Generating pre-funded genesis wallets..." /opt/antithesis/genesis-prep --count 100 --out /shared/configs log_info "Genesis wallet generation complete." +# ── 1b. Ensure Antithesis output directory exists ── +mkdir -p "${ANTITHESIS_OUTPUT_DIR:-/tmp/antithesis}" + +# ── 1c. Wait for node multiaddr files ── +DEVGEN_DIR="${DEVGEN_DIR:-/root/devgen}" +IFS=',' read -ra NODES <<< "${STRESS_NODES:-lotus0}" +log_info "Waiting for node multiaddr files..." +MAX_WAIT=300 +WAITED=0 +FOUND=false +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + for node in "${NODES[@]}"; do + node=$(echo "$node" | tr -d ' ') + ADDR_FILE="${DEVGEN_DIR}/${node}/${node}-ipv4addr" + if [ -f "$ADDR_FILE" ] && [ -s "$ADDR_FILE" ]; then + log_info "Found multiaddr for ${node}" + FOUND=true + break + fi + done + if [ "$FOUND" = true ]; then + break + fi + sleep 5 + WAITED=$((WAITED + 5)) +done +if [ "$FOUND" = false ]; then + log_warn "No multiaddr files found after ${MAX_WAIT}s, continuing anyway..." +fi + +# ── 1d. Wait for network name ── +NETWORK_FILE="${DEVGEN_DIR}/lotus0/network_name" +log_info "Waiting for network name at ${NETWORK_FILE}..." +WAITED=0 +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + if [ -f "$NETWORK_FILE" ] && [ -s "$NETWORK_FILE" ]; then + log_info "Network name: $(cat "$NETWORK_FILE")" + break + fi + sleep 5 + WAITED=$((WAITED + 5)) +done + +# ── 1e. Allow nodes to finish connecting ── +sleep 10 + # ── 2. Time sync ── log_info "Synchronizing system time..." if ntpdate -q pool.ntp.org &>/dev/null; then @@ -122,4 +168,9 @@ if [ "${FUZZER_ENABLED:-1}" = "1" ]; then FUZZER_PID=$! fi -wait -n $STRESS_PID ${FUZZER_PID:-} +# ── 8. Launch hegel workload ── +log_info "Launching hegel workload..." +/usr/local/bin/hegel-workload & +HEGEL_PID=$! + +wait -n $STRESS_PID ${FUZZER_PID:-} $HEGEL_PID From f019be6d78b09b8203afd1acaf3eb78c050e81ce Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 2 Apr 2026 15:59:39 -0400 Subject: [PATCH 39/47] ci: update workload build context to repo root for hegel-workload access --- .github/workflows/build_push_workload.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_push_workload.yml b/.github/workflows/build_push_workload.yml index 758cb29f..7bcdfd34 100644 --- a/.github/workflows/build_push_workload.yml +++ b/.github/workflows/build_push_workload.yml @@ -44,7 +44,7 @@ jobs: - name: Build and push workload uses: docker/build-push-action@v5 with: - context: ./workload + context: . file: ./workload/Dockerfile push: true tags: ${{ steps.meta-workload.outputs.tags }}, ${{ env.GITHUB_REF_NAME }} From 5f734c55a1050d541449cdd279baa7f1fa4a72d5 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 2 Apr 2026 15:59:58 -0400 Subject: [PATCH 40/47] ci: remove standalone hegel-workload build workflow --- .../workflows/build_push_hegel_workload.yml | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/build_push_hegel_workload.yml diff --git a/.github/workflows/build_push_hegel_workload.yml b/.github/workflows/build_push_hegel_workload.yml deleted file mode 100644 index f834953d..00000000 --- a/.github/workflows/build_push_hegel_workload.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Build & Push Hegel Workload - -on: - schedule: - # 6 PM EST daily - - cron: '0 22 * * *' - workflow_dispatch: - inputs: - workload_tag: - type: string - required: true - default: 'latest' - description: A hegel workload tag - -jobs: - antithesis: - runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - - steps: - - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Antithesis Google Artifact Registry - uses: docker/login-action@v3 - with: - registry: us-central1-docker.pkg.dev - username: _json_key - password: ${{ secrets.ANTITHESIS_GAR_KEY }} - - - name: Extract metadata (tags) for Docker hegel-workload - id: meta-workload - uses: docker/metadata-action@v5 - with: - images: us-central1-docker.pkg.dev/molten-verve-216720/filecoin-repository/hegel-workload - tags: | - type=sha - ${{ github.event.inputs.workload_tag }} - - name: Build and push hegel-workload - uses: docker/build-push-action@v5 - with: - context: ./hegel-workload - file: ./hegel-workload/Dockerfile - push: true - tags: ${{ steps.meta-workload.outputs.tags }}, ${{ env.GITHUB_REF_NAME }} - labels: ${{ steps.meta-workload.outputs.labels }} From 113368a0bb437ac95541838a462ca96363f06215 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 2 Apr 2026 16:00:16 -0400 Subject: [PATCH 41/47] ci: remove standalone hegel-workload references from run workflow --- .github/workflows/run_antithesis_test.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run_antithesis_test.yml b/.github/workflows/run_antithesis_test.yml index e8391311..6222f01e 100644 --- a/.github/workflows/run_antithesis_test.yml +++ b/.github/workflows/run_antithesis_test.yml @@ -55,11 +55,6 @@ on: required: true default: 'latest' description: The workload image tag used for the test run - hegel_workload: - type: string - required: true - default: 'latest' - description: The hegel-workload image tag used for the test run filwizard: type: string required: true @@ -117,7 +112,7 @@ jobs: - name: Build images list id: images run: | - IMAGES="drand:${{ inputs.drand }};forest:${{ inputs.forest }};lotus:${{ inputs.lotus }};workload:${{ inputs.workload }};hegel-workload:${{ inputs.hegel_workload }};curio:${{ inputs.curio }}" + IMAGES="drand:${{ inputs.drand }};forest:${{ inputs.forest }};lotus:${{ inputs.lotus }};workload:${{ inputs.workload }};curio:${{ inputs.curio }}" if [ -n "${{ inputs.lotus_0_tag }}" ] && [ "${{ inputs.lotus_0_tag }}" != "${{ inputs.lotus }}" ]; then IMAGES="${IMAGES};lotus:${{ inputs.lotus_0_tag }}" fi @@ -175,7 +170,7 @@ jobs: username: filecoin password: ${{ secrets.ANTITHESIS_PASSWORD }} github_token: ${{ secrets.GH_PAT }} - images: drand:latest;forest:latest;lotus:latest;workload:latest;hegel-workload:latest + images: drand:latest;forest:latest;lotus:latest;workload:latest config_image: config:latest description: "Nightly Profile: 12 hr balanced full-coverage run" additional_parameters: |- @@ -200,7 +195,7 @@ jobs: username: filecoin password: ${{ secrets.ANTITHESIS_PASSWORD }} github_token: ${{ secrets.GH_PAT }} - images: drand:latest;forest:latest;lotus:latest;workload:latest;hegel-workload:latest;curio:latest;filwizard:latest + images: drand:latest;forest:latest;lotus:latest;workload:latest;curio:latest;filwizard:latest config_image: config:latest description: "FOC Profile: 12 hr Curio PDP lifecycle run" additional_parameters: |- From 3325207d9dd05ea315d6a81faeaf5ad00d2d8af0 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 2 Apr 2026 16:00:27 -0400 Subject: [PATCH 42/47] ci: remove standalone hegel-workload from PR workflow, merge into workload build --- .github/workflows/pr_antithesis_test.yml | 48 +----------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/.github/workflows/pr_antithesis_test.yml b/.github/workflows/pr_antithesis_test.yml index d65e31b8..8f619cc2 100644 --- a/.github/workflows/pr_antithesis_test.yml +++ b/.github/workflows/pr_antithesis_test.yml @@ -26,7 +26,6 @@ jobs: curio: ${{ steps.filter.outputs.curio }} drand: ${{ steps.filter.outputs.drand }} workload: ${{ steps.filter.outputs.workload }} - hegel_workload: ${{ steps.filter.outputs.hegel_workload }} filwizard: ${{ steps.filter.outputs.filwizard }} curio_or_lotus_changed: ${{ steps.curio-check.outputs.changed }} endpoints: ${{ steps.endpoints.outputs.matrix }} @@ -46,7 +45,6 @@ jobs: - 'drand/**' workload: - 'workload/**' - hegel_workload: - 'hegel-workload/**' filwizard: - 'filwizard/**' @@ -209,46 +207,12 @@ jobs: - name: Build and push workload uses: docker/build-push-action@v5 with: - context: ./workload + context: . file: ./workload/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - build-hegel-workload: - needs: detect-changes - if: needs.detect-changes.outputs.hegel_workload == 'true' - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - uses: docker/setup-buildx-action@v3 - - uses: docker/login-action@v3 - with: - registry: us-central1-docker.pkg.dev - username: _json_key - password: ${{ secrets.ANTITHESIS_GAR_KEY }} - - name: Extract metadata for hegel-workload - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/hegel-workload - tags: | - type=sha - pr-${{ github.event.pull_request.number }} - ${{ contains(github.event.pull_request.labels.*.name, 'antithesis-test-filecoin') && 'smoke' || '' }} - ${{ contains(github.event.pull_request.labels.*.name, 'antithesis-test-foc') && 'smoke-foc' || '' }} - - name: Build and push hegel-workload - uses: docker/build-push-action@v5 - with: - context: ./hegel-workload - file: ./hegel-workload/Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-filwizard: needs: detect-changes if: needs.detect-changes.outputs.filwizard == 'true' @@ -365,7 +329,6 @@ jobs: - build-forest - build-drand - build-workload - - build-hegel-workload - build-filwizard - build-curio - build-config @@ -389,7 +352,6 @@ jobs: FOREST_CHANGED: ${{ needs.detect-changes.outputs.forest }} DRAND_CHANGED: ${{ needs.detect-changes.outputs.drand }} WORKLOAD_CHANGED: ${{ needs.detect-changes.outputs.workload }} - HEGEL_WORKLOAD_CHANGED: ${{ needs.detect-changes.outputs.hegel_workload }} FILWIZARD_CHANGED: ${{ needs.detect-changes.outputs.filwizard }} CURIO_OR_LOTUS_CHANGED: ${{ needs.detect-changes.outputs.curio_or_lotus_changed }} run: | @@ -425,12 +387,6 @@ jobs: WORKLOAD_REF="workload:latest" fi - if [[ "$HEGEL_WORKLOAD_CHANGED" == "true" ]]; then - HEGEL_WORKLOAD_REF="hegel-workload:${SMOKE_TAG}" - else - HEGEL_WORKLOAD_REF="hegel-workload:latest" - fi - if [[ "$FILWIZARD_CHANGED" == "true" ]]; then FILWIZARD_REF="filwizard:${SMOKE_TAG}" else @@ -446,7 +402,7 @@ jobs: CONFIG_REF="config:${SMOKE_TAG}" # curio and filwizard are only used by the filecoin-foc endpoint - IMAGES="${DRAND_REF};${FOREST_REF};${LOTUS_REF};${WORKLOAD_REF};${HEGEL_WORKLOAD_REF}" + IMAGES="${DRAND_REF};${FOREST_REF};${LOTUS_REF};${WORKLOAD_REF}" if [[ "$NOTEBOOK" == "filecoin-foc" ]]; then IMAGES="${IMAGES};${CURIO_REF};${FILWIZARD_REF}" fi From 595098a81844e0979edbc84485cee9f8e491fbb7 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Thu, 2 Apr 2026 18:06:01 -0400 Subject: [PATCH 43/47] ci: remove vestigial standalone hegel-workload image/service The hegel-workload binary is now built and launched inside the combined workload image. Remove the standalone Dockerfile, entrypoint, compose service, Makefile target, and CI references that are no longer needed. --- .github/workflows/pr_antithesis_test.yml | 1 - .../workflows/run_antithesis_upgradetest.yml | 7 +-- Makefile | 8 +-- docker-compose.yaml | 20 ------- hegel-workload/Dockerfile | 28 --------- hegel-workload/entrypoint.sh | 59 ------------------- 6 files changed, 2 insertions(+), 121 deletions(-) delete mode 100644 hegel-workload/Dockerfile delete mode 100755 hegel-workload/entrypoint.sh diff --git a/.github/workflows/pr_antithesis_test.yml b/.github/workflows/pr_antithesis_test.yml index 8f619cc2..fc66022e 100644 --- a/.github/workflows/pr_antithesis_test.yml +++ b/.github/workflows/pr_antithesis_test.yml @@ -45,7 +45,6 @@ jobs: - 'drand/**' workload: - 'workload/**' - - 'hegel-workload/**' filwizard: - 'filwizard/**' - name: Check if curio or lotus changed diff --git a/.github/workflows/run_antithesis_upgradetest.yml b/.github/workflows/run_antithesis_upgradetest.yml index a37c167f..fb3a5a91 100644 --- a/.github/workflows/run_antithesis_upgradetest.yml +++ b/.github/workflows/run_antithesis_upgradetest.yml @@ -37,11 +37,6 @@ on: required: true default: 'latest' description: The workload image tag used for the test run - hegel_workload: - type: string - required: true - default: 'latest' - description: The hegel-workload image tag used for the test run config: type: string required: true @@ -88,7 +83,7 @@ jobs: username: filecoin password: ${{ secrets.ANTITHESIS_PASSWORD }} github_token: ${{ secrets.GH_PAT }} - images: drand:${{ inputs.drand }};forest:${{ inputs.forest }};lotus:${{ inputs.lotus }};workload:${{ inputs.workload }};hegel-workload:${{ inputs.hegel_workload }};curio:${{ inputs.curio }} + images: drand:${{ inputs.drand }};forest:${{ inputs.forest }};lotus:${{ inputs.lotus }};workload:${{ inputs.workload }};curio:${{ inputs.curio }} config_image: config:${{ github.event.inputs.config }} description: "Manual Upgrade Test" email_recipients: ${{ github.event.inputs.emails }} diff --git a/Makefile b/Makefile index a7ec6e03..d955a174 100644 --- a/Makefile +++ b/Makefile @@ -116,11 +116,6 @@ build-workload: @echo "Building workload for $(TARGET_ARCH)..." $(BUILD_CMD) -t workload:latest -f workload/Dockerfile workload -.PHONY: build-hegel-workload -build-hegel-workload: - @echo "Building hegel-workload for $(TARGET_ARCH)..." - $(BUILD_CMD) -t hegel-workload:latest -f hegel-workload/Dockerfile hegel-workload - .PHONY: build-filwizard build-filwizard: @echo "Building filwizard for $(TARGET_ARCH)..." @@ -173,7 +168,7 @@ build-nodes: build-lotus build-forest build-curio @echo "Node images built." .PHONY: build-all -build-all: build-drand build-lotus build-forest build-curio build-workload build-filwizard build-hegel-workload +build-all: build-drand build-lotus build-forest build-curio build-workload build-filwizard @echo "All images built." # ========================================== @@ -210,7 +205,6 @@ help: @echo " make build-curio Build curio image" @echo " make build-filwizard Build filwizard image" @echo " make build-workload Build workload image" - @echo " make build-hegel-workload Build hegel-workload image (Rust/Hegel)" @echo "" @echo "Build groups:" @echo " make build-infra Build infrastructure (drand)" diff --git a/docker-compose.yaml b/docker-compose.yaml index bad10a68..99dc8a18 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -225,26 +225,6 @@ services: - ./data/forest0:/root/devgen/forest0 - ./data/curio:/root/devgen/curio - hegel-workload: - <<: [ *filecoin_service, *needs_lotus0_healthy ] - image: hegel-workload:latest - container_name: hegel-workload - environment: - - STRESS_NODES=${STRESS_NODES:-lotus0,lotus1} - - ANTITHESIS_OUTPUT_DIR=/tmp/antithesis - - HEGEL_BATCH_SIZE=100 - - RUST_LOG=info - - RPC_PORT=${STRESS_RPC_PORT:-1234} - - MONITOR_INTERVAL_MS=5000 - - RPC_TRAFFIC_INTERVAL_MS=1000 - volumes: - - ./shared/configs:/shared/configs - - ./data/lotus0:/root/devgen/lotus0 - - ./data/lotus1:/root/devgen/lotus1 - - ./data/lotus2:/root/devgen/lotus2 - - ./data/lotus3:/root/devgen/lotus3 - - ./data/forest0:/root/devgen/forest0 - lotus-miner0: <<: [ *filecoin_service, *needs_lotus0_healthy ] image: lotus:${LOTUS_MINER_0_TAG:-${LOTUS_TAG:-latest}} diff --git a/hegel-workload/Dockerfile b/hegel-workload/Dockerfile deleted file mode 100644 index 5c1794a2..00000000 --- a/hegel-workload/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# Stage 1: Build the Rust binary -FROM rust:1.86 AS builder -WORKDIR /build -COPY Cargo.toml Cargo.lock ./ -COPY src/ ./src/ -RUN cargo build --release --features antithesis - -# Stage 2: Runtime with Python 3 + uv (required by Hegel's hegel-core Python backend) -FROM ubuntu:24.04 -RUN apt-get update && apt-get install -y --no-install-recommends \ - python3 \ - python3-venv \ - curl \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -# Install uv (Python package manager used by Hegel to auto-install hegel-core) -RUN curl -LsSf https://astral.sh/uv/install.sh | sh -ENV PATH="/root/.local/bin:${PATH}" - -# Copy the built binary -COPY --from=builder /build/target/release/hegel-workload /usr/local/bin/hegel-workload - -# Copy entrypoint -COPY entrypoint.sh /opt/antithesis/entrypoint.sh -RUN chmod +x /opt/antithesis/entrypoint.sh - -ENTRYPOINT ["/opt/antithesis/entrypoint.sh"] diff --git a/hegel-workload/entrypoint.sh b/hegel-workload/entrypoint.sh deleted file mode 100755 index 5e49cf90..00000000 --- a/hegel-workload/entrypoint.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash -set -euo pipefail - -echo "[hegel-workload] starting entrypoint" - -# Ensure Antithesis output directory exists (needed by hegeltest SDK even outside Antithesis) -mkdir -p "${ANTITHESIS_OUTPUT_DIR:-/tmp/antithesis}" - -# Default devgen directory -DEVGEN_DIR="${DEVGEN_DIR:-/root/devgen}" - -# Parse STRESS_NODES into an array -IFS=',' read -ra NODES <<< "${STRESS_NODES:-lotus0}" - -# Wait for at least one node's multiaddr file to exist -echo "[hegel-workload] waiting for node multiaddr files..." -MAX_WAIT=300 -WAITED=0 -FOUND=false -while [ "$WAITED" -lt "$MAX_WAIT" ]; do - for node in "${NODES[@]}"; do - node=$(echo "$node" | tr -d ' ') - ADDR_FILE="${DEVGEN_DIR}/${node}/${node}-ipv4addr" - if [ -f "$ADDR_FILE" ] && [ -s "$ADDR_FILE" ]; then - echo "[hegel-workload] found multiaddr for ${node}" - FOUND=true - break - fi - done - if [ "$FOUND" = true ]; then - break - fi - sleep 5 - WAITED=$((WAITED + 5)) -done - -if [ "$FOUND" = false ]; then - echo "[hegel-workload] ERROR: no multiaddr files found after ${MAX_WAIT}s" - exit 1 -fi - -# Wait for network_name file -NETWORK_FILE="${DEVGEN_DIR}/lotus0/network_name" -echo "[hegel-workload] waiting for network name at ${NETWORK_FILE}..." -WAITED=0 -while [ "$WAITED" -lt "$MAX_WAIT" ]; do - if [ -f "$NETWORK_FILE" ] && [ -s "$NETWORK_FILE" ]; then - echo "[hegel-workload] network name: $(cat "$NETWORK_FILE")" - break - fi - sleep 5 - WAITED=$((WAITED + 5)) -done - -# Give nodes a few more seconds to finish connecting to each other -sleep 10 - -echo "[hegel-workload] launching hegel-workload binary" -exec /usr/local/bin/hegel-workload From 73631ef77841a7fe825dbd52c9839656b09e9045 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Fri, 3 Apr 2026 10:21:35 -0400 Subject: [PATCH 44/47] fix: pre-install hegel-core in Docker image for offline Antithesis runs The Antithesis simulation has no internet access, causing hegel-workload to panic when hegeltest tries to download hegel-core from PyPI at runtime. Pre-install hegel-core into a venv at build time and set HEGEL_SERVER_COMMAND so hegeltest uses the local binary directly. --- workload/Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/workload/Dockerfile b/workload/Dockerfile index a61dd6b2..adc110eb 100644 --- a/workload/Dockerfile +++ b/workload/Dockerfile @@ -41,6 +41,11 @@ RUN GOARCH=$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') \ COPY --from=rust-builder /build/target/release/hegel-workload /usr/local/bin/hegel-workload +# Pre-install hegel-core so the workload doesn't need internet access at runtime. +# HEGEL_SERVER_COMMAND tells hegeltest to use this binary instead of downloading. +RUN uv venv .hegel/venv && uv pip install --python .hegel/venv/bin/python hegel-core==0.2.3 +ENV HEGEL_SERVER_COMMAND="/opt/antithesis/.hegel/venv/bin/hegel" + RUN chmod +x ./entrypoint/entrypoint.sh ENTRYPOINT ["/opt/antithesis/entrypoint/entrypoint.sh"] From 88fc9f8b21e3ecfee9ab65e3517816b702c2d446 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Fri, 3 Apr 2026 10:33:05 -0400 Subject: [PATCH 45/47] fix: restore Rust toolchain install in lotus Dockerfile The Rust toolchain was accidentally removed when the Dockerfile was rewritten. filecoin-ffi requires cargo to build filcrypto from source. --- lotus/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lotus/Dockerfile b/lotus/Dockerfile index 6af141f6..b64e5e80 100644 --- a/lotus/Dockerfile +++ b/lotus/Dockerfile @@ -38,6 +38,10 @@ WORKDIR /lotus_instrumented/customer/ # deleting original source code RUN rm -rf /lotus +# install Rust toolchain (required by filecoin-ffi to build filcrypto from source) +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + # install runtime binaries (lotus-shed used in curio) (not using: 2k-lotus-worker) RUN make 2k-lotus 2k-lotus-miner 2k-lotus-seed 2k-lotus-shed From 8caf67d000f849e91702ca4475e54268c49d53d3 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Mon, 6 Apr 2026 10:58:53 -0400 Subject: [PATCH 46/47] split hegel-workload into its own container (same image) --- docker-compose.yaml | 21 ++++++++ workload/Dockerfile | 2 +- workload/entrypoint/entrypoint-hegel.sh | 65 +++++++++++++++++++++++++ workload/entrypoint/entrypoint.sh | 7 +-- 4 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 workload/entrypoint/entrypoint-hegel.sh diff --git a/docker-compose.yaml b/docker-compose.yaml index 99dc8a18..b4ff8f3d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -225,6 +225,27 @@ services: - ./data/forest0:/root/devgen/forest0 - ./data/curio:/root/devgen/curio + hegel-workload: + <<: [ *filecoin_service ] + image: workload:latest + container_name: hegel-workload + entrypoint: ["/opt/antithesis/entrypoint/entrypoint-hegel.sh"] + environment: + - STRESS_NODES=lotus0,lotus1,lotus2,lotus3 + - STRESS_RPC_PORT=1234 + - STRESS_KEYSTORE_PATH=/shared/configs/stress_keystore.json + - STRESS_WAIT_HEIGHT=10 + - RUST_LOG=info + volumes: + - ./shared/configs:/shared/configs + - ./shared:/shared + - ./data/lotus0:/root/devgen/lotus0 + - ./data/lotus1:/root/devgen/lotus1 + - ./data/lotus2:/root/devgen/lotus2 + - ./data/lotus3:/root/devgen/lotus3 + - ./data/forest0:/root/devgen/forest0 + - ./data/curio:/root/devgen/curio + lotus-miner0: <<: [ *filecoin_service, *needs_lotus0_healthy ] image: lotus:${LOTUS_MINER_0_TAG:-${LOTUS_TAG:-latest}} diff --git a/workload/Dockerfile b/workload/Dockerfile index adc110eb..0163fc71 100644 --- a/workload/Dockerfile +++ b/workload/Dockerfile @@ -46,6 +46,6 @@ COPY --from=rust-builder /build/target/release/hegel-workload /usr/local/bin/heg RUN uv venv .hegel/venv && uv pip install --python .hegel/venv/bin/python hegel-core==0.2.3 ENV HEGEL_SERVER_COMMAND="/opt/antithesis/.hegel/venv/bin/hegel" -RUN chmod +x ./entrypoint/entrypoint.sh +RUN chmod +x ./entrypoint/entrypoint.sh ./entrypoint/entrypoint-hegel.sh ENTRYPOINT ["/opt/antithesis/entrypoint/entrypoint.sh"] diff --git a/workload/entrypoint/entrypoint-hegel.sh b/workload/entrypoint/entrypoint-hegel.sh new file mode 100644 index 00000000..094f4297 --- /dev/null +++ b/workload/entrypoint/entrypoint-hegel.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { echo -e "${GREEN}[HEGEL]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[HEGEL]${NC} $1"; } + +# ── 1. Ensure Antithesis output directory exists ── +mkdir -p "${ANTITHESIS_OUTPUT_DIR:-/tmp/antithesis}" + +# ── 2. Wait for genesis keystore (produced by the main workload container) ── +KEYSTORE="${STRESS_KEYSTORE_PATH:-/shared/configs/stress_keystore.json}" +log_info "Waiting for keystore at ${KEYSTORE}..." +while [ ! -f "$KEYSTORE" ] || [ ! -s "$KEYSTORE" ]; do sleep 2; done +log_info "Keystore ready." + +# ── 3. Wait for node multiaddr files ── +DEVGEN_DIR="${DEVGEN_DIR:-/root/devgen}" +IFS=',' read -ra NODES <<< "${STRESS_NODES:-lotus0}" +log_info "Waiting for node multiaddr files..." +MAX_WAIT=300 +WAITED=0 +FOUND=false +while [ "$WAITED" -lt "$MAX_WAIT" ]; do + for node in "${NODES[@]}"; do + node=$(echo "$node" | tr -d ' ') + ADDR_FILE="${DEVGEN_DIR}/${node}/${node}-ipv4addr" + if [ -f "$ADDR_FILE" ] && [ -s "$ADDR_FILE" ]; then + log_info "Found multiaddr for ${node}" + FOUND=true + break + fi + done + if [ "$FOUND" = true ]; then + break + fi + sleep 5 + WAITED=$((WAITED + 5)) +done +if [ "$FOUND" = false ]; then + log_warn "No multiaddr files found after ${MAX_WAIT}s, continuing anyway..." +fi + +# ── 4. Wait for blockchain to reach minimum epoch ── +WAIT_HEIGHT="${STRESS_WAIT_HEIGHT:-10}" +RPC_URL="http://lotus0:${STRESS_RPC_PORT:-1234}/rpc/v1" +log_info "Waiting for block height to reach ${WAIT_HEIGHT}..." +while true; do + height=$(curl -sf -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"Filecoin.ChainHead","params":[],"id":1}' \ + "$RPC_URL" 2>/dev/null | jq -r '.result.Height // empty' 2>/dev/null) + if [ -n "$height" ] && [ "$height" -ge "$WAIT_HEIGHT" ] 2>/dev/null; then + log_info "Blockchain ready at height ${height}" + break + fi + log_info "Current height: ${height:-unknown}, waiting..." + sleep 5 +done + +# ── 5. Launch hegel workload ── +log_info "Launching hegel workload..." +exec env RUST_LOG="${RUST_LOG:-info}" /usr/local/bin/hegel-workload diff --git a/workload/entrypoint/entrypoint.sh b/workload/entrypoint/entrypoint.sh index db96cdf7..bf7e8054 100755 --- a/workload/entrypoint/entrypoint.sh +++ b/workload/entrypoint/entrypoint.sh @@ -168,9 +168,4 @@ if [ "${FUZZER_ENABLED:-1}" = "1" ]; then FUZZER_PID=$! fi -# ── 8. Launch hegel workload ── -log_info "Launching hegel workload..." -/usr/local/bin/hegel-workload & -HEGEL_PID=$! - -wait -n $STRESS_PID ${FUZZER_PID:-} $HEGEL_PID +wait -n $STRESS_PID ${FUZZER_PID:-} From 0bd69079ab3255b01377d5a6cbb27eb6f86316d2 Mon Sep 17 00:00:00 2001 From: Sean Cheatham Date: Mon, 6 Apr 2026 16:34:17 -0400 Subject: [PATCH 47/47] Forest patch fixes --- forest/Dockerfile | 9 ++------- forest/patches/forest.patch | 18 ++++++------------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/forest/Dockerfile b/forest/Dockerfile index cbea62f5..b764aa80 100644 --- a/forest/Dockerfile +++ b/forest/Dockerfile @@ -1,7 +1,7 @@ FROM docker.io/rust:1.81-bookworm AS builder # Pin forest to a specific branch -ARG GIT_COMMIT="" +ARG GIT_COMMIT="d0dc5b83e776a9df14371dd4e26179526b8696b2" ARG LOCAL_BUILD=1 # Step 1: Install dependencies RUN apt-get update && \ @@ -21,12 +21,7 @@ RUN git clone https://github.com/chainsafe/forest /forest WORKDIR /forest -# Use provided commit or default to latest main -RUN if [ -n "${GIT_COMMIT}" ]; then \ - git checkout ${GIT_COMMIT}; \ - else \ - echo "No GIT_COMMIT specified, using latest main"; \ - fi +RUN git checkout ${GIT_COMMIT} COPY ./patches/forest.patch ./forest.patch RUN git apply forest.patch diff --git a/forest/patches/forest.patch b/forest/patches/forest.patch index bd5c6ab3..8294713d 100644 --- a/forest/patches/forest.patch +++ b/forest/patches/forest.patch @@ -33,7 +33,7 @@ index 32e34106c..dc8e01860 100644 OpportunisticGraftScoreThreshold = 3.5 ) diff --git a/src/libp2p/gossip_params.rs b/src/libp2p/gossip_params.rs -index 26298991e..5873e3cd1 100644 +index 38822868c..55aa20dd6 100644 --- a/src/libp2p/gossip_params.rs +++ b/src/libp2p/gossip_params.rs @@ -45,7 +45,7 @@ fn build_msg_topic_config() -> TopicScoreParams { @@ -84,21 +84,15 @@ index 26298991e..5873e3cd1 100644 opportunistic_graft_threshold: 3.5, } diff --git a/src/rpc/methods/chain.rs b/src/rpc/methods/chain.rs -index 35b9dfa..5fade36 100644 +index 620ab0086..8177746ba 100644 --- a/src/rpc/methods/chain.rs +++ b/src/rpc/methods/chain.rs -@@ -1076,12 +1076,12 @@ impl ChainGetTipSetV2 { - // Latest F3 finalized tipset is older than EC finality, falling back to EC finality -- if head.epoch() > f3_finalized_head.epoch() + ctx.chain_config().policy.chain_finality { -+ if head.epoch() > f3_finalized_head.epoch() + 20 { - Self::get_ec_finalized_tipset(ctx) - } else { - Ok(f3_finalized_head) - } - } - +@@ -1082,7 +1082,7 @@ impl ChainGetTipSetV2 { + pub fn get_ec_finalized_tipset(ctx: &Ctx) -> anyhow::Result { let head = ctx.chain_store().heaviest_tipset(); - let ec_finality_epoch = (head.epoch() - ctx.chain_config().policy.chain_finality).max(0); + let ec_finality_epoch = (head.epoch() - 20).max(0); Ok(ctx.chain_index().tipset_by_height( + ec_finality_epoch, + head,