Skip to content

Commit 1b7c921

Browse files
Merge branch 'main' into bing/elem-it
2 parents 8d2beb9 + 057310e commit 1b7c921

46 files changed

Lines changed: 761 additions & 597 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bindings/napi/pool.zig

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
const std = @import("std");
2-
const js = @import("zapi:zapi").js;
32
const Node = @import("persistent_merkle_tree").Node;
43
const RefCount = @import("state_transition").RefCount;
54

65
/// Backs the `PoolRc` wrapper allocation only — `Node.Pool` uses its own
76
/// `InitOptions` allocators.
87
const allocator = std.heap.page_allocator;
98

10-
const default_pool_size: u32 = 0;
9+
const pool_size_environment_variable = "LODESTAR_Z_NODE_POOL_CAPACITY";
10+
// Arbitrary limit to avoid excessive memory usage.
11+
const default_pool_size: u32 = 10_000_000;
1112

1213
const PoolRc = RefCount(Node.Pool);
1314

15+
fn poolSizeFromEnvironment() !u32 {
16+
const raw = std.c.getenv(pool_size_environment_variable) orelse return default_pool_size;
17+
const value = std.mem.span(raw);
18+
return std.fmt.parseInt(u32, value, 10) catch error.InvalidPoolCapacity;
19+
}
20+
1421
/// Pool is wrapped in `RefCount` so binding objects holding pool refs at
1522
/// process exit keep the pool alive until their JS finalizer runs. NAPI
1623
/// env cleanup hook fires before module-level JS holders are finalized,
@@ -22,9 +29,9 @@ const State = struct {
2229
pub fn init(self: *State) !void {
2330
if (self.pool_rc != null) return;
2431

25-
// Small-object lane must stay non-page: page_allocator rounds each
26-
// alloc to 4 KB and blows up once the binding preheats 10M nodes.
27-
var pool_value = try Node.Pool.init(.{ .allocator = std.heap.c_allocator, .pool_size = default_pool_size });
32+
const pool_size = try poolSizeFromEnvironment();
33+
34+
var pool_value = try Node.Pool.init(.{ .allocator = std.heap.c_allocator, .pool_size = pool_size });
2835
errdefer pool_value.deinit();
2936

3037
self.pool_rc = try PoolRc.init(allocator, pool_value);
@@ -49,17 +56,3 @@ const State = struct {
4956
};
5057

5158
pub var state: State = .{};
52-
53-
/// JS: pool.ensureCapacity(newSize)
54-
pub fn ensureCapacity(new_size: js.Number) !void {
55-
if (state.pool_rc == null) {
56-
return error.PoolNotInitialized;
57-
}
58-
59-
const requested = new_size.assertU32();
60-
const old_size = state.pool().nodes.capacity;
61-
if (requested <= old_size) {
62-
return;
63-
}
64-
try state.pool().preheat(@intCast(requested - state.pool().nodes.capacity));
65-
}

bindings/napi/root.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const std = @import("std");
22
const builtin = @import("builtin");
33
const js = @import("zapi:zapi").js;
4-
pub const pool = @import("./pool.zig");
4+
const pool = @import("./pool.zig");
55
pub const shuffle = @import("./shuffle.zig");
66
pub const config = @import("./config.zig");
77
pub const metrics = @import("./metrics.zig");

bindings/perf/loadState.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ const stateBytes = await reader.readSerializedState();
1212
await reader.close();
1313
const requiredPubkeyCapacity = getPubkeyCacheCapacityForState(stateBytes);
1414

15-
bindings.pool.ensureCapacity(10_000_000);
1615
let loadedPkix = false;
1716
try {
1817
bindings.pubkeys.load("./mainnet.pkix", requiredPubkeyCapacity);

bindings/src/index.d.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,9 +370,6 @@ export declare class BeaconStateView {
370370
}
371371

372372
declare const bindings: {
373-
pool: {
374-
ensureCapacity: (capacity: number) => void;
375-
};
376373
config: {
377374
set: (chainConfig: object, genesisValidatorsRoot: Uint8Array) => void;
378375
};

bindings/test/beaconStateView.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ describe("BeaconStateView", () => {
140140
global.gc?.();
141141

142142
// Phase 2: Create native BeaconStateView
143-
bindings.pool.ensureCapacity(10_000_000);
144143
try {
145144
bindings.pubkeys.load("./mainnet.pkix", MAINNET_PUBKEY_CACHE_LIMIT);
146145
} catch (_e) {

bindings/test/demo.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ const hasPkix = printDuration("check for pkix file", () => {
3232
}
3333
});
3434

35-
bindings.pool.ensureCapacity(10_000_000);
36-
3735
const reader = await printDurationAsync("load era reader", () => era.era.EraReader.open(config, getFirstEraFilePath()));
3836

3937
const nextReader = await printDurationAsync("load era reader", () =>

bindings/test/teardown.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const reader = await era.era.EraReader.open(config, getFirstEraFilePath());
2323
const stateBytes = await reader.readSerializedState();
2424
await reader.close();
2525
26-
bindings.pool.ensureCapacity(10_000_000);
2726
bindings.pubkeys.ensureCapacity(getSerializedFuluValidatorCount(stateBytes));
2827
2928
const seedState = bindings.BeaconStateView.createFromBytes(stateBytes);

docs/security/IMPLEMENTATION_MAP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ supported caller supplies a current hostile path.
3030
| Serialized block boundary | [`BeaconStateView.stateTransition`](../../bindings/napi/BeaconStateView.zig) accepts serialized signed-block bytes and passes the decoded block to the transition. This is a hostile-input boundary for integrated callers. |
3131
| BLS verifier validation | [`bls_verifier.zig`](../../bindings/napi/bls_verifier.zig) validates every signature for infinity and G2 membership before pairing. It also validates raw public keys for infinity and G1 membership. Indexed and aggregate sets trust cached affine keys. Direct append callers must supply a validated public key. State-transition appends follow successful deposit proof-of-possession checks. Bulk sync requires a trusted validator list. PKIX load requires trusted file provenance. |
3232
| Pubkey cache | [`pubkey_cache.zig`](../../src/state_transition/cache/pubkey_cache.zig) defines an application-wide, append-only cache with locked access and no escaping pointers into movable storage. [`bindings/napi/pubkeys.zig`](../../bindings/napi/pubkeys.zig) owns its process-wide instance. |
33+
| Persistent Merkle tree pool | [`Node.Pool`](../../src/persistent_merkle_tree/Node.zig) allocates a fixed number of user slots and returns `PoolExhausted` without resizing. The shared addon reads `LODESTAR_Z_NODE_POOL_CAPACITY` during initialization, defaults to 10,000,000 slots, and rejects invalid values. Chunked-leaf and container payloads use a separate dynamic allocator. |
3334
| Reused epoch cache | [`epoch_transition_cache.zig`](../../src/state_transition/cache/epoch_transition_cache.zig) stores process-global arrays borrowed by an `EpochTransitionCache`. The lock covers acquisition and resize, not the full borrowed lifetime. Current safe use requires non-overlapping transitions and no concurrent teardown. |
3435
| PKIX persistence | [`pkix.zig`](../../src/state_transition/cache/pkix.zig) checks framing, bounds, ABI compatibility, and corruption checksums. It does not authenticate the file or semantically revalidate affine entries, so file provenance remains trusted. |
3536
| Build and release provenance | [`build.zig.zon`](../../build.zig.zon) and [`pnpm-lock.yaml`](../../pnpm-lock.yaml) pin dependency inputs. [`publish-bindings.yml`](../../.github/workflows/publish-bindings.yml) pins actions, builds ReleaseSafe artifacts, and publishes them with npm provenance. |

examples/metrics.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ const hasPkix = (() => {
4545
}
4646
})();
4747

48-
console.log("Initializing pool...");
49-
bindings.pool.ensureCapacity(10_000_000);
50-
5148
console.log("Initializing metrics...");
5249
bindings.metrics.init();
5350

src/beacon_node/chain/state_cache/block_state_cache.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ const BlockHarness = struct {
421421

422422
h.allocator = allocator;
423423
h.io = std.testing.io;
424-
h.pool = try Node.Pool.init(.{ .page_allocator = allocator, .allocator = allocator, .pool_size = 256 * 8 });
424+
h.pool = try Node.Pool.init(.{ .page_allocator = allocator, .allocator = allocator, .pool_size = 180_000 });
425425
errdefer h.pool.deinit();
426426

427427
h.factory = try TestStateFactory.init(allocator, &h.pool);
@@ -762,7 +762,7 @@ fn cloneDistinct(seed: *CachedBeaconState, alloc: Allocator, slot: u64) !*Cached
762762
test "BlockStateCache add - insert/prune/duplicate paths free the owned state exactly once" {
763763
const seed_alloc = testing.allocator;
764764
const io = std.testing.io;
765-
const pool_size = 256 * 64;
765+
const pool_size = 180_000;
766766
var pool = try Node.Pool.init(.{ .page_allocator = seed_alloc, .allocator = seed_alloc, .pool_size = pool_size });
767767
defer pool.deinit();
768768

0 commit comments

Comments
 (0)