Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
9038c98
refactor: make XXXDecisionRoot fns return `js.String`
spiral-ladder May 1, 2026
a7c8844
update root related tests
spiral-ladder May 4, 2026
af2072e
fmt
spiral-ladder May 3, 2026
0b5c2a6
unabbreviate
spiral-ladder May 3, 2026
1bcb36f
refactor: make XXXDecisionRoot fns return `js.String`
spiral-ladder May 1, 2026
aa0369f
fix: param order in BeaconBlockBody
spiral-ladder May 1, 2026
ef383d9
refactor(bindings): return js.Number from getBalance
spiral-ladder May 1, 2026
7dc7a69
feat: align BeaconStateView to IBeaconStateView
spiral-ladder May 1, 2026
fd1c0a9
align doc comment for isExecutionEnabled
spiral-ladder May 5, 2026
58656ce
revert(bindings): pass SSZ bytes for voluntary exit validation
spiral-ladder May 5, 2026
0fe22ee
Fix getSingleProof param from any -> bigint
spiral-ladder May 5, 2026
aec1443
Merge branch 'main' into bing/native-stf
spiral-ladder May 5, 2026
45c2b20
revert slot value u32
spiral-ladder May 5, 2026
67084e3
revert: transitionOpts changes
spiral-ladder May 5, 2026
43b2244
fix doc comment for isExecutionEnabled
spiral-ladder May 5, 2026
0fe7152
doc: doc comment toValue stub
spiral-ladder May 5, 2026
b8e18e4
doc: doc comment CompactMultiProof type
spiral-ladder May 5, 2026
e2007c5
refactor: put `parseOptions` into `transition_opts.zig`
spiral-ladder May 5, 2026
37ca3b8
feat: naive loadOtherState
spiral-ladder May 5, 2026
342c1dc
fix rest of toU32 to toI64
spiral-ladder May 5, 2026
4909369
comments
spiral-ladder May 5, 2026
284e2bc
feat: bindings to `getExpectedWithdrawals` and native tweaks
spiral-ladder May 5, 2026
c062409
merge 'main' into bing/native-stf
spiral-ladder May 14, 2026
fbc60eb
loadState
spiral-ladder May 17, 2026
496fd10
fixes
spiral-ladder May 18, 2026
e827abb
feat: weakSubjectivity
spiral-ladder May 18, 2026
ee60eed
fix imports for weak subjectivity
spiral-ladder May 18, 2026
b45e1ce
toValue
spiral-ladder May 18, 2026
596f538
redundant com
spiral-ladder May 18, 2026
f14a0e1
feat: getSyncCommitteesWitness
spiral-ladder May 18, 2026
289c634
biome fix
spiral-ladder May 18, 2026
7392298
fix isMergeTransitionComplete to be a method
spiral-ladder May 20, 2026
03e3f37
fix: update expect bigints to be numbers
spiral-ladder May 20, 2026
450a4d9
update shape of serialized outputs
spiral-ladder May 20, 2026
7ed8b5a
dedup getExpectedWithdrawals
spiral-ladder May 20, 2026
7768778
revert isMergeTransitionComplete to property
spiral-ladder May 20, 2026
5530704
add stub for withParentPayloadApplied
spiral-ladder May 20, 2026
2011f30
refactor: update APIs related to voluntary exits
spiral-ladder May 25, 2026
c5d5fb6
move stf exports into dedicated files
spiral-ladder May 25, 2026
b6706a9
fix: isExecutionEnabled accepts block object
spiral-ladder May 25, 2026
aac8aea
getBeaconProposerOrNull
spiral-ladder Jun 2, 2026
755a472
merge 'main' into 'bing/native-stf'
spiral-ladder Jun 11, 2026
0d25986
bindings(pubkeys): add reset api
spiral-ladder Jun 11, 2026
414714b
remove stateTransition.zig
spiral-ladder Jun 11, 2026
e6e2b14
fix: run stf with block fork seq instead of cached
spiral-ladder Jun 11, 2026
038cf63
remove export stateTransition
spiral-ladder Jun 11, 2026
4753bb1
fix: various fixes in config
spiral-ladder Jun 16, 2026
c9f6ebd
merge 'main' into 'bing/native-stf'
spiral-ladder Jun 16, 2026
c1d7bd7
fmt lint
spiral-ladder Jun 16, 2026
6e417d9
zig fmt
spiral-ladder Jun 16, 2026
6dac261
chore: no-op when syncPubkeys run on a pk cache with shrinking
spiral-ladder Jun 16, 2026
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
667 changes: 593 additions & 74 deletions bindings/napi/BeaconStateView.zig

Large diffs are not rendered by default.

185 changes: 103 additions & 82 deletions bindings/napi/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ const active_preset = @import("preset").active_preset;
const c = @import("config");
const BeaconConfig = @import("config").BeaconConfig;
const ChainConfig = @import("config").ChainConfig;
const Preset = @import("preset").Preset;

/// Allocator for internal allocations.
/// Creating ChainConfigs allocate memory for certain fields.
const allocator = std.heap.page_allocator;
const max_blob_schedule_entries = 16;

