Skip to content

Check Spark spend ZK proofs on acceptance into the mempool and cache the result, so it's not re-checked later#1665

Merged
reubenyap merged 4 commits into
masterfrom
zk-proofs-refactor
Oct 23, 2025
Merged

Check Spark spend ZK proofs on acceptance into the mempool and cache the result, so it's not re-checked later#1665
reubenyap merged 4 commits into
masterfrom
zk-proofs-refactor

Conversation

@psolstice

Copy link
Copy Markdown
Contributor

PR intention

When a lot of Spark spend transaction are mined the miner has a problem with performance. ZK proofs for a Spark spend are checked once on acceptance into the mempool, and then every time a block is updated and getblocktemplate is called every transaction is checked once again making the complexity of multiple checks O(N^2) where N is the number of Spark spend transaction in the block. This PR caches the result of the first check and makes the check multithreaded.

Code changes brief

Use thread pool to defer the checks and cache the result.

@coderabbitai

coderabbitai Bot commented Sep 15, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds asynchronous, cached Spark spend-proof verification with a background thread pool and per-transaction check state, exposes spark::ShutdownSparkState(), integrates that shutdown call into the wallet shutdown path, and adds a thread-pool shutdown-state accessor.

Changes

Cohort / File(s) Summary
Spark proof verification async + cache
src/spark/state.cpp
Introduces ProofCheckState, gCheckedSparkSpendTransactions (with locking), and gCheckProofThreadPool; implements async proof checks, caching of pass/fail/in-progress, waiting for stateful checks, soft-accept behavior for non-stateful paths, switches spends to shared_ptr, adds includes, and implements ShutdownSparkState().
Public API for shutdown
src/spark/state.h
Declares void ShutdownSparkState() in namespace spark (placed after CheckSparkTransaction) with no other API changes.
Wallet shutdown integration
src/spark/sparkwallet.cpp
Adds #include "state.h" and calls spark::ShutdownSparkState() from CSparkWallet::FinishTasks() after shutting down the wallet thread pool.
Thread pool state accessor
src/liblelantus/threadpool.h
Adds bool IsPoolShutdown() to ParallelOpThreadPool that returns the internal shutdown flag under lock for safe inspection.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Node
  participant Wallet as CSparkWallet
  participant Spark as spark::state
  participant Pool as gCheckProofThreadPool

  rect rgb(240,248,255)
  Node->>Wallet: Begin shutdown (FinishTasks)
  Wallet->>Wallet: Shutdown wallet thread pool
  Wallet->>Spark: spark::ShutdownSparkState()
  Spark->>Pool: Request pool shutdown
  Pool-->>Spark: Pool stops accepting tasks / drains
  Note over Wallet,Spark: Ensures background proof tasks stop before exit
  end
Loading
sequenceDiagram
  autonumber
  participant Validator as Tx Validation
  participant Cache as gCheckedSparkSpendTransactions
  participant Pool as gCheckProofThreadPool
  participant Verifier as Proof Verifier

  Validator->>Cache: Check tx state
  alt previously failed
    Cache-->>Validator: Reject immediately
  else previously passed
    Cache-->>Validator: Accept, skip verify
  else in-progress
    alt needs stateful sigma check
      Validator->>Cache: Wait for completion
      Cache-->>Validator: Return result
    else non-stateful path
      Cache-->>Validator: Soft-accept (true)
    end
  else not checked
    alt non-stateful path
      Validator->>Pool: Enqueue async verification
      Pool->>Verifier: Verify proofs (async)
      Verifier-->>Cache: Update pass/fail
    else stateful required
      Validator->>Verifier: Verify synchronously
      Verifier-->>Cache: Update pass/fail
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix sparktest segfault #1575 — Modifies Spark shutdown/thread-pool lifecycle; closely related to adding and invoking ShutdownSparkState() during wallet shutdown.

Suggested reviewers

  • levonpetrosyan93

Poem

