Skip to content
Merged
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
34 changes: 34 additions & 0 deletions bindings/napi/pubkeys.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const blst_bindings = @import("./blst.zig");
const state_transition = @import("state_transition");
const PubkeyCache = state_transition.PubkeyCache;
const pkix = state_transition.pkix;
const Validator = @import("consensus_types").phase0.Validator.Type;

/// Uses the page allocator for the process-wide cache's internal allocations.
const allocator = std.heap.page_allocator;
Expand Down Expand Up @@ -183,6 +184,39 @@ pub fn append(index: js.Number, pubkey: js.Uint8Array) !void {
try state.cache.append(io, pubkey_bytes, idx);
}

/// JS: pubkeys.syncPubkeys(validators)
pub fn syncPubkeys(validators: js.Array) !void {
if (!state.initialized) return error.PubkeyIndexNotInitialized;

const validator_count = try validators.length();
const io = js.io();
const num_cached = state.cache.count(io);
if (validator_count <= num_cached) return;

const num_new_validators = validator_count - num_cached;

// SAFETY: the first `num_cached` validator ptrs are intentionally left undefined,
// because syncPubkeys only has to append the last `num_new_validators` pubkeys.
const validator_ptrs = try allocator.alloc(*const Validator, validator_count);
defer allocator.free(validator_ptrs);

const new_validators = try allocator.alloc(Validator, num_new_validators);
defer allocator.free(new_validators);

// `new_index` is the index of the soon-to-be added pubkey of a new validator.
// `i` is the local-only index of the temporary backing memory `new_validators`.
for (num_cached..validator_count, 0..) |new_index, i| {
const value = try validators.get(@intCast(new_index));
const partial_validator = try (try value.asObject(struct { pubkey: js.Uint8Array })).get();

new_validators[i] = undefined;
new_validators[i].pubkey = try partial_validator.pubkey.toArray(blst_bindings.PublicKey.COMPRESS_SIZE);
validator_ptrs[new_index] = &new_validators[i];
}

try state.cache.syncPubkeys(io, validator_ptrs);
}

/// JS: pubkeys.size() → number
/// Note: zapi DSL does not yet support namespace-level getters, so this is a function.
pub fn size() !js.Number {
Expand Down
2 changes: 2 additions & 0 deletions bindings/src/pubkeys.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface PubkeyCache {
getIndex(pubkey: Uint8Array): number | null;
/** Append at the next index. Exact replays are no-ops; invalid, conflicting, duplicate, or sparse entries throw. */
append(index: number, pubkey: Uint8Array): void;
/** Populate the cache from the missing suffix of a validator list. */
syncPubkeys(validators: {pubkey: Uint8Array}[]): void;
/** Current number of cached entries. */
readonly size: number;
/** Number of entries the current native allocation can hold without growing. */
Expand Down
4 changes: 4 additions & 0 deletions bindings/src/pubkeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export const pubkeyCache = {
native.append(index, pubkey);
},

syncPubkeys(validators) {
native.syncPubkeys(validators);
},

get size() {
return native.size();
},
Expand Down
27 changes: 27 additions & 0 deletions bindings/test/pubkeys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,33 @@ describe("pubkeys", () => {
}
});

it("syncPubkeys incrementally populates both lookup directions", () => {
pubkeyCache.reset();
const validators = keypairs.map(({pubkeyBytes}) => ({pubkey: pubkeyBytes}));

pubkeyCache.syncPubkeys(validators.slice(0, 1));
pubkeyCache.syncPubkeys(validators);

expectCacheContents();
});

it("syncPubkeys accepts a historical validator list", () => {
const validators = keypairs.map(({pubkeyBytes}) => ({pubkey: pubkeyBytes}));

pubkeyCache.syncPubkeys(validators.slice(0, 1));

expectCacheContents();
});

it("syncPubkeys rejects an invalid suffix without publishing valid entries", () => {
pubkeyCache.reset();
const validators = [{pubkey: keypairs[0].pubkeyBytes}, {pubkey: new Uint8Array(48)}];

expect(() => pubkeyCache.syncPubkeys(validators)).toThrow();
expect(pubkeyCache.size).toBe(0);
expect(pubkeyCache.getIndex(keypairs[0].pubkeyBytes)).toBeNull();
});

it("get caches deserialized values", () => {
const pk1 = pubkeyCache.getOrThrow(0);
const pk2 = pubkeyCache.getOrThrow(0);
Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
.hash = "zig_yaml-0.1.0-C1161kFWAwDxjKAFmklKwWVDvz2mmq0Q__bDhGGjeyd3",
},
.zapi = .{
.url = "https://github.com/ChainSafe/zapi/archive/9dd211167c4774c5a79e3d6fd360c22adca0a138.tar.gz",
.hash = "zapi-3.1.0-rIqzUQtsBADx3WYJ6Fe4BY1M3Q6zGpsti2B_cvgxZgto",
.url = "https://github.com/ChainSafe/zapi/archive/ab42ab08de92e1ea1f9f8a3f124e5c4bf55d564d.tar.gz",
.hash = "zapi-3.1.0-rIqzUXatBABpDFl_itCopyVIJdcEHttbhye7WfTNoHCR",
},
.zbench = .{
.url = "git+https://github.com/hendriknielaender/zBench#b2b89c475e3ef1bb2bd71255c80478a82d3e0ca8",
Expand Down
Loading