From 8d1652896422b7d8b89343b71144c4ea2b32a260 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 14:06:56 -0300 Subject: [PATCH 01/12] Initial `aggsigdb.MemDB` implementation - Use Actor model - Implement `store` - Test `store` --- crates/core/src/aggsigdb/memory.rs | 151 +++++++++++++++++++++++++++++ crates/core/src/aggsigdb/mod.rs | 2 + crates/core/src/lib.rs | 6 +- crates/core/src/types.rs | 58 +++++------ 4 files changed, 188 insertions(+), 29 deletions(-) create mode 100644 crates/core/src/aggsigdb/memory.rs create mode 100644 crates/core/src/aggsigdb/mod.rs diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs new file mode 100644 index 00000000..1b4a94a7 --- /dev/null +++ b/crates/core/src/aggsigdb/memory.rs @@ -0,0 +1,151 @@ +use crate::types; +use std::collections::{HashMap, hash_map::Entry}; + +#[derive(Debug, thiserror::Error)] +enum StoreError { + #[error("Mismatching data")] + MismatchingData, + + #[error("Send error: {0}")] + Send(#[from] tokio::sync::mpsc::error::SendError), + + #[error("Recv error: {0}")] + Recv(#[from] tokio::sync::oneshot::error::RecvError), +} + +#[derive(Debug)] +enum MemDBCommand { + Store { + duty: types::Duty, + pub_key: types::PubKey, + signed_data: Box, + + response: tokio::sync::oneshot::Sender>, + }, +} + +#[derive(Debug)] +struct MemDBActor { + receiver: tokio::sync::mpsc::Receiver, + data: HashMap<(types::Duty, types::PubKey), Box>, +} + +impl MemDBActor { + fn new(receiver: tokio::sync::mpsc::Receiver) -> Self { + Self { + receiver, + data: HashMap::new(), + } + } + + async fn run(&mut self) { + while let Some(cmd) = self.receiver.recv().await { + match cmd { + MemDBCommand::Store { + duty, + pub_key, + signed_data, + response, + } => { + let result = self.store(duty, pub_key, signed_data).await; + let _ = response.send(result); + } + } + } + } + + async fn store( + &mut self, + duty: types::Duty, + pub_key: types::PubKey, + signed_data: Box, + ) -> Result<(), StoreError> { + // TODO: Add deadline tracking + // _ = db.deadliner.Add(command.duty) + + match self.data.entry((duty, pub_key)) { + Entry::Occupied(slot) if slot.get().as_ref() != signed_data.as_ref() => { + Err(StoreError::MismatchingData) + } + Entry::Occupied(_) => Ok(()), + Entry::Vacant(slot) => { + slot.insert(signed_data); + Ok(()) + } + } + } +} + +struct MemDBHandle { + sender: tokio::sync::mpsc::Sender, +} + +impl MemDBHandle { + fn new() -> Self { + let (sender, receiver) = tokio::sync::mpsc::channel(100); + let mut actor = MemDBActor::new(receiver); + // TODO: Pass a cancellation token + tokio::spawn(async move { + actor.run().await; + }); + Self { sender } + } + + async fn store( + &self, + duty: types::Duty, + pub_key: types::PubKey, + signed_data: Box, + ) -> Result<(), StoreError> { + let (tx, rx) = tokio::sync::oneshot::channel(); + self.sender + .send(MemDBCommand::Store { + duty, + pub_key, + signed_data, + response: tx, + }) + .await?; + rx.await? + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + signeddata::SignedDataError, + types::{Duty, PubKey, Signature, SignedData, SlotNumber}, + }; + + #[derive(Debug, Clone, PartialEq, Eq)] + struct MockSignedData; + + impl SignedData for MockSignedData { + fn signature(&self) -> Result { + Ok(Signature::new([42u8; 96])) + } + + fn set_signature(&self, _signature: Signature) -> Result { + Ok(self.clone()) + } + + fn message_root(&self) -> Result<[u8; 32], SignedDataError> { + Ok([42u8; 32]) + } + } + + #[tokio::test] + async fn test_single_handle_store() { + let handle = MemDBHandle::new(); + let duty = Duty::new_attester_duty(SlotNumber::new(1)); + let pub_key = PubKey::new([7u8; 48]); + let signed_data: Box = Box::new(MockSignedData); + + let task = tokio::spawn(async move { handle.store(duty, pub_key, signed_data).await }); + + task.await + .expect("store task panicked") + .expect("store returned an error"); + } +} diff --git a/crates/core/src/aggsigdb/mod.rs b/crates/core/src/aggsigdb/mod.rs new file mode 100644 index 00000000..54c50307 --- /dev/null +++ b/crates/core/src/aggsigdb/mod.rs @@ -0,0 +1,2 @@ +/// Memory implementation of the AggSigDB. +pub mod memory; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 1076ade4..91cfd4d0 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -23,10 +23,14 @@ pub mod version; /// Duty deadline tracking and notification. pub mod deadline; -/// parsigdb +/// Implementations of ParSigDB. pub mod parsigdb; +/// Implementations of AggSigDB. +pub mod aggsigdb; + mod parsigex_codec; + // SSZ codec operates on compile-time-constant byte sizes and offsets. // Arithmetic is bounded and casts from `usize` to `u32` are safe because all // sizes are well below `u32::MAX`. diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 8d971f7d..b531ad0a 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -704,51 +704,52 @@ impl TryFrom<(&DutyType, &pbcore::ParSignedDataSet)> for ParSignedDataSet { } } -/// SignedDataSet is a set of signed duty data. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SignedDataSet(HashMap); - -impl Default for SignedDataSet -where - T: SignedData, -{ - fn default() -> Self { - Self(HashMap::default()) - } -} +/// A set of signed duty data. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct SignedDataSet(HashMap>); -impl SignedDataSet -where - T: SignedData, -{ +impl SignedDataSet { /// Create a new signed data set. pub fn new() -> Self { Self::default() } /// Get a signed data by public key. - pub fn get(&self, pub_key: &PubKey) -> Option<&T> { - self.0.get(pub_key) + pub fn get(&self, pub_key: &PubKey) -> Option<&dyn SignedData> { + self.0.get(pub_key).map(|b| b.as_ref()) } /// Insert a signed data. - pub fn insert(&mut self, pub_key: PubKey, signed_data: T) { - self.0.insert(pub_key, signed_data); + pub fn insert(&mut self, pub_key: PubKey, signed_data: impl SignedData) { + self.0.insert(pub_key, Box::new(signed_data)); } /// Remove a signed data by public key. - pub fn remove(&mut self, pub_key: &PubKey) -> Option { + pub fn remove(&mut self, pub_key: &PubKey) -> Option> { self.0.remove(pub_key) } - /// Inner signed data set. - pub fn inner(&self) -> &HashMap { - &self.0 + /// Iterate over the signed data set by reference. + pub fn iter(&self) -> std::collections::hash_map::Iter<'_, PubKey, Box> { + self.0.iter() } +} - /// Inner signed data set. - pub fn inner_mut(&mut self) -> &mut HashMap { - &mut self.0 +impl IntoIterator for SignedDataSet { + type IntoIter = std::collections::hash_map::IntoIter>; + type Item = (PubKey, Box); + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a SignedDataSet { + type IntoIter = std::collections::hash_map::Iter<'a, PubKey, Box>; + type Item = (&'a PubKey, &'a Box); + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() } } @@ -1052,9 +1053,10 @@ mod tests { fn signed_data_set() { let mut signed_data_set = SignedDataSet::new(); signed_data_set.insert(PubKey::new([42u8; PK_LEN]), MockSignedData); + let expected: &dyn SignedData = &MockSignedData; assert_eq!( signed_data_set.get(&PubKey::new([42u8; PK_LEN])), - Some(&MockSignedData) + Some(expected) ); } From 8504da10ea5dc093e1a03b947300fc49694c8802 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 15:43:14 -0300 Subject: [PATCH 02/12] Refactor - Expose top-level `new` - Remove impossible errors - Add inline docs --- crates/core/src/aggsigdb/memory.rs | 65 +++++++++++++++++------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index 1b4a94a7..1330acfa 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -1,37 +1,34 @@ use crate::types; use std::collections::{HashMap, hash_map::Entry}; +/// Errors for the in-memory AggSigDB implementation. #[derive(Debug, thiserror::Error)] -enum StoreError { +pub enum Error { + /// Data for the same duty and public key already exists but does not match + /// the new data. #[error("Mismatching data")] MismatchingData, - - #[error("Send error: {0}")] - Send(#[from] tokio::sync::mpsc::error::SendError), - - #[error("Recv error: {0}")] - Recv(#[from] tokio::sync::oneshot::error::RecvError), } #[derive(Debug)] -enum MemDBCommand { +enum Command { Store { duty: types::Duty, pub_key: types::PubKey, signed_data: Box, - response: tokio::sync::oneshot::Sender>, + response: tokio::sync::oneshot::Sender>, }, } #[derive(Debug)] -struct MemDBActor { - receiver: tokio::sync::mpsc::Receiver, +struct Actor { + receiver: tokio::sync::mpsc::Receiver, data: HashMap<(types::Duty, types::PubKey), Box>, } -impl MemDBActor { - fn new(receiver: tokio::sync::mpsc::Receiver) -> Self { +impl Actor { + fn new(receiver: tokio::sync::mpsc::Receiver) -> Self { Self { receiver, data: HashMap::new(), @@ -41,7 +38,7 @@ impl MemDBActor { async fn run(&mut self) { while let Some(cmd) = self.receiver.recv().await { match cmd { - MemDBCommand::Store { + Command::Store { duty, pub_key, signed_data, @@ -59,13 +56,13 @@ impl MemDBActor { duty: types::Duty, pub_key: types::PubKey, signed_data: Box, - ) -> Result<(), StoreError> { + ) -> Result<(), Error> { // TODO: Add deadline tracking // _ = db.deadliner.Add(command.duty) match self.data.entry((duty, pub_key)) { Entry::Occupied(slot) if slot.get().as_ref() != signed_data.as_ref() => { - Err(StoreError::MismatchingData) + Err(Error::MismatchingData) } Entry::Occupied(_) => Ok(()), Entry::Vacant(slot) => { @@ -76,43 +73,53 @@ impl MemDBActor { } } -struct MemDBHandle { - sender: tokio::sync::mpsc::Sender, +/// Handle to interact with the AggSigDB in-memory actor. +#[derive(Clone)] +pub struct Handle { + sender: tokio::sync::mpsc::Sender, } -impl MemDBHandle { +impl Handle { fn new() -> Self { let (sender, receiver) = tokio::sync::mpsc::channel(100); - let mut actor = MemDBActor::new(receiver); - // TODO: Pass a cancellation token + let mut actor = Actor::new(receiver); tokio::spawn(async move { actor.run().await; }); Self { sender } } - async fn store( + /// Stores aggregated signed duty data set. + pub async fn store( &self, duty: types::Duty, pub_key: types::PubKey, signed_data: Box, - ) -> Result<(), StoreError> { + ) -> Result<(), Error> { let (tx, rx) = tokio::sync::oneshot::channel(); - self.sender - .send(MemDBCommand::Store { + let _ = self + .sender + .send(Command::Store { duty, pub_key, signed_data, response: tx, }) - .await?; - rx.await? + .await; + rx.await.expect("Actor task has been killed") } } +/// Create a new memory AggSigDB implementation and return its handle. +/// +/// Clone this handle to share access to the same AggSigDB instance across +/// multiple tasks. +pub fn new() -> Handle { + Handle::new() +} + #[cfg(test)] mod tests { - use super::*; use crate::{ signeddata::SignedDataError, types::{Duty, PubKey, Signature, SignedData, SlotNumber}, @@ -137,7 +144,7 @@ mod tests { #[tokio::test] async fn test_single_handle_store() { - let handle = MemDBHandle::new(); + let handle = super::new(); let duty = Duty::new_attester_duty(SlotNumber::new(1)); let pub_key = PubKey::new([7u8; 48]); let signed_data: Box = Box::new(MockSignedData); From 0890f6323899fae4957cfb44e68960e7dbba4287 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 16:31:38 -0300 Subject: [PATCH 03/12] Rewrite from Actor - Use Mutex/Notify pattern - Remove need for handles - Adjust tests and docs --- crates/core/src/aggsigdb/memory.rs | 160 +++++++++++++---------------- 1 file changed, 72 insertions(+), 88 deletions(-) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index 1330acfa..e4724860 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -1,5 +1,10 @@ +use tokio::sync::{Mutex, Notify}; + use crate::types; -use std::collections::{HashMap, hash_map::Entry}; +use std::{ + collections::{HashMap, hash_map::Entry}, + sync::Arc, +}; /// Errors for the in-memory AggSigDB implementation. #[derive(Debug, thiserror::Error)] @@ -10,112 +15,77 @@ pub enum Error { MismatchingData, } -#[derive(Debug)] -enum Command { - Store { - duty: types::Duty, - pub_key: types::PubKey, - signed_data: Box, - - response: tokio::sync::oneshot::Sender>, - }, -} +/// An in-memory implementation of the AggSigDB. +/// +/// Share an instance by cloning. Cloning is cheap and creates a new reference +/// to the same underlying data. +#[derive(Clone)] +pub struct MemDB(Arc); #[derive(Debug)] -struct Actor { - receiver: tokio::sync::mpsc::Receiver, - data: HashMap<(types::Duty, types::PubKey), Box>, +struct MemDBInner { + data: Mutex>>, + notify: Notify, } -impl Actor { - fn new(receiver: tokio::sync::mpsc::Receiver) -> Self { - Self { - receiver, - data: HashMap::new(), - } +impl MemDB { + /// Creates a new in-memory AggSigDB instance. + pub fn new() -> Self { + Self(Arc::new(MemDBInner { + data: Mutex::new(HashMap::new()), + notify: Notify::new(), + })) } - async fn run(&mut self) { - while let Some(cmd) = self.receiver.recv().await { - match cmd { - Command::Store { - duty, - pub_key, - signed_data, - response, - } => { - let result = self.store(duty, pub_key, signed_data).await; - let _ = response.send(result); - } - } - } - } - - async fn store( - &mut self, + /// Stores aggregated signed duty data set. + pub async fn store( + &self, duty: types::Duty, pub_key: types::PubKey, signed_data: Box, ) -> Result<(), Error> { - // TODO: Add deadline tracking - // _ = db.deadliner.Add(command.duty) + let mut data = self.0.data.lock().await; - match self.data.entry((duty, pub_key)) { + match data.entry((duty, pub_key)) { Entry::Occupied(slot) if slot.get().as_ref() != signed_data.as_ref() => { Err(Error::MismatchingData) } Entry::Occupied(_) => Ok(()), Entry::Vacant(slot) => { slot.insert(signed_data); + // TODO: Optimize to only wake those who are waiting for this specific duty and + // pubkey + self.0.notify.notify_waiters(); Ok(()) } } } -} - -/// Handle to interact with the AggSigDB in-memory actor. -#[derive(Clone)] -pub struct Handle { - sender: tokio::sync::mpsc::Sender, -} - -impl Handle { - fn new() -> Self { - let (sender, receiver) = tokio::sync::mpsc::channel(100); - let mut actor = Actor::new(receiver); - tokio::spawn(async move { - actor.run().await; - }); - Self { sender } - } - /// Stores aggregated signed duty data set. - pub async fn store( + /// Blocks and returns the aggregated signed duty data when available. + pub async fn wait_for( &self, duty: types::Duty, pub_key: types::PubKey, - signed_data: Box, - ) -> Result<(), Error> { - let (tx, rx) = tokio::sync::oneshot::channel(); - let _ = self - .sender - .send(Command::Store { - duty, - pub_key, - signed_data, - response: tx, - }) - .await; - rx.await.expect("Actor task has been killed") - } -} + ) -> Box { + let k = (duty, pub_key); + loop { + // Register interest before checking the map so that a concurrent `store` either + // (a) inserts before we check and we observe the value, or (b) inserts after + // our `notified()` is enabled and wakes us. + let notified = self.0.notify.notified(); + tokio::pin!(notified); + notified.as_mut().enable(); + + { + let data = self.0.data.lock().await; + if let Some(data) = data.get(&k) { + return data.clone(); + } + } -/// Create a new memory AggSigDB implementation and return its handle. -/// -/// Clone this handle to share access to the same AggSigDB instance across -/// multiple tasks. -pub fn new() -> Handle { - Handle::new() + notified.await; + } + } } #[cfg(test)] @@ -142,17 +112,31 @@ mod tests { } } - #[tokio::test] - async fn test_single_handle_store() { - let handle = super::new(); + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn store_and_wait() { + let store = super::MemDB::new(); let duty = Duty::new_attester_duty(SlotNumber::new(1)); let pub_key = PubKey::new([7u8; 48]); let signed_data: Box = Box::new(MockSignedData); - let task = tokio::spawn(async move { handle.store(duty, pub_key, signed_data).await }); + let reader = { + let store = store.clone(); + let duty = duty.clone(); + let pub_key = pub_key.clone(); + + tokio::spawn(async move { store.wait_for(duty, pub_key).await }) + }; + + // Give the reader a chance to reach `notified.await` before we store, so the + // test actually exercises the notify wakeup path rather than the + // fast-path lookup. + tokio::task::yield_now().await; + assert!(!reader.is_finished(), "wait_for should block until store"); + + let write = store.store(duty, pub_key, signed_data.clone()).await; + let read = reader.await.unwrap(); - task.await - .expect("store task panicked") - .expect("store returned an error"); + assert!(write.is_ok()); + assert_eq!(read, signed_data); } } From df51e51ee8c8185918652b723aa44c89bc793c90 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 18:04:58 -0300 Subject: [PATCH 04/12] Add eviction through Deadliner - Use existing Deadliner API (Arc) - Add tests --- crates/core/src/aggsigdb/memory.rs | 147 ++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 13 deletions(-) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index e4724860..2e9d4369 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -1,10 +1,9 @@ -use tokio::sync::{Mutex, Notify}; - -use crate::types; +use crate::{deadline::Deadliner, types}; use std::{ collections::{HashMap, hash_map::Entry}, sync::Arc, }; +use tokio::sync::{Mutex, Notify}; /// Errors for the in-memory AggSigDB implementation. #[derive(Debug, thiserror::Error)] @@ -22,19 +21,43 @@ pub enum Error { #[derive(Clone)] pub struct MemDB(Arc); -#[derive(Debug)] struct MemDBInner { data: Mutex>>, + deadliner: Arc, notify: Notify, } impl MemDB { /// Creates a new in-memory AggSigDB instance. - pub fn new() -> Self { - Self(Arc::new(MemDBInner { + pub fn new(deadliner: Arc) -> Self { + let this = Self(Arc::new(MemDBInner { data: Mutex::new(HashMap::new()), + deadliner: Arc::clone(&deadliner), notify: Notify::new(), - })) + })); + + match deadliner.c() { + Some(evictions) => { + tokio::spawn(Self::evict(Arc::downgrade(&this.0), evictions)); + } + None => { + // TODO: In Charon, `deadliner.c()` always returns `Some` + } + } + + this + } + + async fn evict( + inner: std::sync::Weak, + mut evictions: tokio::sync::mpsc::Receiver, + ) { + while let Some(duty) = evictions.recv().await { + let Some(inner) = inner.upgrade() else { + return; + }; + inner.data.lock().await.retain(|(d, _), _| d != &duty); + } } /// Stores aggregated signed duty data set. @@ -44,6 +67,9 @@ impl MemDB { pub_key: types::PubKey, signed_data: Box, ) -> Result<(), Error> { + // TODO(charon): Distinguish between no deadline supported vs already expired. + let _ = self.0.deadliner.add(duty.clone()).await; + let mut data = self.0.data.lock().await; match data.entry((duty, pub_key)) { @@ -91,16 +117,27 @@ impl MemDB { #[cfg(test)] mod tests { use crate::{ + deadline::Deadliner, signeddata::SignedDataError, types::{Duty, PubKey, Signature, SignedData, SlotNumber}, }; + use async_trait::async_trait; + use std::sync::Arc; + use tokio::sync; + /// Some mock signed data type for testing. #[derive(Debug, Clone, PartialEq, Eq)] - struct MockSignedData; + struct MockSignedData(u8); + + impl MockSignedData { + fn for_test(value: u8) -> Box { + Box::new(Self(value)) + } + } impl SignedData for MockSignedData { fn signature(&self) -> Result { - Ok(Signature::new([42u8; 96])) + Ok(Signature::new([self.0; 96])) } fn set_signature(&self, _signature: Signature) -> Result { @@ -108,16 +145,45 @@ mod tests { } fn message_root(&self) -> Result<[u8; 32], SignedDataError> { - Ok([42u8; 32]) + Ok([self.0; 32]) + } + } + + /// Deadliner that hands out a caller-supplied receiver, allowing tests to + /// drive eviction by sending on the paired sender. + struct TestDeadliner(std::sync::Mutex>>); + + impl TestDeadliner { + fn new(receiver: sync::mpsc::Receiver) -> Arc { + Arc::new(Self(std::sync::Mutex::new(Some(receiver)))) + } + + /// Creates a deadliner that never returns any duties to evict, so no + /// eviction will occur. + fn never() -> Arc { + Arc::new(Self(std::sync::Mutex::new(None))) + } + } + + #[async_trait] + impl Deadliner for TestDeadliner { + async fn add(&self, _duty: Duty) -> bool { + true + } + + fn c(&self) -> Option> { + self.0.lock().unwrap().take() } } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn store_and_wait() { - let store = super::MemDB::new(); + async fn wait_then_store() { + let deadliner = TestDeadliner::never(); + let store = super::MemDB::new(deadliner); + let duty = Duty::new_attester_duty(SlotNumber::new(1)); let pub_key = PubKey::new([7u8; 48]); - let signed_data: Box = Box::new(MockSignedData); + let signed_data: Box = MockSignedData::for_test(0); let reader = { let store = store.clone(); @@ -139,4 +205,59 @@ mod tests { assert!(write.is_ok()); assert_eq!(read, signed_data); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn store_evict_wait_then_store() { + let (evict_tx, evict_rx) = sync::mpsc::channel::(1); + let deadliner = TestDeadliner::new(evict_rx); + + let store = super::MemDB::new(deadliner); + + let duty = Duty::new_attester_duty(SlotNumber::new(1)); + let pub_key = PubKey::new([7u8; 48]); + let first = MockSignedData::for_test(1); + let second = MockSignedData::for_test(2); + + store + .store(duty.clone(), pub_key, first.clone()) + .await + .unwrap(); + + // The eviction task runs concurrently, so we poll until the specific + // data gone, so new readers are guaranteed to not observe it. + evict_tx.send(duty.clone()).await.unwrap(); + tokio::time::timeout(std::time::Duration::from_secs(2), async { + while store + .0 + .data + .lock() + .await + .contains_key(&(duty.clone(), pub_key)) + { + tokio::task::yield_now().await; + } + }) + .await + .expect("eviction was not applied in time"); + + let reader = { + let store = store.clone(); + let duty = duty.clone(); + + tokio::spawn(async move { store.wait_for(duty, pub_key).await }) + }; + + // The eviction has been applied, so wait_for has no entry to return and must + // block. + tokio::task::yield_now().await; + assert!(!reader.is_finished(), "wait_for should block until store"); + + // Store new data for the same duty and pubkey. The reader should wake up and + // return the new data, not the evicted data. + store.store(duty, pub_key, second.clone()).await.unwrap(); + + let read = reader.await.unwrap(); + assert_eq!(read, second); + assert_ne!(read, first); + } } From 453bdcf4303bae02c81ea5dbc5d9fa0f4f7a6c5b Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 18:10:56 -0300 Subject: [PATCH 05/12] Port simple test cases --- crates/core/src/aggsigdb/memory.rs | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index 2e9d4369..f1e31a44 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -260,4 +260,60 @@ mod tests { assert_eq!(read, second); assert_ne!(read, first); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn write_read() { + let store = super::MemDB::new(TestDeadliner::never()); + + let duty = Duty::new_proposer_duty(SlotNumber::new(10)); + let pub_key = PubKey::new([7u8; 48]); + let signed_data = MockSignedData::for_test(42); + + store + .store(duty.clone(), pub_key, signed_data.clone()) + .await + .unwrap(); + + let result = store.wait_for(duty, pub_key).await; + assert_eq!(result, signed_data); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn cannot_overwrite() { + let store = super::MemDB::new(TestDeadliner::never()); + + let duty = Duty::new_proposer_duty(SlotNumber::new(10)); + let pub_key = PubKey::new([7u8; 48]); + let first = MockSignedData::for_test(1); + let second = MockSignedData::for_test(2); + + store.store(duty.clone(), pub_key, first).await.unwrap(); + + let err = store + .store(duty, pub_key, second) + .await + .expect_err("storing mismatching data should fail"); + assert!(matches!(err, super::Error::MismatchingData)); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn write_idempotent() { + let store = super::MemDB::new(TestDeadliner::never()); + + let duty = Duty::new_proposer_duty(SlotNumber::new(10)); + let pub_key = PubKey::new([7u8; 48]); + let signed_data = MockSignedData::for_test(42); + + store + .store(duty.clone(), pub_key, signed_data.clone()) + .await + .unwrap(); + store + .store(duty.clone(), pub_key, signed_data.clone()) + .await + .unwrap(); + + let result = store.wait_for(duty, pub_key).await; + assert_eq!(result, signed_data); + } } From be9370f391f439bbe374cf4aa295d5e077fda85b Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 18:13:22 -0300 Subject: [PATCH 06/12] Reorder tests and rename - Match Charon --- crates/core/src/aggsigdb/memory.rs | 116 ++++++++++++++--------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index f1e31a44..3a6e0ba5 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -177,7 +177,24 @@ mod tests { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn wait_then_store() { + async fn write_read() { + let store = super::MemDB::new(TestDeadliner::never()); + + let duty = Duty::new_proposer_duty(SlotNumber::new(10)); + let pub_key = PubKey::new([7u8; 48]); + let signed_data = MockSignedData::for_test(42); + + store + .store(duty.clone(), pub_key, signed_data.clone()) + .await + .unwrap(); + + let result = store.wait_for(duty, pub_key).await; + assert_eq!(result, signed_data); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn write_unblocks() { let deadliner = TestDeadliner::never(); let store = super::MemDB::new(deadliner); @@ -207,7 +224,46 @@ mod tests { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn store_evict_wait_then_store() { + async fn cannot_overwrite() { + let store = super::MemDB::new(TestDeadliner::never()); + + let duty = Duty::new_proposer_duty(SlotNumber::new(10)); + let pub_key = PubKey::new([7u8; 48]); + let first = MockSignedData::for_test(1); + let second = MockSignedData::for_test(2); + + store.store(duty.clone(), pub_key, first).await.unwrap(); + + let err = store + .store(duty, pub_key, second) + .await + .expect_err("storing mismatching data should fail"); + assert!(matches!(err, super::Error::MismatchingData)); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn write_idempotent() { + let store = super::MemDB::new(TestDeadliner::never()); + + let duty = Duty::new_proposer_duty(SlotNumber::new(10)); + let pub_key = PubKey::new([7u8; 48]); + let signed_data = MockSignedData::for_test(42); + + store + .store(duty.clone(), pub_key, signed_data.clone()) + .await + .unwrap(); + store + .store(duty.clone(), pub_key, signed_data.clone()) + .await + .unwrap(); + + let result = store.wait_for(duty, pub_key).await; + assert_eq!(result, signed_data); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn write_evict_wait_then_write() { let (evict_tx, evict_rx) = sync::mpsc::channel::(1); let deadliner = TestDeadliner::new(evict_rx); @@ -260,60 +316,4 @@ mod tests { assert_eq!(read, second); assert_ne!(read, first); } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn write_read() { - let store = super::MemDB::new(TestDeadliner::never()); - - let duty = Duty::new_proposer_duty(SlotNumber::new(10)); - let pub_key = PubKey::new([7u8; 48]); - let signed_data = MockSignedData::for_test(42); - - store - .store(duty.clone(), pub_key, signed_data.clone()) - .await - .unwrap(); - - let result = store.wait_for(duty, pub_key).await; - assert_eq!(result, signed_data); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn cannot_overwrite() { - let store = super::MemDB::new(TestDeadliner::never()); - - let duty = Duty::new_proposer_duty(SlotNumber::new(10)); - let pub_key = PubKey::new([7u8; 48]); - let first = MockSignedData::for_test(1); - let second = MockSignedData::for_test(2); - - store.store(duty.clone(), pub_key, first).await.unwrap(); - - let err = store - .store(duty, pub_key, second) - .await - .expect_err("storing mismatching data should fail"); - assert!(matches!(err, super::Error::MismatchingData)); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn write_idempotent() { - let store = super::MemDB::new(TestDeadliner::never()); - - let duty = Duty::new_proposer_duty(SlotNumber::new(10)); - let pub_key = PubKey::new([7u8; 48]); - let signed_data = MockSignedData::for_test(42); - - store - .store(duty.clone(), pub_key, signed_data.clone()) - .await - .unwrap(); - store - .store(duty.clone(), pub_key, signed_data.clone()) - .await - .unwrap(); - - let result = store.wait_for(duty, pub_key).await; - assert_eq!(result, signed_data); - } } From 6d614914bcd26bae0e7f6892bc85a87390f06adb Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 18:23:07 -0300 Subject: [PATCH 07/12] Add more tests - Rust specific --- crates/core/src/aggsigdb/memory.rs | 77 ++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index 3a6e0ba5..a226f65d 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -316,4 +316,81 @@ mod tests { assert_eq!(read, second); assert_ne!(read, first); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + async fn write_unblocks_many() { + const N: usize = 4; + + let store = super::MemDB::new(TestDeadliner::never()); + let duty = Duty::new_proposer_duty(SlotNumber::new(10)); + let pub_key = PubKey::new([7u8; 48]); + let signed_data = MockSignedData::for_test(42); + + let readers: Vec<_> = (0..N) + .map(|_| { + let store = store.clone(); + let duty = duty.clone(); + tokio::spawn(async move { store.wait_for(duty, pub_key).await }) + }) + .collect(); + + // Give readers a chance to reach `notified.await` before the store. + tokio::task::yield_now().await; + for reader in &readers { + assert!( + !reader.is_finished(), + "all readers should block until store" + ); + } + + // A single store unblocks all readers. + store + .store(duty, pub_key, signed_data.clone()) + .await + .unwrap(); + + for reader in readers { + let read = reader.await.unwrap(); + assert_eq!(read, signed_data); + } + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn unrelated_write_does_not_unblock() { + let store = super::MemDB::new(TestDeadliner::never()); + + let duty_a = Duty::new_proposer_duty(SlotNumber::new(10)); + let data_a = MockSignedData::for_test(1); + + let duty_b = Duty::new_attester_duty(SlotNumber::new(20)); + let data_b = MockSignedData::for_test(2); + + let pub_key = PubKey::new([7u8; 48]); + + let reader = { + let store = store.clone(); + let duty_a = duty_a.clone(); + tokio::spawn(async move { store.wait_for(duty_a, pub_key).await }) + }; + + tokio::task::yield_now().await; + assert!(!reader.is_finished(), "reader should block initially"); + + // Storing an unrelated key wakes readers, which block again since the store is + // unrelated. + store.store(duty_b, pub_key, data_b.clone()).await.unwrap(); + + tokio::task::yield_now().await; + assert!( + !reader.is_finished(), + "reader should re-block after unrelated store" + ); + + // Storing the actual key unblocks the reader. + store.store(duty_a, pub_key, data_a.clone()).await.unwrap(); + + let read = reader.await.unwrap(); + assert_eq!(read, data_a); + assert_ne!(read, data_b); + } } From 6a91cbeb029718c5d309d50bce96e642afa56f56 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 18:23:59 -0300 Subject: [PATCH 08/12] Update lockfile --- Cargo.lock | 434 +++++++++++++++++++++++++++++------------------------ 1 file changed, 241 insertions(+), 193 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 934c8932..9441dcea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -133,7 +133,7 @@ dependencies = [ "k256", "once_cell", "rand 0.8.6", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_with", @@ -179,9 +179,9 @@ dependencies = [ [[package]] name = "alloy-core" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e8604b0c092fabc80d075ede181c9b9e596249c70b99253082d7e689836529" +checksum = "62ddde5968de6044d67af107ad835bc0069a7ca245870b94c5958a7d8712b184" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -192,9 +192,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" +checksum = "a475bb02d9cef2dbb99065c1664ab3fe1f9352e21d6d5ed3f02cdbfc06ed1abc" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -203,7 +203,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow 0.7.15", + "winnow 1.0.3", ] [[package]] @@ -246,15 +246,16 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec6ae911a2fc304a7cb80a79fb7bed6d1474aed4e7c203df1f8ff538f64fc78d" +checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" dependencies = [ "alloy-primitives", "alloy-rlp", "borsh", "once_cell", "serde", + "thiserror 2.0.18", ] [[package]] @@ -297,9 +298,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" +checksum = "7c36c9d7f9021601b04bfef14a4b64849f6d73116a4e91e071d7fbfe10247901" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -363,9 +364,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" dependencies = [ "alloy-rlp", "bytes", @@ -373,7 +374,7 @@ dependencies = [ "const-hex", "derive_more", "foldhash 0.2.0", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap 2.14.0", "itoa", "k256", @@ -384,8 +385,9 @@ dependencies = [ "rapidhash", "ruint", "rustc-hash", + "secp256k1 0.31.1", "serde", - "sha3", + "sha3 0.11.0", ] [[package]] @@ -560,9 +562,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +checksum = "840128ed2b2971d6d4668a553fe403a82683d3acc646c73e75887e7157408033" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -574,9 +576,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +checksum = "63ec265e5d65d725175f6ca7711c970824c90ef9c0d1f1973711d4150ee612dd" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -586,16 +588,16 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "sha3", + "sha3 0.11.0", "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-macro-input" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +checksum = "89bf01077f18650876cfa682eb1f949967b5cde03f1a51c955c469d2c9b4aa67" dependencies = [ "alloy-json-abi", "const-hex", @@ -611,19 +613,19 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +checksum = "857b470ecdd2ed38beaf82ad1a38c516a8ff75266750f38b9eeed001d575241b" dependencies = [ "serde", - "winnow 0.7.15", + "winnow 1.0.3", ] [[package]] name = "alloy-sol-types" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +checksum = "384cf252de0db2dec52821eac037a7f57e2aa33fe5b900ce6fe39973402341f1" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -749,7 +751,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -760,7 +762,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -972,9 +974,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "asn1-rs" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -1154,9 +1156,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "zeroize", @@ -1164,9 +1166,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -1355,6 +1357,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "blst" version = "0.3.16" @@ -1587,9 +1598,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -1691,7 +1702,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -2024,6 +2035,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctor" version = "0.2.9" @@ -2153,9 +2173,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -2188,7 +2208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2293,12 +2313,22 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -2312,9 +2342,9 @@ dependencies = [ [[package]] name = "docker_credential" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4564c274ebf369f501de192b02a0b81a5c4bda375abfe526aa70fc702fa6fa0" +checksum = "29547a1dc60885a552306986316bc9701ba120c1a8db6769fa68691529ad373d" dependencies = [ "base64", "serde", @@ -2398,9 +2428,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -2489,7 +2519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2528,9 +2558,9 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a4a4e4273b0135111fe9464e35465067766a8f664615b5a86338b73864407" +checksum = "e462875ad8693755ea8913d6e905715c76ea4836e2254e18c9cf0f7a8f8c2a13" dependencies = [ "alloy-primitives", "ethereum_serde_utils", @@ -2543,9 +2573,9 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cd82c68120c89361e1a457245cf212f7d9f541bffaffed530c8f2d54a160b2" +checksum = "daf022360bdbe9456eda5f35718a50476d5b2a0d51a97ed4eae27420737a6fba" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -2642,13 +2672,12 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -2843,12 +2872,12 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" dependencies = [ - "gloo-timers 0.2.6", - "send_wrapper 0.4.0", + "gloo-timers 0.4.0", + "send_wrapper", ] [[package]] @@ -2968,9 +2997,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -2980,9 +3009,9 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" dependencies = [ "futures-channel", "futures-core", @@ -3003,9 +3032,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -3064,15 +3093,18 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", - "serde", - "serde_core", ] [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "hashlink" @@ -3265,6 +3297,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.9.0" @@ -3363,7 +3404,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -3617,7 +3658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -3650,16 +3691,6 @@ 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" @@ -3760,9 +3791,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -3794,11 +3825,21 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + [[package]] name = "keccak-asm" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" +checksum = "1766b89733097006f3a1388a02849865d6bc98c89273cb622e29fdd209922183" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -3839,9 +3880,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" -version = "0.18.3+1.9.2" +version = "0.18.4+1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +checksum = "9b26f66f35e1871b22efcf7191564123d2a446ca0538cde63c23adfefa9b15b7" dependencies = [ "cc", "libc", @@ -4259,7 +4300,7 @@ dependencies = [ "pin-project", "rand 0.8.6", "salsa20", - "sha3", + "sha3 0.10.9", "tracing", ] @@ -4485,7 +4526,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-webrtc-utils", - "send_wrapper 0.6.0", + "send_wrapper", "thiserror 2.0.18", "tracing", "wasm-bindgen", @@ -4524,7 +4565,7 @@ dependencies = [ "futures", "js-sys", "libp2p-core", - "send_wrapper 0.6.0", + "send_wrapper", "thiserror 2.0.18", "tracing", "wasm-bindgen", @@ -4544,7 +4585,7 @@ dependencies = [ "libp2p-noise", "multiaddr", "multihash", - "send_wrapper 0.6.0", + "send_wrapper", "thiserror 2.0.18", "tracing", "wasm-bindgen", @@ -4567,18 +4608,6 @@ dependencies = [ "yamux 0.13.10", ] -[[package]] -name = "libredox" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.7.4", -] - [[package]] name = "libz-sys" version = "1.1.28" @@ -4645,9 +4674,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "macro-string" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" dependencies = [ "proc-macro2", "quote", @@ -4925,7 +4954,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4963,9 +4992,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -5088,7 +5117,7 @@ dependencies = [ "oas3", "prettyplease", "proc-macro2", - "quick-xml", + "quick-xml 0.40.1", "quote", "regex", "reqwest 0.13.3", @@ -5141,9 +5170,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.79" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags", "cfg-if", @@ -5172,9 +5201,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -5256,7 +5285,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -5361,18 +5390,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -5407,12 +5436,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "plotters" version = "0.3.7" @@ -5509,7 +5532,7 @@ dependencies = [ "pluto-ssz", "pluto-testutil", "pluto-tracing", - "quick-xml", + "quick-xml 0.39.4", "rand 0.8.6", "reqwest 0.13.3", "serde", @@ -5727,7 +5750,7 @@ dependencies = [ "serde_json", "serde_with", "sha2", - "sha3", + "sha3 0.10.9", "tempfile", "test-case", "thiserror 2.0.18", @@ -6222,9 +6245,19 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.39.2" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "2474bd2e5029e7ccb6abb2ba48cf2383a333851dedf495901544281590c7da7f" dependencies = [ "memchr", "serde", @@ -6244,7 +6277,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -6282,7 +6315,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -6494,15 +6527,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" -dependencies = [ - "bitflags", -] - [[package]] name = "ref-cast" version = "1.0.25" @@ -6774,7 +6798,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6833,7 +6857,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6981,10 +7005,21 @@ checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", "rand 0.8.6", - "secp256k1-sys", + "secp256k1-sys 0.10.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.4", + "secp256k1-sys 0.11.0", +] + [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -6994,6 +7029,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -7045,12 +7089,6 @@ dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - [[package]] name = "send_wrapper" version = "0.6.0" @@ -7149,11 +7187,12 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.19.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" dependencies = [ "base64", + "bs58", "chrono", "hex", "indexmap 1.9.3", @@ -7168,9 +7207,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.19.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -7230,14 +7269,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", - "keccak", + "keccak 0.1.6", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", ] [[package]] name = "sha3-asm" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" +checksum = "9f3f15d4e239ebe08413eed880e0f9b5af4b40ee0472543320efa91d488e96a7" dependencies = [ "cc", "cfg-if", @@ -7355,7 +7404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7475,9 +7524,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +checksum = "ec005042c7d952febc1a3ef5b0f6674e9054aa836877a31c90b20e25b3d31744" dependencies = [ "paste", "proc-macro2", @@ -7554,9 +7603,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -7573,7 +7622,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7766,9 +7815,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -7890,7 +7939,7 @@ dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -7899,7 +7948,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.2", + "winnow 1.0.3", ] [[package]] @@ -7910,9 +7959,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tonic" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "axum", @@ -7939,9 +7988,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost 0.14.3", @@ -7969,20 +8018,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -8043,9 +8092,9 @@ dependencies = [ [[package]] name = "tracing-loki" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3beec919fbdf99d719de8eda6adae3281f8a5b71ae40431f44dc7423053d34" +checksum = "a8d1ad78bf74c1790b0825ddc35ad1bb9498736c51c8437796b81cadf4916cd8" dependencies = [ "loki-api", "reqwest 0.12.28", @@ -8053,7 +8102,6 @@ dependencies = [ "serde_json", "snap", "tokio", - "tokio-stream", "tracing", "tracing-core", "tracing-log", @@ -8202,7 +8250,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -8449,9 +8497,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -8462,9 +8510,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.70" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ "js-sys", "wasm-bindgen", @@ -8472,9 +8520,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8482,9 +8530,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -8495,9 +8543,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -8565,9 +8613,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", @@ -8638,7 +8686,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8988,9 +9036,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -9266,9 +9314,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] From 1ecfe68060e2d3c7e2aa38a694ae9cbb916dfc42 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 18:44:44 -0300 Subject: [PATCH 09/12] Apply clippy suggestions --- crates/core/src/aggsigdb/memory.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index a226f65d..8c3298f1 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -205,7 +205,6 @@ mod tests { let reader = { let store = store.clone(); let duty = duty.clone(); - let pub_key = pub_key.clone(); tokio::spawn(async move { store.wait_for(duty, pub_key).await }) }; From 98993babdf1b490140a8be97439bb9f8b5e437f1 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 21:02:56 -0300 Subject: [PATCH 10/12] Use `SignedDataSet` as input instead of single `SignedData` - Matches Charon API --- crates/core/src/aggsigdb/memory.rs | 125 ++++++++++++++++------------- 1 file changed, 70 insertions(+), 55 deletions(-) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index 8c3298f1..c0e6ae3c 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -61,30 +61,36 @@ impl MemDB { } /// Stores aggregated signed duty data set. - pub async fn store( - &self, - duty: types::Duty, - pub_key: types::PubKey, - signed_data: Box, - ) -> Result<(), Error> { + pub async fn store(&self, duty: types::Duty, set: types::SignedDataSet) -> Result<(), Error> { // TODO(charon): Distinguish between no deadline supported vs already expired. let _ = self.0.deadliner.add(duty.clone()).await; let mut data = self.0.data.lock().await; - match data.entry((duty, pub_key)) { - Entry::Occupied(slot) if slot.get().as_ref() != signed_data.as_ref() => { - Err(Error::MismatchingData) - } - Entry::Occupied(_) => Ok(()), - Entry::Vacant(slot) => { - slot.insert(signed_data); - // TODO: Optimize to only wake those who are waiting for this specific duty and - // pubkey - self.0.notify.notify_waiters(); - Ok(()) - } - } + let result = set + .into_iter() + .map(|(pub_key, signed_data)| { + let key = (duty.clone(), pub_key); + + match data.entry(key) { + Entry::Occupied(slot) if slot.get().as_ref() != signed_data.as_ref() => { + Err(Error::MismatchingData) + } + Entry::Occupied(_) => Ok(()), + Entry::Vacant(slot) => { + slot.insert(signed_data); + Ok(()) + } + } + }) + .collect::, _>>(); + + // TODO: Optimize wake to only occur if new data was actually inserted, + // and to only wake those who are waiting for the specific duty and pubkey, + // rather than all waiters. + self.0.notify.notify_waiters(); + + result.map(|_| ()) } /// Blocks and returns the aggregated signed duty data when available. @@ -119,7 +125,7 @@ mod tests { use crate::{ deadline::Deadliner, signeddata::SignedDataError, - types::{Duty, PubKey, Signature, SignedData, SlotNumber}, + types::{Duty, PubKey, Signature, SignedData, SignedDataSet, SlotNumber}, }; use async_trait::async_trait; use std::sync::Arc; @@ -129,12 +135,6 @@ mod tests { #[derive(Debug, Clone, PartialEq, Eq)] struct MockSignedData(u8); - impl MockSignedData { - fn for_test(value: u8) -> Box { - Box::new(Self(value)) - } - } - impl SignedData for MockSignedData { fn signature(&self) -> Result { Ok(Signature::new([self.0; 96])) @@ -149,6 +149,18 @@ mod tests { } } + impl MockSignedData { + fn to_set(&self, pub_key: PubKey) -> SignedDataSet { + let mut set = SignedDataSet::new(); + set.insert(pub_key, self.clone()); + set + } + + fn boxed(&self) -> Box { + Box::new(self.clone()) + } + } + /// Deadliner that hands out a caller-supplied receiver, allowing tests to /// drive eviction by sending on the paired sender. struct TestDeadliner(std::sync::Mutex>>); @@ -182,15 +194,15 @@ mod tests { let duty = Duty::new_proposer_duty(SlotNumber::new(10)); let pub_key = PubKey::new([7u8; 48]); - let signed_data = MockSignedData::for_test(42); + let signed_data = MockSignedData(42); store - .store(duty.clone(), pub_key, signed_data.clone()) + .store(duty.clone(), signed_data.to_set(pub_key)) .await .unwrap(); let result = store.wait_for(duty, pub_key).await; - assert_eq!(result, signed_data); + assert_eq!(result, signed_data.boxed()); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -200,7 +212,7 @@ mod tests { let duty = Duty::new_attester_duty(SlotNumber::new(1)); let pub_key = PubKey::new([7u8; 48]); - let signed_data: Box = MockSignedData::for_test(0); + let signed_data = MockSignedData(0); let reader = { let store = store.clone(); @@ -215,11 +227,11 @@ mod tests { tokio::task::yield_now().await; assert!(!reader.is_finished(), "wait_for should block until store"); - let write = store.store(duty, pub_key, signed_data.clone()).await; + let write = store.store(duty, signed_data.to_set(pub_key)).await; let read = reader.await.unwrap(); assert!(write.is_ok()); - assert_eq!(read, signed_data); + assert_eq!(read, signed_data.boxed()); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -228,13 +240,16 @@ mod tests { let duty = Duty::new_proposer_duty(SlotNumber::new(10)); let pub_key = PubKey::new([7u8; 48]); - let first = MockSignedData::for_test(1); - let second = MockSignedData::for_test(2); + let first = MockSignedData(1); + let second = MockSignedData(2); - store.store(duty.clone(), pub_key, first).await.unwrap(); + store + .store(duty.clone(), first.to_set(pub_key)) + .await + .unwrap(); let err = store - .store(duty, pub_key, second) + .store(duty, second.to_set(pub_key)) .await .expect_err("storing mismatching data should fail"); assert!(matches!(err, super::Error::MismatchingData)); @@ -246,19 +261,19 @@ mod tests { let duty = Duty::new_proposer_duty(SlotNumber::new(10)); let pub_key = PubKey::new([7u8; 48]); - let signed_data = MockSignedData::for_test(42); + let signed_data = MockSignedData(42); store - .store(duty.clone(), pub_key, signed_data.clone()) + .store(duty.clone(), signed_data.to_set(pub_key)) .await .unwrap(); store - .store(duty.clone(), pub_key, signed_data.clone()) + .store(duty.clone(), signed_data.to_set(pub_key)) .await .unwrap(); let result = store.wait_for(duty, pub_key).await; - assert_eq!(result, signed_data); + assert_eq!(result, signed_data.boxed()); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -270,11 +285,11 @@ mod tests { let duty = Duty::new_attester_duty(SlotNumber::new(1)); let pub_key = PubKey::new([7u8; 48]); - let first = MockSignedData::for_test(1); - let second = MockSignedData::for_test(2); + let first = MockSignedData(1); + let second = MockSignedData(2); store - .store(duty.clone(), pub_key, first.clone()) + .store(duty.clone(), first.to_set(pub_key)) .await .unwrap(); @@ -309,11 +324,11 @@ mod tests { // Store new data for the same duty and pubkey. The reader should wake up and // return the new data, not the evicted data. - store.store(duty, pub_key, second.clone()).await.unwrap(); + store.store(duty, second.to_set(pub_key)).await.unwrap(); let read = reader.await.unwrap(); - assert_eq!(read, second); - assert_ne!(read, first); + assert_eq!(read, second.boxed()); + assert_ne!(read, first.boxed()); } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] @@ -323,7 +338,7 @@ mod tests { let store = super::MemDB::new(TestDeadliner::never()); let duty = Duty::new_proposer_duty(SlotNumber::new(10)); let pub_key = PubKey::new([7u8; 48]); - let signed_data = MockSignedData::for_test(42); + let signed_data = MockSignedData(42); let readers: Vec<_> = (0..N) .map(|_| { @@ -344,13 +359,13 @@ mod tests { // A single store unblocks all readers. store - .store(duty, pub_key, signed_data.clone()) + .store(duty, signed_data.to_set(pub_key)) .await .unwrap(); for reader in readers { let read = reader.await.unwrap(); - assert_eq!(read, signed_data); + assert_eq!(read, signed_data.boxed()); } } @@ -359,10 +374,10 @@ mod tests { let store = super::MemDB::new(TestDeadliner::never()); let duty_a = Duty::new_proposer_duty(SlotNumber::new(10)); - let data_a = MockSignedData::for_test(1); + let data_a = MockSignedData(1); let duty_b = Duty::new_attester_duty(SlotNumber::new(20)); - let data_b = MockSignedData::for_test(2); + let data_b = MockSignedData(2); let pub_key = PubKey::new([7u8; 48]); @@ -377,7 +392,7 @@ mod tests { // Storing an unrelated key wakes readers, which block again since the store is // unrelated. - store.store(duty_b, pub_key, data_b.clone()).await.unwrap(); + store.store(duty_b, data_b.to_set(pub_key)).await.unwrap(); tokio::task::yield_now().await; assert!( @@ -386,10 +401,10 @@ mod tests { ); // Storing the actual key unblocks the reader. - store.store(duty_a, pub_key, data_a.clone()).await.unwrap(); + store.store(duty_a, data_a.to_set(pub_key)).await.unwrap(); let read = reader.await.unwrap(); - assert_eq!(read, data_a); - assert_ne!(read, data_b); + assert_eq!(read, data_a.boxed()); + assert_ne!(read, data_b.boxed()); } } From 33c15ac12c1f9ab247fad5c2ae129c4251e0a415 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 20 May 2026 21:12:32 -0300 Subject: [PATCH 11/12] Refactor - Use `try_for_each` - Proper naming convention --- crates/core/src/aggsigdb/memory.rs | 62 ++++++++++++++++-------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index c0e6ae3c..f361463f 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -67,30 +67,28 @@ impl MemDB { let mut data = self.0.data.lock().await; - let result = set - .into_iter() - .map(|(pub_key, signed_data)| { - let key = (duty.clone(), pub_key); - - match data.entry(key) { - Entry::Occupied(slot) if slot.get().as_ref() != signed_data.as_ref() => { - Err(Error::MismatchingData) - } - Entry::Occupied(_) => Ok(()), - Entry::Vacant(slot) => { - slot.insert(signed_data); - Ok(()) - } + // NOTE: Partial insertions on error match the semantics of Charon. + let result = set.into_iter().try_for_each(|(pub_key, signed_data)| { + let key = (duty.clone(), pub_key); + + match data.entry(key) { + Entry::Occupied(slot) if slot.get().as_ref() != signed_data.as_ref() => { + Err(Error::MismatchingData) } - }) - .collect::, _>>(); + Entry::Occupied(_) => Ok(()), + Entry::Vacant(slot) => { + slot.insert(signed_data); + Ok(()) + } + } + }); // TODO: Optimize wake to only occur if new data was actually inserted, // and to only wake those who are waiting for the specific duty and pubkey, // rather than all waiters. self.0.notify.notify_waiters(); - result.map(|_| ()) + result } /// Blocks and returns the aggregated signed duty data when available. @@ -150,7 +148,7 @@ mod tests { } impl MockSignedData { - fn to_set(&self, pub_key: PubKey) -> SignedDataSet { + fn singleton(&self, pub_key: PubKey) -> SignedDataSet { let mut set = SignedDataSet::new(); set.insert(pub_key, self.clone()); set @@ -197,7 +195,7 @@ mod tests { let signed_data = MockSignedData(42); store - .store(duty.clone(), signed_data.to_set(pub_key)) + .store(duty.clone(), signed_data.singleton(pub_key)) .await .unwrap(); @@ -227,7 +225,7 @@ mod tests { tokio::task::yield_now().await; assert!(!reader.is_finished(), "wait_for should block until store"); - let write = store.store(duty, signed_data.to_set(pub_key)).await; + let write = store.store(duty, signed_data.singleton(pub_key)).await; let read = reader.await.unwrap(); assert!(write.is_ok()); @@ -244,12 +242,12 @@ mod tests { let second = MockSignedData(2); store - .store(duty.clone(), first.to_set(pub_key)) + .store(duty.clone(), first.singleton(pub_key)) .await .unwrap(); let err = store - .store(duty, second.to_set(pub_key)) + .store(duty, second.singleton(pub_key)) .await .expect_err("storing mismatching data should fail"); assert!(matches!(err, super::Error::MismatchingData)); @@ -264,11 +262,11 @@ mod tests { let signed_data = MockSignedData(42); store - .store(duty.clone(), signed_data.to_set(pub_key)) + .store(duty.clone(), signed_data.singleton(pub_key)) .await .unwrap(); store - .store(duty.clone(), signed_data.to_set(pub_key)) + .store(duty.clone(), signed_data.singleton(pub_key)) .await .unwrap(); @@ -289,7 +287,7 @@ mod tests { let second = MockSignedData(2); store - .store(duty.clone(), first.to_set(pub_key)) + .store(duty.clone(), first.singleton(pub_key)) .await .unwrap(); @@ -324,7 +322,7 @@ mod tests { // Store new data for the same duty and pubkey. The reader should wake up and // return the new data, not the evicted data. - store.store(duty, second.to_set(pub_key)).await.unwrap(); + store.store(duty, second.singleton(pub_key)).await.unwrap(); let read = reader.await.unwrap(); assert_eq!(read, second.boxed()); @@ -359,7 +357,7 @@ mod tests { // A single store unblocks all readers. store - .store(duty, signed_data.to_set(pub_key)) + .store(duty, signed_data.singleton(pub_key)) .await .unwrap(); @@ -392,7 +390,10 @@ mod tests { // Storing an unrelated key wakes readers, which block again since the store is // unrelated. - store.store(duty_b, data_b.to_set(pub_key)).await.unwrap(); + store + .store(duty_b, data_b.singleton(pub_key)) + .await + .unwrap(); tokio::task::yield_now().await; assert!( @@ -401,7 +402,10 @@ mod tests { ); // Storing the actual key unblocks the reader. - store.store(duty_a, data_a.to_set(pub_key)).await.unwrap(); + store + .store(duty_a, data_a.singleton(pub_key)) + .await + .unwrap(); let read = reader.await.unwrap(); assert_eq!(read, data_a.boxed()); From b341668fad81921a0268583432afa563e01e1618 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 21 May 2026 02:08:55 -0300 Subject: [PATCH 12/12] Apply Claude suggestions - Annotate `wait_for` abort mechanism - Log when deadliner does not have a channel --- crates/core/src/aggsigdb/memory.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/core/src/aggsigdb/memory.rs b/crates/core/src/aggsigdb/memory.rs index f361463f..1e6af9f5 100644 --- a/crates/core/src/aggsigdb/memory.rs +++ b/crates/core/src/aggsigdb/memory.rs @@ -41,7 +41,7 @@ impl MemDB { tokio::spawn(Self::evict(Arc::downgrade(&this.0), evictions)); } None => { - // TODO: In Charon, `deadliner.c()` always returns `Some` + tracing::warn!("Deadliner channel is not available"); } } @@ -62,12 +62,13 @@ impl MemDB { /// Stores aggregated signed duty data set. pub async fn store(&self, duty: types::Duty, set: types::SignedDataSet) -> Result<(), Error> { + let mut data = self.0.data.lock().await; + // TODO(charon): Distinguish between no deadline supported vs already expired. let _ = self.0.deadliner.add(duty.clone()).await; - let mut data = self.0.data.lock().await; - // NOTE: Partial insertions on error match the semantics of Charon. + let mut inserted = false; let result = set.into_iter().try_for_each(|(pub_key, signed_data)| { let key = (duty.clone(), pub_key); @@ -78,20 +79,25 @@ impl MemDB { Entry::Occupied(_) => Ok(()), Entry::Vacant(slot) => { slot.insert(signed_data); + inserted = true; Ok(()) } } }); - // TODO: Optimize wake to only occur if new data was actually inserted, - // and to only wake those who are waiting for the specific duty and pubkey, - // rather than all waiters. - self.0.notify.notify_waiters(); + if inserted { + // TODO: Optimize to only wake those who are waiting for the specific duty and + // pubkey, rather than all waiters. + self.0.notify.notify_waiters(); + } result } /// Blocks and returns the aggregated signed duty data when available. + /// + /// Might block indefinitely if no data is ever stored for the given duty + /// and public key. pub async fn wait_for( &self, duty: types::Duty,