I thump a hop as threads unwind,
Proofs checked slow or cached in mind,
Pools hush, maps close each tiny gate,
I tuck my whiskers, await the late.
Spark sleeps now — carrot dreams align. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change: verifying Spark spend ZK proofs on mempool acceptance and caching the result to avoid repeated re-checks. It directly matches the changeset (adding caching and multithreaded/deferred checks) and is clear and specific enough for a teammate scanning history. The phrasing is concise and contains no irrelevant noise.
Description Check ✅ Passed The PR description follows the repository template by including the mandatory "PR intention" and the "Code changes brief" sections; the intention clearly states the performance problem (O(N^2) re-checks) and the high-level solution (cache the first check and use a thread pool). The Code changes brief concisely summarizes the architectural change (deferred checks via thread pool and result caching), so required information is present and coherent with the raw change summary. Overall the description is complete and on-topic.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch zk-proofs-refactor

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/spark/sparkwallet.cpp (1)

3-3: Remove duplicate include of "state.h"

The file "state.h" is included on line 3 and again on line 11. This duplicate include should be removed.

Apply this diff to remove the duplicate:

 #include "../liblelantus/threadpool.h"
 #include "sparkwallet.h"
-#include "state.h"
 #include "../wallet/wallet.h"
src/spark/state.cpp (2)

12-27: Thread-safety concern with shared_ptr usage in ProofCheckState

The checkInProgress field is accessed from multiple threads but the shared_ptr itself may not be thread-safe for concurrent read/write operations. While the underlying boost::future is thread-safe, updating the shared_ptr pointer itself requires synchronization.

The current code does lock cs_checkedSparkSpendTransactions when accessing the map, which should provide the necessary synchronization. However, consider documenting this requirement clearly.

Consider adding a comment to clarify the synchronization requirements:

 struct ProofCheckState {
     // if this is true, then the proof was already checked, no need to check again
     bool fChecked;
 
     // result of the check (if fChecked is true)
     bool fResult;
 
-    // if this is non-null, then the proof is being checked right now
+    // if this is non-null, then the proof is being checked right now
+    // Note: Access to this field must be protected by cs_checkedSparkSpendTransactions
     std::shared_ptr<boost::future<bool>> checkInProgress;
 };

932-934: Minimal shutdown implementation

