Skip to content

feat: support bitArrays in to_napi_value#368

Open
spiral-ladder wants to merge 2 commits into
mainfrom
bing/bit-arr-napi-value
Open

feat: support bitArrays in to_napi_value#368
spiral-ladder wants to merge 2 commits into
mainfrom
bing/bit-arr-napi-value

Conversation

@spiral-ladder

Copy link
Copy Markdown
Collaborator

extracted from #347

toValue() requires support for converting bit arrays into napi values

`toValue()` requires support for converting bit arrays into napi values
@spiral-ladder spiral-ladder self-assigned this May 19, 2026
@spiral-ladder spiral-ladder requested a review from a team as a code owner May 19, 2026 09:36
@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 extends the N-API conversion logic to include support for bit arrays. By implementing a dedicated conversion path for bit vectors and bit lists, the changes ensure that these data structures are correctly represented when passed across the FFI boundary, maintaining interoperability with existing TypeScript implementations.

Highlights

  • BitArray Support: Added support for converting bit vectors and bit lists into N-API values to ensure compatibility with ssz-ts.
  • New Helper Function: Introduced the bitArrayToNapiValue function to encapsulate the creation of N-API objects containing the underlying byte array and bit length.
New Features

🧠 You can now enable Memory (public preview) 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 the 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 counterproductive. 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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request adds support for bit vectors and bit lists in the N-API bindings by introducing the bitArrayToNapiValue helper function and updating the sszValueToNapiValue logic. Feedback was provided to improve the robustness of the new function by adding assertions for argument validation, specifically to ensure that bit_len is within the range of an i64 and that the data slice length is consistent with the bit length, as required by the repository's style guide.

Comment on lines +66 to +76
fn bitArrayToNapiValue(env: napi.Env, data: []const u8, bit_len: usize) !napi.Value {
var bytes: [*]u8 = undefined;
const buf = try env.createArrayBuffer(data.len, &bytes);
@memcpy(bytes[0..data.len], data);
const uint8_array = try env.createTypedarray(.uint8, data.len, buf, 0);

const obj = try env.createObject();
try obj.setNamedProperty("uint8Array", uint8_array);
try obj.setNamedProperty("bitLen", try env.createInt64(@intCast(bit_len)));
return obj;
}

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

The bitArrayToNapiValue function is missing assertions for its arguments, which is a requirement of the repository's style guide (based on TigerStyle). Specifically, it should assert that bit_len is within the range of i64 (since it is cast to it for the N-API call) and that the data slice length is consistent with the provided bit_len.

According to the style guide:

  • "Assert all function arguments and return values, pre/postconditions and invariants."
  • "The assertion density of the code must average a minimum of two assertions per function."
  • "Split compound assertions: prefer assert(a); assert(b); over assert(a and b);."
fn bitArrayToNapiValue(env: napi.Env, data: []const u8, bit_len: usize) !napi.Value {
    std.debug.assert(bit_len <= std.math.maxInt(i64));
    std.debug.assert(data.len == (bit_len + 7) / 8);

    var bytes: [*]u8 = undefined;
    const buf = try env.createArrayBuffer(data.len, &bytes);
    @memcpy(bytes[0..data.len], data);
    const uint8_array = try env.createTypedarray(.uint8, data.len, buf, 0);

    const obj = try env.createObject();
    try obj.setNamedProperty("uint8Array", uint8_array);
    try obj.setNamedProperty("bitLen", try env.createInt64(@intCast(bit_len)));
    return obj;
}
References
  1. Assert all function arguments and return values, pre/postconditions and invariants. The assertion density of the code must average a minimum of two assertions per function. Split compound assertions: prefer assert(a); assert(b); over assert(a and b);. (link)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

accepted the assertion for data.len

@spiral-ladder

Copy link
Copy Markdown
Collaborator Author

I think we could do with unit tests for this fn at some point in a future PR

@wemeetagain

Copy link
Copy Markdown
Member

One thing I'm not sure how to handle here.
The shape of the bit array object returned by the bindings will be correct, but the prototype will be incorrect. So probably typescript will catch this as a type mismatch, but if it doesn't, and the object is used downstream as a BitArray, eg uses a bitarray method, then there will be a runtime error.

@spiral-ladder

spiral-ladder commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

One thing I'm not sure how to handle here. The shape of the bit array object returned by the bindings will be correct, but the prototype will be incorrect. So probably typescript will catch this as a type mismatch, but if it doesn't, and the object is used downstream as a BitArray, eg uses a bitarray method, then there will be a runtime error.

there's a bunch of mismatch between whether we pass raw bytes or pass objects in some APIs in our bindings, including (but not limited to) calls related to voluntary exits (see #378) and blocks (see #379)

I thought about it a while and chose to prioritize lodestar and assume we're handling objects on the zig side, at least for the initial integration to reduce diff for better reviewability. That's why in some of the PRs i broke off from #347 i'm manually walking the napi object instead of relying on the serializer/deserializer APIs

This isn't exactly the same issue but in a similar vein if we deal with raw bytes we can leave the burden up to the consumer (lodestar) to convert into consumable types (but that would mean bigger changes on the consumer side, might be harder to review - not sure how big the surface might be atm).

wemeetagain pushed a commit to ChainSafe/lodestar that referenced this pull request May 29, 2026
**Motivation**

- it's tricky to use BitArray, which is defined in ssz for binding, see
this
[concern](ChainSafe/lodestar-z#368 (comment))

**Description**
- the native binding does not have to do anything with `BitArray`, use
`{uint8Array: Uint8Array; bitLen: number}` instead, the binding needs to
conform to`IBeaconStateViewNative` overall
- implement `NativeBeaconStateView` wrapper that conform to the public
api of `IBeaconStateViewLatestFork` so `beacon-node` does not need to
change. It also contains a cache layer so that it does not need to fetch
native multiple times for the same data

---------

Co-authored-by: twoeths <twoeths@users.noreply.github.com>
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.

2 participants