pub const State = struct {
config: BeaconConfig = undefined,
initialized: bool = false,
config_name: [64]u8 = undefined,
blob_schedule: [max_blob_schedule_entries]ChainConfig.BlobScheduleEntry =
[_]ChainConfig.BlobScheduleEntry{.{
.EPOCH = 0,
.MAX_BLOBS_PER_BLOCK = 0,
}} ** max_blob_schedule_entries,

pub fn init(self: *State) void {
if (self.initialized) return;
Expand All @@ -22,33 +27,35 @@ pub const State = struct {
.minimal => self.config = c.minimal.config,
.gnosis => self.config = c.chiado.config,
}
self.initialized = true;
}

pub fn deinit(self: *State) void {
if (!self.initialized) return;

// Free any allocated fields in config here
inline for (std.meta.fields(ChainConfig)) |field| {
switch (field.type) {
[]const u8 => allocator.free(@field(self.config.chain, field.name)),
[]ChainConfig.BlobScheduleEntry => allocator.free(@field(self.config.chain, field.name)),
else => {},
}
}

self.initialized = false;
}
};

pub var state: State = .{};

fn valueToU64(value: napi.Value) !u64 {
const num = try value.getValueDouble();
if (std.math.isPositiveInf(num)) {
return std.math.maxInt(u64);
}
if (!std.math.isFinite(num) or num < 0 or num > @as(f64, @floatFromInt(std.math.maxInt(u64)))) {
return error.InvalidChainConfigFieldValue;
}
return @intFromFloat(num);
}

