Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions bindings/napi/BeaconStateView.zig
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub const js_meta = js.class(.{ .properties = .{
} });

cached_state: ?*CachedBeaconState = null,
pool_rc: ?*pool.PoolRc = null,
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

Update the type reference to PoolRC to match the updated naming convention in pool.zig.

pool_rc: ?*pool.PoolRC = null,

const BeaconStateView = @This();

pub fn init() BeaconStateView {
Expand All @@ -78,6 +79,10 @@ pub fn deinit(self: *BeaconStateView) void {
allocator.destroy(cached_state);
self.cached_state = null;
}
if (self.pool_rc) |rc| {
rc.unref();
self.pool_rc = null;
}
}

// -------------------------
Expand All @@ -90,7 +95,7 @@ pub fn createFromBytes(bytes: js.Uint8Array) !BeaconStateView {
const byte_slice = try bytes.toSlice();
const slot_value = fork_types.readSlotFromAnyBeaconStateBytes(byte_slice);
const fork_seq = config.state.config.forkSeq(slot_value);
state.* = try AnyBeaconState.deserialize(allocator, &pool.state.pool, fork_seq, byte_slice);
state.* = try AnyBeaconState.deserialize(allocator, pool.state.pool(), fork_seq, byte_slice);
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

Add an assertion to verify that the shared pool is initialized before use, as required by the style guide's assertion density and safety rules (Rule 51).

    std.debug.assert(pool.state.pool_rc != null);
    state.* = try AnyBeaconState.deserialize(allocator, pool.state.pool(), fork_seq, byte_slice);

errdefer state.deinit();

const cached_state = try allocator.create(CachedBeaconState);
Expand All @@ -107,7 +112,10 @@ pub fn createFromBytes(bytes: js.Uint8Array) !BeaconStateView {
null,
);

return .{ .cached_state = cached_state };
return .{
.cached_state = cached_state,
.pool_rc = pool.state.poolRc().ref(),
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

Update the call to poolRC() to match the renamed function in pool.zig.

        .pool_rc = pool.state.poolRC().ref(),

};
}

// -------------------------
Expand Down Expand Up @@ -781,7 +789,7 @@ pub fn createMultiProof(self: *const BeaconStateView, descriptor: js.Uint8Array)

