From f4691f8cda5ccbbd695427caa62833dbdfe3b8c2 Mon Sep 17 00:00:00 2001 From: Serhii Volovyk Date: Thu, 12 Mar 2026 12:52:11 +0200 Subject: [PATCH 1/4] do not recycle presignatures --- .../node/src/protocol/presignature.rs | 34 +++++++----- .../node/src/protocol/signature.rs | 23 +++----- chain-signatures/node/src/protocol/triple.rs | 43 ++++++++------- .../node/src/storage/protocol_storage.rs | 55 ------------------- 4 files changed, 51 insertions(+), 104 deletions(-) diff --git a/chain-signatures/node/src/protocol/presignature.rs b/chain-signatures/node/src/protocol/presignature.rs index b915b5671..acca5b7cb 100644 --- a/chain-signatures/node/src/protocol/presignature.rs +++ b/chain-signatures/node/src/protocol/presignature.rs @@ -682,6 +682,7 @@ impl PresignatureSpawner { mut cfg: watch::Receiver, ongoing_gen_tx: watch::Sender, ) { + let mut last_active_warn: Option = None; let mut stockpile_interval = time::interval(Duration::from_millis(100)); let mut expiration_interval = tokio::time::interval(Duration::from_secs(1)); let mut posits = self.msg.subscribe_presignature_posit().await; @@ -720,21 +721,26 @@ impl PresignatureSpawner { self.ongoing_owned.remove(&id); let _ = ongoing_gen_tx.send(self.ongoing.len()); } - _ = stockpile_interval.tick(), if active.len() >= self.threshold => { - self.stockpile(&active, &protocol).await; - let _ = ongoing_gen_tx.send(self.ongoing.len()); - - crate::metrics::storage::NUM_PRESIGNATURES_MINE - - .set(self.len_mine().await as i64); - crate::metrics::storage::NUM_PRESIGNATURES_TOTAL - - .set(self.len_generated().await as i64); - crate::metrics::protocols::NUM_PRESIGNATURE_GENERATORS_TOTAL - - .set( - self.len_potential().await as i64 - self.len_generated().await as i64, + _ = stockpile_interval.tick() => { + if active.len() >= self.threshold { + last_active_warn = None; + self.stockpile(&active, &protocol).await; + let _ = ongoing_gen_tx.send(self.ongoing.len()); + + crate::metrics::storage::NUM_PRESIGNATURES_MINE + .set(self.len_mine().await as i64); + crate::metrics::storage::NUM_PRESIGNATURES_TOTAL + .set(self.len_generated().await as i64); + crate::metrics::protocols::NUM_PRESIGNATURE_GENERATORS_TOTAL + .set(self.len_potential().await as i64 - self.len_generated().await as i64); + } else if last_active_warn.map_or(true, |i: Instant| i.elapsed() > Duration::from_secs(60)) { + tracing::warn!( + ?active, + threshold = self.threshold, + "not enough active participants to generate presignatures" ); + last_active_warn = Some(Instant::now()); + } } Ok(()) = cfg.changed() => { protocol = cfg.borrow().protocol.clone(); diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index dfcac4e02..ed378ffc7 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -272,14 +272,16 @@ impl SignOrganizer { let (presignature_id, presignature, active) = if is_proposer { tracing::info!(?sign_id, round = ?state.round, "proposer waiting for presignature"); let active = active.iter().copied().collect::>(); - let mut recycle = Vec::new(); let remaining = state.budget.remaining(); let fetch = tokio::time::timeout(remaining, async { loop { if let Some(taken) = ctx.presignatures.take_mine(ctx.me).await { let participants = intersect_vec(&[&taken.artifact.participants, &active]); if participants.len() < ctx.threshold { - recycle.push(taken); + tracing::warn!( + ?sign_id, + "discarding presignature due to inactive participants" + ); continue; } @@ -290,13 +292,6 @@ impl SignOrganizer { }) .await; - let presignatures = ctx.presignatures.clone(); - tokio::spawn(async move { - for taken in recycle { - presignatures.recycle_mine(me, taken).await; - } - }); - let (taken, participants) = match fetch { Ok(value) => value, Err(_) => { @@ -622,9 +617,8 @@ impl SignPositor { if counter.enough_rejects(ctx.threshold) { tracing::warn!(?sign_id, ?round, ?from, "received enough REJECTs, reorganizing"); - if let Some(taken) = presignature { - tracing::warn!(?sign_id, "recycling presignature due to REJECTs"); - ctx.presignatures.recycle_mine(ctx.me, taken).await; + if let Some(_taken) = presignature { + tracing::warn!(?sign_id, "discarding presignature due to REJECTs"); } state.bump_round(); return SignPhase::Organizing(SignOrganizer); @@ -664,9 +658,8 @@ impl SignPositor { ?round, "proposer posit deadline reached, expiring round" ); - if let Some(taken) = presignature { - tracing::warn!(?sign_id, "recycling presignature due to proposer timeout"); - ctx.presignatures.recycle_mine(ctx.me, taken).await; + if let Some(_taken) = presignature { + tracing::warn!(?sign_id, "discarding presignature due to proposer timeout"); } } else { tracing::warn!(?sign_id, me=?ctx.me, ?proposer, "deliberator posit timeout waiting for Start, reorganizing"); diff --git a/chain-signatures/node/src/protocol/triple.rs b/chain-signatures/node/src/protocol/triple.rs index 1ee0ae81a..ac5e1ecbb 100644 --- a/chain-signatures/node/src/protocol/triple.rs +++ b/chain-signatures/node/src/protocol/triple.rs @@ -556,10 +556,6 @@ impl TripleSpawner { /// Stockpile triples if the amount of unspent triples is below the minimum /// and the maximum number of all ongoing generation protocols is below the maximum. async fn stockpile(&mut self, participants: &[Participant], cfg: &ProtocolConfig) { - if participants.len() < self.threshold { - return; - } - let not_enough_triples = { // Stopgap to prevent too many triples in the system. This should be around min_triple*nodes*2 // for good measure so that we have enough triples to do presig generation while also maintain @@ -591,6 +587,7 @@ impl TripleSpawner { let mut active = mesh_state.borrow().active().keys_vec(); let mut protocol = cfg.borrow().protocol.clone(); + let mut last_active_warn = None; loop { tokio::select! { @@ -619,22 +616,28 @@ impl TripleSpawner { self.ongoing_introduced.remove(&id); let _ = ongoing_gen_tx.send(self.ongoing.len()); } - _ = stockpile_interval.tick(), if active.len() >= self.threshold => { - self.stockpile(&active, &protocol).await; - let _ = ongoing_gen_tx.send(self.ongoing.len()); - - crate::metrics::storage::NUM_TRIPLES_MINE - - .set(self.len_mine().await as i64); - crate::metrics::storage::NUM_TRIPLES_TOTAL - - .set(self.triple_storage.len_generated().await as i64); - crate::metrics::protocols::NUM_TRIPLE_GENERATORS_INTRODUCED - - .set(self.len_introduced() as i64); - crate::metrics::protocols::NUM_TRIPLE_GENERATORS_TOTAL - - .set(self.len_ongoing() as i64); + _ = stockpile_interval.tick() => { + if active.len() >= self.threshold { + last_active_warn = None; + self.stockpile(&active, &protocol).await; + let _ = ongoing_gen_tx.send(self.ongoing.len()); + + crate::metrics::storage::NUM_TRIPLES_MINE + .set(self.len_mine().await as i64); + crate::metrics::storage::NUM_TRIPLES_TOTAL + .set(self.triple_storage.len_generated().await as i64); + crate::metrics::protocols::NUM_TRIPLE_GENERATORS_INTRODUCED + .set(self.len_introduced() as i64); + crate::metrics::protocols::NUM_TRIPLE_GENERATORS_TOTAL + .set(self.len_ongoing() as i64); + } else if last_active_warn.map_or(true, |i: Instant| i.elapsed() > Duration::from_secs(60)) { + tracing::warn!( + ?active, + threshold = self.threshold, + "not enough active participants to generate triples" + ); + last_active_warn = Some(Instant::now()); + } } Ok(()) = cfg.changed() => { protocol = cfg.borrow().protocol.clone(); diff --git a/chain-signatures/node/src/storage/protocol_storage.rs b/chain-signatures/node/src/storage/protocol_storage.rs index a2ce74feb..80befdf0e 100644 --- a/chain-signatures/node/src/storage/protocol_storage.rs +++ b/chain-signatures/node/src/storage/protocol_storage.rs @@ -620,61 +620,6 @@ impl ProtocolStorage { } } - /// Return a taken artifact back to the available pool. - pub async fn recycle_mine(&self, me: Participant, taken: ArtifactTaken) -> bool { - const SCRIPT: &str = r#" - local artifact_key = KEYS[1] - local mine_key = KEYS[2] - local artifact_id = ARGV[1] - local artifact = ARGV[2] - - -- Add back to artifact hash map - redis.call("HSET", artifact_key, artifact_id, artifact) - - -- Add back to mine set - redis.call("SADD", mine_key, artifact_id) - - return 1 - "#; - - let start = Instant::now(); - let (artifact, mut dropper) = taken.take(); - // We manually handle the return, so we don't want the dropper to unreserve it. - dropper.dropper.take(); - - let id = artifact.id(); - let Some(mut conn) = self.connect().await else { - tracing::warn!(id, "failed to return artifact: connection failed"); - return false; - }; - - let result: Result = redis::Script::new(SCRIPT) - .key(&self.artifact_key) - .key(owner_key(&self.owner_keys, me)) - .arg(id) - .arg(artifact) - .invoke_async(&mut conn) - .await; - - let elapsed = start.elapsed(); - crate::metrics::storage::REDIS_LATENCY - .with_label_values(&[A::METRIC_LABEL, "return_mine"]) - .observe(elapsed.as_millis() as f64); - - match result { - Ok(_) => { - self.reserved.write().await.remove(&id); - self.used.write().await.remove(&id); - tracing::info!(id, ?elapsed, "returned mine artifact"); - true - } - Err(err) => { - tracing::warn!(id, ?err, ?elapsed, "failed to return mine artifact"); - false - } - } - } - /// Check if an artifact is reserved. pub async fn contains_reserved(&self, id: A::Id) -> bool { self.reserved.read().await.contains(&id) From 59808b834a62ece63aafe548a4a0d11140bfe4c9 Mon Sep 17 00:00:00 2001 From: Serhii Volovyk Date: Thu, 12 Mar 2026 13:10:57 +0200 Subject: [PATCH 2/4] add .vscode to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2d8ff61c0..3106d8414 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .direnv .DS_Store .idea +.vscode tmp *.log From feb9b136faa8077d9b7b41df4119d99e07f288e9 Mon Sep 17 00:00:00 2001 From: Serhii Volovyk Date: Thu, 12 Mar 2026 13:12:29 +0200 Subject: [PATCH 3/4] clippy --- chain-signatures/node/src/protocol/presignature.rs | 2 +- chain-signatures/node/src/protocol/triple.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chain-signatures/node/src/protocol/presignature.rs b/chain-signatures/node/src/protocol/presignature.rs index acca5b7cb..b906b80a5 100644 --- a/chain-signatures/node/src/protocol/presignature.rs +++ b/chain-signatures/node/src/protocol/presignature.rs @@ -733,7 +733,7 @@ impl PresignatureSpawner { .set(self.len_generated().await as i64); crate::metrics::protocols::NUM_PRESIGNATURE_GENERATORS_TOTAL .set(self.len_potential().await as i64 - self.len_generated().await as i64); - } else if last_active_warn.map_or(true, |i: Instant| i.elapsed() > Duration::from_secs(60)) { + } else if last_active_warn.is_none_or(|i: Instant| i.elapsed() > Duration::from_secs(60)) { tracing::warn!( ?active, threshold = self.threshold, diff --git a/chain-signatures/node/src/protocol/triple.rs b/chain-signatures/node/src/protocol/triple.rs index ac5e1ecbb..35b5ba970 100644 --- a/chain-signatures/node/src/protocol/triple.rs +++ b/chain-signatures/node/src/protocol/triple.rs @@ -630,7 +630,7 @@ impl TripleSpawner { .set(self.len_introduced() as i64); crate::metrics::protocols::NUM_TRIPLE_GENERATORS_TOTAL .set(self.len_ongoing() as i64); - } else if last_active_warn.map_or(true, |i: Instant| i.elapsed() > Duration::from_secs(60)) { + } else if last_active_warn.is_none_or(|i: Instant| i.elapsed() > Duration::from_secs(60)) { tracing::warn!( ?active, threshold = self.threshold, From 7a8815bcc3eab351e5a4f426c10825cb4fcddf3f Mon Sep 17 00:00:00 2001 From: Serhii Volovyk Date: Tue, 17 Mar 2026 17:15:47 +0200 Subject: [PATCH 4/4] extend comment --- chain-signatures/node/src/protocol/signature.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index 19544fd6f..2d2f7765e 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -288,6 +288,9 @@ impl SignOrganizer { if participants.len() < ctx.threshold { tracing::warn!( ?sign_id, + id = taken.artifact.id, + ?holders, + ?active, "discarding presignature due to inactive participants" ); continue;