/// JS: config.set(chainConfigObj, genesisValidatorsRoot)
pub fn set(object: js.Value, genesis_root: js.Uint8Array) !void {
if (!state.initialized) {
return error.ConfigNotInitialized;
}

// Drop to low-level for the complex object parsing.
const chain_config = try chainConfigFromObject(js.env(), try object.toValue().coerceToObject());

const root_slice = try genesis_root.toSlice();
Expand All @@ -66,80 +73,94 @@ pub fn set(object: js.Value, genesis_root: js.Uint8Array) !void {

pub fn chainConfigFromObject(env: napi.Env, obj: napi.Value) !ChainConfig {
var chain_config: ChainConfig = undefined;

inline for (std.meta.fields(ChainConfig)) |field| {
const field_value = obj.getNamedProperty(field.name) catch |err| {
const field_value: napi.Value = obj.getNamedProperty(field.name) catch |err| {
try env.throwError(@errorName(err), "Missing field " ++ field.name);
return error.PendingException;
};
switch (field.type) {
u64 => {
const num = try field_value.getValueInt64();
// TODO check for infinity
@field(chain_config, field.name) = num;
},
u256 => {
var sign_bit: u1 = 0;
var words_buf: [4]u64 = undefined;
const words = try field_value.getValueBigintWords(&sign_bit, &words_buf);
if (sign_bit != 0) {
return error.InvalidChainConfigFieldValue;
}
var num_u256: u256 = 0;
for (0..4) |i| {
num_u256 |= u256(words[i]) << (@as(u256, i) * 64);
}
@field(chain_config, field.name) = num_u256;
},
[4]u8 => {
const typedarray_info = try field_value.getTypedarrayInfo();
if (typedarray_info.data.len != 4) {
return error.InvalidVersionLength;
}
var version: [4]u8 = undefined;
@memcpy(&version, typedarray_info.data);
@field(chain_config, field.name) = version;
},
[20]u8 => {
const typedarray_info = try field_value.getTypedarrayInfo();
if (typedarray_info.data.len != 20) {
return error.InvalidAddressLength;
}
var address: [20]u8 = undefined;
@memcpy(&address, typedarray_info.data);
@field(chain_config, field.name) = address;
},
[32]u8 => {
const typedarray_info = try field_value.getTypedarrayInfo();
if (typedarray_info.data.len != 32) {
return error.InvalidRootLength;
}
var root: [32]u8 = undefined;
@memcpy(&root, typedarray_info.data);
@field(chain_config, field.name) = root;
},
[]const u8 => {
var str_buf: [64]u8 = undefined;
const str = try field_value.getValueStringUtf8(&str_buf);
@field(chain_config, field.name) = try allocator.dupe(u8, str);
},
[]ChainConfig.BlobScheduleEntry => {
const array_length = try field_value.getArrayLength();
const blob_schedule = try allocator.alloc(c.BlobScheduleEntry, array_length);
errdefer allocator.free(blob_schedule);

for (0..array_length) |i| {
const entry_value = try field_value.getElement(i);
const epoch_value = try entry_value.getNamedProperty("EPOCH");
const max_blobs_value = try entry_value.getNamedProperty("MAX_BLOBS_PER_BLOCK");

blob_schedule[i] = c.BlobScheduleEntry{
.EPOCH = try epoch_value.getValueUint64(),
.MAX_BLOBS_PER_BLOCK = try max_blobs_value.getValueUint64(),

if (try field_value.typeof() == .undefined) {
std.log.warn("missing field value for: {s}, skipping\n", .{field.name});
} else {
switch (field.type) {
Preset => {
var str_buf: [16]u8 = undefined;
const preset_str = try field_value.getValueStringUtf8(&str_buf);
@field(chain_config, field.name) =
if (std.mem.eql(u8, preset_str, "mainnet"))
.mainnet
else if (std.mem.eql(u8, preset_str, "minimal"))
.minimal
else if (std.mem.eql(u8, preset_str, "gnosis"))
.gnosis
else
return error.InvalidPreset;
},
u64 => @field(chain_config, field.name) = try valueToU64(field_value),
u256 => {
var str_buf: [128]u8 = undefined;
const str = try (try field_value.coerceToString()).getValueStringUtf8(&str_buf);
@field(chain_config, field.name) = std.fmt.parseInt(u256, str, 10) catch {
return error.InvalidChainConfigFieldValue;
};
}
@field(chain_config, field.name) = blob_schedule;
},
else => return error.UnsupportedChainConfigFieldType,
},
[4]u8 => {
const typedarray_info = try field_value.getTypedarrayInfo();
if (typedarray_info.data.len != 4) {
return error.InvalidVersionLength;
}
var version: [4]u8 = undefined;
@memcpy(&version, typedarray_info.data);
@field(chain_config, field.name) = version;
},
[20]u8 => {
const typedarray_info = try field_value.getTypedarrayInfo();
if (typedarray_info.data.len != 20) {
return error.InvalidAddressLength;
}
var address: [20]u8 = undefined;
@memcpy(&address, typedarray_info.data);
@field(chain_config, field.name) = address;
},
[32]u8 => {
const typedarray_info = try field_value.getTypedarrayInfo();
if (typedarray_info.data.len != 32) {
return error.InvalidRootLength;
}
var root: [32]u8 = undefined;
@memcpy(&root, typedarray_info.data);
@field(chain_config, field.name) = root;
},
[]const u8 => {
_ = try field_value.getValueStringUtf8(&state.config_name);
if (comptime std.mem.eql(u8, field.name, "CONFIG_NAME")) {
@field(chain_config, field.name) = &state.config_name;
} else {
@compileError("unsupported field: " ++ field.name);
}
},
[]const ChainConfig.BlobScheduleEntry => {
const array_length: usize = @intCast(try field_value.getArrayLength());
if (array_length > max_blob_schedule_entries) {
return error.BlobScheduleTooLong;
}

for (0..array_length) |i| {
const entry_value = try field_value.getElement(@intCast(i));
const epoch_value = try entry_value.getNamedProperty("EPOCH");
const max_blobs_value = try entry_value.getNamedProperty("MAX_BLOBS_PER_BLOCK");

const blob_schedule_entry = ChainConfig.BlobScheduleEntry{
.EPOCH = try valueToU64(epoch_value),
.MAX_BLOBS_PER_BLOCK = try valueToU64(max_blobs_value),
};
state.blob_schedule[i] = blob_schedule_entry;
}
@field(chain_config, field.name) = state.blob_schedule[0..array_length];
},
else => return error.UnsupportedChainConfigFieldType,
}
}
}
return chain_config;
Expand Down
6 changes: 3 additions & 3 deletions bindings/napi/js_types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ pub const Validator = js.Object(struct {
});

pub const ProposerRewards = js.Object(struct {
attestations: js.BigInt,
syncAggregate: js.BigInt,
slashing: js.BigInt,
attestations: js.Number,
syncAggregate: js.Number,
slashing: js.Number,
Comment on lines +59 to +61

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.

high

Reverting these fields to js.BigInt to avoid precision loss for u64 Gwei values, consistent with the feedback in BeaconStateView.zig.

    attestations: js.BigInt,
    syncAggregate: js.BigInt,
    slashing: js.BigInt,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We use number in IBeaconStateView instead of big ints

});

pub const MultiProof = js.Object(struct {
Expand Down
12 changes: 12 additions & 0 deletions bindings/napi/pubkeys.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ pub const State = struct {
self.index2pubkey.deinit(allocator);
self.initialized = false;
}

pub fn reset(self: *State) !void {
if (!self.initialized) return;

self.pubkey2index.clearRetainingCapacity();
self.index2pubkey.shrinkRetainingCapacity(0);
}
};

pub var state: State = .{};
Expand Down Expand Up @@ -152,6 +159,11 @@ pub fn load(file_path: js.String) !void {
state.initialized = true;
}

/// JS: pubkeys.reset()
pub fn reset() !void {
try state.reset();
}

/// JS: pubkeys.getIndex(pubkeyBytes) → number | null
pub fn getIndex(pubkey: js.Uint8Array) !js.Value {
if (!state.initialized) return error.PubkeyIndexNotInitialized;
Expand Down
1 change: 0 additions & 1 deletion bindings/napi/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ pub const pool = @import("./pool.zig");
pub const shuffle = @import("./shuffle.zig");
pub const config = @import("./config.zig");
pub const metrics = @import("./metrics.zig");
pub const stateTransition = @import("./stateTransition.zig");
pub const BeaconStateView = @import("./BeaconStateView.zig");
pub const blst = @import("./blst.zig");
pub const pubkeys = @import("./pubkeys.zig");
Expand Down
Loading
Loading