var proof = persistent_merkle_tree.proof.createProof(
allocator,
&pool.state.pool,
pool.state.pool(),
root_node,
proof_input,
) catch {
Expand Down Expand Up @@ -934,7 +942,10 @@ pub fn processSlots(self: *const BeaconStateView, slot_arg: js.Number, options:
}

try st.processSlots(allocator, napi_io.get(), post_state, slot_value, .{});
return .{ .cached_state = post_state };
return .{
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

Add an assertion to verify that the shared pool is initialized before returning a new view, ensuring the invariant holds and increasing assertion density.

    std.debug.assert(pool.state.pool_rc != null);
    return .{

.cached_state = post_state,
.pool_rc = pool.state.poolRc().ref(),
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

Update the call to poolRC() to match the renamed function in pool.zig.

        .pool_rc = pool.state.poolRC().ref(),

};
}

/// Compute expected withdrawals for the next payload (capella+).
Expand Down
41 changes: 30 additions & 11 deletions bindings/napi/pool.zig
Original file line number Diff line number Diff line change
@@ -1,42 +1,61 @@
const std = @import("std");
const js = @import("zapi:zapi").js;
const Node = @import("persistent_merkle_tree").Node;
const RefCount = @import("state_transition").RefCount;

/// Pool uses page allocator for internal allocations.
/// It's recommended to never reallocate the pool after initialization.
const allocator = std.heap.page_allocator;

const default_pool_size: u32 = 0;

pub const PoolRc = RefCount(Node.Pool);
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

According to the style guide (Rule 224), acronyms should use proper capitalization. Since RC stands for Reference Count, it should be capitalized as PoolRC.

pub const PoolRC = RefCount(Node.Pool);
References
  1. Use proper capitalization for acronyms (VSRState, not VsrState). (link)


/// Pool is wrapped in `RefCount` so binding objects holding pool refs at
/// process exit keep the pool alive until their JS finalizer runs. NAPI
/// env cleanup hook fires before module-level JS holders are finalized,
/// so an unconditional `pool.deinit()` there would free memory that
/// `pool.unref()` calls in those finalizers still need.
pub const State = struct {
pool: Node.Pool = undefined,
initialized: bool = false,
pool_rc: ?*PoolRc = null,
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

Update the type name to PoolRC to adhere to the acronym capitalization rule.

    pool_rc: ?*PoolRC = null,


pub fn init(self: *State) !void {
if (self.initialized) return;
self.pool = try Node.Pool.init(allocator, default_pool_size);
self.initialized = true;
if (self.pool_rc != null) return;
var pool_value = try Node.Pool.init(allocator, default_pool_size);
errdefer pool_value.deinit();
self.pool_rc = try PoolRc.init(allocator, pool_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

Update the type name to PoolRC to adhere to the acronym capitalization rule.

        self.pool_rc = try PoolRC.init(allocator, pool_value);

}

pub fn deinit(self: *State) void {
if (!self.initialized) return;
self.pool.deinit();
self.initialized = false;
if (self.pool_rc) |rc| {
rc.unref();
self.pool_rc = null;
}
}

pub fn pool(self: *State) *Node.Pool {
std.debug.assert(self.pool_rc != null);
return &self.pool_rc.?.instance;
}
Comment on lines +36 to +39
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 style guide (Rule 51) requires asserting function arguments and invariants. Adding an explicit assertion here improves safety and follows the 'fail-fast' principle.

    pub fn pool(self: *State) *Node.Pool {
        std.debug.assert(self.pool_rc != null);
        return &self.pool_rc.?.instance;
    }
References
  1. Assert all function arguments and return values, pre/postconditions and invariants. (link)


pub fn poolRc(self: *State) *PoolRc {
std.debug.assert(self.pool_rc != null);
return self.pool_rc.?;
}
Comment on lines +41 to 44
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

Rename the function to poolRC and update the return type to PoolRC to adhere to the acronym capitalization rule (Rule 224). Additionally, add an assertion for the pool initialization invariant.

    pub fn poolRC(self: *State) *PoolRC {
        std.debug.assert(self.pool_rc != null);
        return self.pool_rc.?;
    }

};

pub var state: State = .{};

/// JS: pool.ensureCapacity(newSize)
pub fn ensureCapacity(new_size: js.Number) !void {
if (!state.initialized) {
if (state.pool_rc == null) {
return error.PoolNotInitialized;
}

const requested = new_size.assertU32();
const old_size = state.pool.nodes.capacity;
const old_size = state.pool().nodes.capacity;
if (requested <= old_size) {
return;
}
try state.pool.preheat(@intCast(requested - state.pool.nodes.capacity));
try state.pool().preheat(@intCast(requested - state.pool().nodes.capacity));
}
49 changes: 49 additions & 0 deletions bindings/test/teardown.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {spawnSync} from "node:child_process";
import {unlinkSync, writeFileSync} from "node:fs";
import {join} from "node:path";
import {describe, expect, it} from "vitest";

describe("BeaconStateView teardown", () => {
it("creates view at module scope and exits cleanly", () => {
const projectRoot = join(import.meta.dirname, "../..");
// Fixture must live under the project root so Node resolves
// workspace packages like @lodestar/config from local node_modules.
const fixturePath = join(projectRoot, `bindings/test/.tmp-teardown-${process.pid}.mjs`);

writeFileSync(
fixturePath,
`
import {config} from "@lodestar/config/default";
import * as era from "@lodestar/era";
import bindings from "../src/index.js";
import {getFirstEraFilePath} from "./eraFiles.ts";

const reader = await era.era.EraReader.open(config, getFirstEraFilePath());
const stateBytes = await reader.readSerializedState();
await reader.close();

bindings.pool.ensureCapacity(10_000_000);
bindings.pubkeys.ensureCapacity(2_000_000);

const seedState = bindings.BeaconStateView.createFromBytes(stateBytes);
console.log("slot=" + seedState.slot);
`
);

try {
const result = spawnSync(process.execPath, ["--experimental-strip-types", fixturePath], {
cwd: projectRoot,
encoding: "utf-8",
timeout: 60_000,
});
expect(result.status, `stdout=${result.stdout} stderr=${result.stderr}`).toBe(0);
expect(result.stderr, "no panic on stderr").not.toContain("panic:");
} finally {
try {
unlinkSync(fixturePath);
} catch (_e) {
// ignore
}
}
}, 90_000);
});
1 change: 1 addition & 0 deletions src/state_transition/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub const TransitionOpts = @import("state_transition.zig").TransitionOpts;

pub const metrics = @import("metrics.zig");

pub const RefCount = @import("./utils/ref_count.zig").RefCount;
pub const computeSigningRoot = @import("./utils/signing_root.zig").computeSigningRoot;
pub const computeEpochAtSlot = @import("./utils/epoch.zig").computeEpochAtSlot;
pub const CachedBeaconState = @import("./cache/state_cache.zig").CachedBeaconState;
Expand Down
Loading