The ShutdownSparkState() function only shuts down the thread pool. Consider whether the cache (gCheckedSparkSpendTransactions) should also be cleared on shutdown to free memory.

 void ShutdownSparkState() {
     gCheckProofThreadPool.Shutdown();
+    {
+        LOCK(cs_checkedSparkSpendTransactions);
+        gCheckedSparkSpendTransactions.clear();
+    }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55520fe and 1226965.

📒 Files selected for processing (3)
  • src/spark/sparkwallet.cpp (2 hunks)
  • src/spark/state.cpp (7 hunks)
  • src/spark/state.h (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/spark/sparkwallet.cpp (1)
src/spark/state.cpp (2)
  • ShutdownSparkState (932-934)
  • ShutdownSparkState (932-932)
src/spark/state.h (1)
src/spark/state.cpp (2)
  • ShutdownSparkState (932-934)
  • ShutdownSparkState (932-932)
src/spark/state.cpp (2)
src/libspark/spend_transaction.cpp (4)
  • verify (228-233)
  • verify (228-230)
  • verify (238-408)
  • verify (238-241)
src/batchproof_container.cpp (2)
  • verify (48-56)
  • verify (48-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-guix-aarch64-linux-gnu
  • GitHub Check: build-guix-x86_64-linux-gnu
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-mac-cmake
  • GitHub Check: build-linux-cmake
  • GitHub Check: Create Source Distribution
  • GitHub Check: build-windows-cmake
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-guix-x86_64-linux-gnu
  • GitHub Check: build-guix-aarch64-linux-gnu
  • GitHub Check: Create Source Distribution
🔇 Additional comments (10)
src/spark/state.h (1)

86-87: LGTM!

The addition of the ShutdownSparkState() declaration is appropriate for graceful shutdown of the new thread pool used for asynchronous proof verification.

src/spark/sparkwallet.cpp (1)

89-90: LGTM!

The addition of spark::ShutdownSparkState() after shutting down the wallet's thread pool ensures proper cleanup of the global proof verification thread pool.

src/spark/state.cpp (8)

1-1: LGTM!

The addition of threadpool.h and <set> includes is appropriate for the new asynchronous proof verification functionality.

Also applies to: 8-8


608-611: Ownership transfer to shared_ptr for async verification

The change from unique_ptr to shared_ptr with std::make_shared is necessary for the asynchronous verification model where the spend object needs to be accessed from the thread pool after the function returns.


675-682: Soft error handling for non-stateful checks

The change to return true instead of failing when fStatefulSigmaCheck is false allows mempool acceptance to proceed with deferred verification. This is consistent with the PR's goal of avoiding redundant checks.


742-743: Consistent soft error return for missing cover sets

The conditional return based on fStatefulSigmaCheck is consistent with the soft error approach for mempool acceptance.


784-790: Cache update after verification

The code correctly updates the cache with the verification result, but only when !fChecked. This ensures the cache is populated only once per transaction.


793-795: Early return optimization for non-stateful checks

The early return when !fStatefulSigmaCheck avoids unnecessary work for mempool acceptance, relying on the asynchronous verification to complete in the background.


689-693: Confirm mempool soft-error handling (fStatefulSigmaCheck == false)

The code returns true when index->GetBlockHash() != idAndHash.second && !fStatefulSigmaCheck (src/spark/state.cpp, ~lines 689–693). I found only this occurrence — locate where fStatefulSigmaCheck is set and verify the mempool acceptance path performs the full stateful-sigma validation before accepting the tx; if it does not, add stricter validation here or reject in mempool.


555-586: No deadlock — unlock/get/lock sequence verified safe

Searched the repo: gCheckedSparkSpendTransactions is declared at src/spark/state.cpp:24 and used at ~557–586 (the unlock/get/lock path) and ~763–789 (posting the future). No erase/clear/swap or other future->get() calls were found. The code copies checkState.checkInProgress into a local shared_ptr before unlocking, so waiting on future outside the lock is safe; the only remaining invalidation risk would be an explicit erase of the map entry — none exists.

Comment thread src/spark/state.cpp
Comment on lines +753 to +778
if (fChecked) {
// if we are here, then the proof was already checked and it passed
passVerify = true;
}
else {
if (fStatefulSigmaCheck) {
// we need the answer now, so verify and execute
passVerify = spark::SpendTransaction::verify(*spend, cover_sets);
}
else {
LOCK(cs_checkedSparkSpendTransactions);
// put the proof into the thread pool for verification
auto future = gCheckProofThreadPool.PostTask([spend, cover_sets]() {
try {
return spark::SpendTransaction::verify(*spend, cover_sets);
} catch (const std::exception &) {
return false;
}
});
auto &checkState = gCheckedSparkSpendTransactions[hashTx];
checkState.fChecked = false;
checkState.checkInProgress = std::make_shared<boost::future<bool>>(std::move(future));
// return true for now, the result will be processed later
return true;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify thread pool task completion before shutdown

The asynchronous proof verification posts tasks to gCheckProofThreadPool but doesn't guarantee they complete before shutdown. The ShutdownSparkState() function should ensure all pending tasks are completed or cancelled properly.

Consider ensuring that the thread pool's Shutdown() method waits for all pending tasks to complete or provides a mechanism to cancel them gracefully.


🏁 Script executed:

#!/bin/bash
# Check the implementation of ParallelOpThreadPool::Shutdown
rg "class ParallelOpThreadPool" -A 30 --type cpp
rg "Shutdown\(\)" src/liblelantus/threadpool.h -A 10

Length of output: 3333


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/liblelantus/threadpool.h ==="
if [ -f src/liblelantus/threadpool.h ]; then
  sed -n '1,400p' src/liblelantus/threadpool.h || true
else
  echo "MISSING: src/liblelantus/threadpool.h"
fi

echo
echo "=== src/spark/state.cpp (first 480 lines) ==="
if [ -f src/spark/state.cpp ]; then
  sed -n '1,480p' src/spark/state.cpp || true
else
  echo "MISSING: src/spark/state.cpp"
fi

echo
echo "=== repo search for symbols ==="
rg -n "gCheckProofThreadPool" || true
rg -n "gCheckedSparkSpendTransactions" || true
rg -n "ShutdownSparkState" || true
rg -n "ShutdownSpark" || true

Length of output: 22036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/spark/state.cpp:520-820 ==="
sed -n '520,820p' src/spark/state.cpp || true

echo
echo "=== src/spark/state.cpp:820-1000 ==="
sed -n '820,1000p' src/spark/state.cpp || true

echo
echo "=== usages ==="
rg -n "checkInProgress" src/spark/state.cpp || true
rg -n "gCheckedSparkSpendTransactions" src/spark/state.cpp || true
rg -n "ShutdownSparkState" -n || true

Length of output: 19338


Reject new postings to the thread pool after shutdown and ensure in-flight tasks are drained.

ParallelOpThreadPool::Shutdown() (src/liblelantus/threadpool.h) joins existing worker threads and will drain tasks already enqueued, but PostTask() does not check the pool's shutdown flag and can enqueue/start new tasks after Shutdown() is called. CheckSparkSpendTransaction posts tasks at src/spark/state.cpp:~765; ShutdownSparkState() only calls gCheckProofThreadPool.Shutdown() at src/spark/state.cpp:932.

Fix: make PostTask() refuse/reject new submissions when shutdown==true (return a ready failed/empty future or throw) or add an explicit "accepting_tasks" gate and flip it before calling ShutdownSparkState(); ensure no code posts to gCheckProofThreadPool after that gate is set.

🤖 Prompt for AI Agents
In src/spark/state.cpp around lines 753-778, CheckSparkSpendTransaction posts
verification tasks to gCheckProofThreadPool without preventing new submissions
after shutdown; update the codebase so PostTask refuses new submissions when the
pool is shutting down (e.g., check a shutdown flag and return an
immediately-ready failed/empty future or throw) or add an explicit
accepting_tasks gate that is flipped off before ShutdownSparkState() calls
gCheckProofThreadPool.Shutdown(); then update callers (including this block) to
handle a rejected PostTask (do not assume a task was enqueued — either fall back
to synchronous verification or return false/appropriate state) and ensure
ShutdownSparkState() flips the gate before calling Shutdown() so no new tasks
are posted and in-flight tasks are drained by the existing Shutdown logic.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/liblelantus/threadpool.h (2)

9-9: Missing header for std::find_if

ThreadProc() uses std::find_if but <algorithm> is commented out. Restore it to avoid non‑portable builds.

-//#include <algorithm>
+#include <algorithm>

81-95: Reject tasks after shutdown in PostTask

PostTask must not restart threads or enqueue work when shutdown==true; make it return an exceptional future or throw instead.

     boost::future<Result> PostTask(std::function<Result()> task) {
         boost::packaged_task<Result> packagedTask(std::move(task));
         boost::future<Result> ret = packagedTask.get_future();
 
         boost::mutex::scoped_lock lock(task_queue_mutex);
 
+        if (shutdown) {
+            // Prevent scheduling after shutdown
+            // Option A: throw (simple, clear)
+            throw std::runtime_error("ParallelOpThreadPool is shut down");
+            // Option B (if exceptions undesired): return an exceptional/ready future instead.
+        }
+
         // lazy start threads on first request or after shutdown
         if (threads.size() < number_of_threads)
             StartThreads();
 
         task_queue.emplace(std::move(packagedTask));
         task_queue_condition.notify_one();
 
         return std::move(ret);
     }

Audit callers that post tasks (many do not check IsPoolShutdown): src/spark/sparkwallet.cpp:490,503,638,648; src/liblelantus/lelantus_prover.cpp:155; src/batchproof_container.cpp:212,315; src/spark/state.cpp:769 (IsPoolShutdown checked at 761–766).

♻️ Duplicate comments (1)
src/spark/state.cpp (1)

764-766: Race condition: PostTask after shutdown check.

There's a potential race condition between checking IsPoolShutdown() and calling PostTask(). The thread pool could shut down between these two operations, potentially causing issues.

Consider adding a mechanism in the thread pool to reject new task submissions when shutdown is in progress, or ensure that PostTask() handles shutdown state gracefully.

🧹 Nitpick comments (3)
src/liblelantus/threadpool.h (3)

118-121: Make IsPoolShutdown const/noexcept and avoid lock-order pitfalls

Accessor should be const; locking inside a non‑const method is surprising. Also, calling it while the pool already holds the mutex would deadlock. Either make shutdown atomic or keep the lock but mark the mutex mutable. Minimal change below.

-    boost::mutex                                task_queue_mutex;
+    mutable boost::mutex                        task_queue_mutex;
@@
-    bool IsPoolShutdown() {
-        boost::mutex::scoped_lock lock(task_queue_mutex);
-        return shutdown;
-    }
+    bool IsPoolShutdown() const noexcept {
+        boost::mutex::scoped_lock lock(task_queue_mutex);
+        return shutdown;
+    }

Also applies to: 33-33


97-99: Return size_t (and noexcept) from GetNumberOfThreads

Match the member type and signal no-throw.

-    int GetNumberOfThreads() const {
-        return number_of_threads;
-    }
+    std::size_t GetNumberOfThreads() const noexcept { return number_of_threads; }

94-95: Avoid redundant std::move on local return

Let NRVO do its job; keeps intent clear.

-        return std::move(ret);
+        return ret;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1226965 and d6b7aed.

📒 Files selected for processing (2)
  • src/liblelantus/threadpool.h (1 hunks)
  • src/spark/state.cpp (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/spark/state.cpp (2)
src/libspark/spend_transaction.cpp (4)
  • verify (228-233)
  • verify (228-230)
  • verify (238-408)
  • verify (238-241)
src/batchproof_container.cpp (2)
  • verify (48-56)
  • verify (48-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-mac-cmake
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-windows-cmake
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-guix-x86_64-linux-gnu
  • GitHub Check: build-guix-aarch64-linux-gnu
  • GitHub Check: Create Source Distribution
  • GitHub Check: build-linux-cmake
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-guix-aarch64-linux-gnu
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-windows-cmake
  • GitHub Check: build-mac-cmake
  • GitHub Check: Create Source Distribution
  • GitHub Check: build-linux-cmake
  • GitHub Check: build-guix-x86_64-linux-gnu
🔇 Additional comments (12)
src/spark/state.cpp (12)

1-1: Include order for thread pool.

The thread pool header is included first, which is appropriate given its foundational role in the new asynchronous verification system.


8-8: Necessary include for transaction state tracking.

The <set> header is appropriately added to support the new caching and verification state management structures.


12-21: Well-designed proof check state structure.

The ProofCheckState structure provides a clean interface for tracking verification state with appropriate fields for completion status, results, and in-progress checks.


23-27: Thread-safe caching infrastructure.

The global map and critical section properly protect the cached verification states. The use of transaction hash as the key provides appropriate granularity for caching.


555-586: Proper cache lookup and wait handling.

The cache lookup logic correctly handles both completed and in-progress verifications. The synchronization when waiting for stateful checks is appropriate - unlocking before the blocking wait and re-locking after.


608-608: Ownership change for thread pool compatibility.

The change from unique_ptr to shared_ptr is necessary for thread pool task lifetime management, allowing the lambda capture to maintain ownership during asynchronous execution.


675-682: Soft error handling for non-stateful checks.

The differentiation between stateful and non-stateful checks is well-implemented. For non-stateful checks (mempool acceptance), missing coin groups result in soft errors that return true, deferring verification to later stages.

Also applies to: 689-693


742-744: Consistent soft error pattern.

The cover set validation follows the same soft error pattern, appropriately handling cases where verification cannot proceed during non-stateful checks.


753-782: Well-structured asynchronous verification logic.

The verification flow properly handles three cases:

  1. Already verified (cache hit)
  2. Stateful check (synchronous execution)
  3. Non-stateful check (asynchronous thread pool execution)

The task submission to the thread pool includes proper exception handling within the lambda.


787-794: Proper result caching.

The caching of verification results after completion ensures that repeated checks for the same transaction will be fast. The cleanup of the checkInProgress pointer is appropriate.


797-800: Early return for non-stateful checks.

The early return for non-stateful checks is correct - these checks only need to validate that verification can be deferred, not that it passes immediately.


936-938: Clean shutdown implementation.

The ShutdownSparkState() function provides a simple interface to shut down the thread pool. The underlying thread pool's Shutdown() method handles joining threads appropriately.

Comment thread src/spark/state.cpp
@reubenyap reubenyap merged commit 0e9c9b7 into master Oct 23, 2025
37 of 39 checks passed
@reubenyap reubenyap deleted the zk-proofs-refactor branch October 23, 2025 17:15
@coderabbitai coderabbitai Bot mentioned this pull request Mar 3, 2026
This was referenced Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants