Skip to content

refactor(bls): use pointer arrays#253

Closed
spiral-ladder wants to merge 4 commits intobing/native-mt-blst-v2from
bing/native-mt-blst-pointers
Closed

refactor(bls): use pointer arrays#253
spiral-ladder wants to merge 4 commits intobing/native-mt-blst-v2from
bing/native-mt-blst-pointers

Conversation

@spiral-ladder
Copy link
Copy Markdown
Collaborator

Seems like copying value arrays might be one of the causes of RSS strain on feat3 which is running our native bls implementation for about ~48 hours now. See thread on Discord for more details.

The suggestion on the thread was to use
napi_adjust_external_memory - reason is that the native memory management is opaque to V8's GC. But I highly doubt this is the reason, since blst-ts also does not use this API.

It is likely, it seems, that it's due to large amounts of allocations causing memory fragmentation when memory is freed/cleared.

Seems like copying value arrays might be one of the causes of RSS strain on
feat3 which is running our native bls implementation for about ~48 hours
now. See [thread](https://discord.com/channels/593655374469660673/1479085402395508976/1484525564181090426) on Discord
for more details.

The suggestion on the thread was to use
[`napi_adjust_external_memory`](https://nodejs.org/api/n-api.html#napi-adjust-external-memory) - reason is that 
the native memory management is opaque to V8's GC. But I highly doubt
this is the reason, since `blst-ts` also does not use this API.

It is likely, it seems, that it's due to large amounts of allocations
causing memory fragmentation when memory is freed/cleared.
@spiral-ladder spiral-ladder self-assigned this Mar 20, 2026
@spiral-ladder spiral-ladder requested a review from a team as a code owner March 20, 2026 16:22
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant refactoring to the BLS (Boneh-Lynn-Shacham) implementation by transitioning from using value arrays to pointer arrays for handling cryptographic signatures and public keys. This change is a strategic effort to mitigate potential memory strain and fragmentation issues that have been observed in the native BLS operations, thereby improving the overall memory efficiency and stability of the system.

Highlights

  • Memory Optimization: Refactored BLS operations to use pointer arrays instead of value arrays for signatures and public keys, aiming to reduce memory consumption and fragmentation.
  • API Changes: Updated function signatures across various BLS aggregation and verification methods to accept slices of pointers ([]const *const Type) instead of slices of values ([]const Type).
  • Consistency Across Modules: Applied the pointer array refactor consistently in NAPI bindings, core BLS logic, state transition processes, and test utilities.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request refactors BLS signature and public key aggregation functions across the codebase to use slices of pointers ([]const *const Type) instead of slices of values ([]const Type). This change affects multiple files in bindings/napi/blst.zig, src/bls/, src/state_transition/, and test files, requiring adjustments in memory allocation and how objects are referenced. A critical bug was identified in src/bls/Signature.zig where pks[0] is accessed without checking if the pks slice is empty, which could lead to a crash. Additionally, several medium-severity improvement opportunities were noted in bindings/napi/blst.zig to explicitly pass options to env.unwrap functions, adhering to the repository's style guide for robustness and readability.

Comment thread src/bls/Signature.zig
Comment on lines +76 to 77
pks[0],
pks_validate,
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.

critical

CRITICAL: The code is dereferencing a pointer without ensuring that the pks slice is not empty. Accessing pks[0] when pks is empty will cause a crash. Add a check to ensure pks.len > 0 before accessing pks[0].

    if (n_elems == 0 or msgs.len != n_elems) {
        return BlstError.VerifyFail;
    }
    if (pks.len == 0) {
        return BlstError.VerifyFail; // Add this check
    }
    var pairing = Pairing.init(buffer, true, dst);
    try pairing.aggregate(
        pks[0],
        pks_validate,
        self,
        sig_groupcheck,
        &msgs[0],
        null,
    );
References
  1. Ensure that slices are not empty before accessing their elements to prevent out-of-bounds access.

Comment thread bindings/napi/blst.zig
const sig = try env.unwrap(Signature, sig_value);
sigs[i] = sig.*;
sigs[i] = try env.unwrap(Signature, sig_value);
}
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.

medium

MEDIUM: According to the Lodestar State Transition Zig Style Guide, it's better to explicitly pass options to library functions instead of relying on defaults. While napi.newInstance might have sensible defaults, explicitly passing options improves readability and prevents potential bugs if the library changes its defaults in the future. See Repository Style Guide, lines 170-173.

        sigs[i] = try env.unwrap(Signature, sig_value, .{}); // Explicitly pass options, even if empty

References
  1. Explicitly pass options to library functions at the call site, instead of relying on the defaults. This improves readability and avoids latent bugs if the library changes its defaults. (link)

Comment thread bindings/napi/blst.zig

const pk_value = try pks_array.getElement(@intCast(i));
pk_ptrs[i] = try env.unwrap(PublicKey, pk_value);
pks[i] = try env.unwrap(PublicKey, pk_value);
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.

medium

MEDIUM: Following the style guide, explicitly pass options to env.unwrap to ensure that the code is robust against future changes in the library's default behavior. See Repository Style Guide, lines 170-173.

        pks[i] = try env.unwrap(PublicKey, pk_value, .{}); // Explicitly pass options

References
  1. Explicitly pass options to library functions at the call site, instead of relying on the defaults. This improves readability and avoids latent bugs if the library changes its defaults. (link)

Comment thread bindings/napi/blst.zig
const pk_value = try pks_array.getElement(@intCast(i));
const pk = try env.unwrap(PublicKey, pk_value);
pks[i] = pk.*;
pks[i] = try env.unwrap(PublicKey, pk_value);
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.

medium

MEDIUM: Per the style guide, explicitly pass options to env.unwrap to make the code more readable and prevent potential issues if the library's defaults change. See Repository Style Guide, lines 170-173.

        pks[i] = try env.unwrap(PublicKey, pk_value, .{}); // Explicitly pass options

References
  1. Explicitly pass options to library functions at the call site, instead of relying on the defaults. This improves readability and avoids latent bugs if the library changes its defaults. (link)

Comment thread bindings/napi/blst.zig
const sig_value = try sigs_array.getElement(@intCast(i));
const sig = try env.unwrap(Signature, sig_value);
sigs[i] = sig.*;
sigs[i] = try env.unwrap(Signature, sig_value);
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.

medium

MEDIUM: For consistency and to adhere to the style guide, explicitly pass options to env.unwrap. See Repository Style Guide, lines 170-173.

        sigs[i] = try env.unwrap(Signature, sig_value, .{}); // Explicitly pass options

References
  1. Explicitly pass options to library functions at the call site, instead of relying on the defaults. This improves readability and avoids latent bugs if the library changes its defaults. (link)

Comment thread bindings/napi/blst.zig
const pk_value = try pks_array.getElement(@intCast(i));
const pk = try env.unwrap(PublicKey, pk_value);
pks[i] = pk.*;
pks[i] = try env.unwrap(PublicKey, pk_value);
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.

medium

MEDIUM: To improve code clarity and prevent potential issues, explicitly pass options to env.unwrap. See Repository Style Guide, lines 170-173.

        pks[i] = try env.unwrap(PublicKey, pk_value, .{}); // Explicitly pass options

References
  1. Explicitly pass options to library functions at the call site, instead of relying on the defaults. This improves readability and avoids latent bugs if the library changes its defaults. (link)

Copy link
Copy Markdown
Contributor

@lodekeeper-z lodekeeper-z left a comment

Choose a reason for hiding this comment

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

Review: refactor(bls): use pointer arrays

Good refactor. The motivation is sound — BLS point structs are large (P1Affine = 48 bytes, P2Affine = 96 bytes), and the old code was copying them out of NAPI-wrapped objects just to pass to aggregation/verification functions that ultimately need pointers anyway. Pointer slices eliminate those copies and align the core BLS API with how ThreadPool.aggregateVerify already works.

What's good

  1. fastAggregateVerifyPreAggregated cleanup — The old @ptrCast from *const PublicKey to [*]const PublicKey then slicing to [0..1] was a type-punning hack. The new &[_]*const PublicKey{pk} is clean and safe. Nice.

  2. NAPI bindingsenv.unwrap() already returns a pointer to the wrapped object, so storing *const T directly instead of copying sig.* / pk.* is the right call. Less work, less memory.

  3. ConsistencyThreadPool.aggregateVerify already took []const *PublicKey. This brings Signature.aggregateVerify, fastAggregateVerify, AggregatePublicKey.aggregate, and AggregateSignature.aggregate in line.

One thing to verify

blst_aggregateSerializedPublicKeys — This function deserializes into a pks: []PublicKey array, then builds a separate pk_ptrs pointing into it. That's correct, but it's now two allocations where there was one. For the NAPI hot path (if this is called in tight loops), worth confirming the extra alloc is negligible vs the deserialization cost. I expect it is — just flagging.

On Gemini's comments

  • The "critical" about empty pks in aggregateVerify is a false positive — the existing guard if (n_elems == 0 or msgs.len != n_elems) at the top of the function already handles this. No change needed.
  • The env.unwrap "explicit options" suggestions are noise — unwrap doesn't take an options parameter in zapi.

Devil's advocate: why not []*const T everywhere?

The PR uses []const *const PublicKey in some places and []const *PublicKey in others (ThreadPool). This is fine for now since the const-ness propagates correctly through Zig's type system, but it'd be worth settling on one convention as more APIs adopt pointer slices. Not blocking.

Overall: clean, mechanical, correct. 👍

This was referenced Mar 24, 2026
@GrapeBaBa
Copy link
Copy Markdown
Contributor

I am not sure using pointer arrays is better than value arrays in native zig client use case, maybe lose prefetch, cache friendly. Is there a bench comparison for both in native zig test?

@spiral-ladder
Copy link
Copy Markdown
Collaborator Author

consolidating in #306

@spiral-ladder
Copy link
Copy Markdown
Collaborator Author

@GrapeBaBa your observation is right; this didn't make any noticeable difference and was mainly an attempt at diagnosing rss issues. closing this

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