Skip to content
Draft
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
1 change: 0 additions & 1 deletion bench/state_transition/process_block.zig
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ fn ProcessSyncAggregateBench(comptime fork: ForkSeq, comptime opts: BenchOpts) t

state_transition.processSyncAggregate(
fork,
allocator,
cloned.config,
cloned.epoch_cache,
cloned.state.castToFork(fork),
Expand Down
29 changes: 27 additions & 2 deletions src/ssz/type/bit_vector.zig
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ pub fn BitVector(comptime _length: comptime_int) type {
/// Allocates and returns an `ArrayList` of indices where the bit at the index of `self` is set to `true`.
///
/// Caller must call `deinit` on the returned list
pub fn intersectValues(
pub fn intersectValuesAlloc(
self: *const @This(),
comptime T: type,
allocator: std.mem.Allocator,
Expand All @@ -148,6 +148,31 @@ pub fn BitVector(comptime _length: comptime_int) type {
}
return indices;
}

/// Returns a slice into `out` of values where the corresponding bit is set to `true`.
pub fn intersectValues(
self: *const @This(),
comptime T: type,
values: *const [length]T,
out: *[length]T,
) []T {
Comment on lines +153 to +158

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 51), all function arguments should be asserted. This function is missing checks for its pointer arguments (self, values, out).

Please add assertions at the beginning of the function body to ensure these pointers are not null. This improves safety by catching potential null pointer dereferences early.

References
  1. Assert all function arguments and return values, pre/postconditions and invariants. A function must not operate blindly on data it has not checked. The purpose of a function is to increase the probability that a program is correct. Assertions within a function are part of how functions serve this purpose. The assertion density of the code must average a minimum of two assertions per function. (link)

var i: usize = 0;
for (0..byte_len) |i_byte| {
var b = self.data[i_byte];
// Kernighan's algorithm to count the set bits instead of going through 0..8 for every byte
while (b != 0) {
const lsb: usize = @as(u8, @ctz(b)); // Get the index of least significant bit
const bit_index = i_byte * 8 + lsb;
out[i] = values[bit_index];
i += 1;
// The `b - 1` flips the bits starting from `lsb` index
// And `&` will reset the last bit at `lsb` index
b &= b - 1;
}
Comment on lines +159 to +171

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 44) recommends avoiding usize in favor of explicitly-sized integer types like u32 to improve portability and explicitness. The variables i, lsb, and bit_index are currently usize.

Please consider changing them to a fixed-size type like u32.

            var i: u32 = 0;
            for (0..byte_len) |i_byte| {
                var b = self.data[i_byte];
                // Kernighan's algorithm to count the set bits instead of going through 0..8 for every byte
                while (b != 0) {
                    const lsb: u32 = @ctz(b); // Get the index of least significant bit
                    const bit_index: u32 = @as(u32, i_byte) * 8 + lsb;
                    out[i] = values[bit_index];
                    i += 1;
                    // The `b - 1` flips the bits starting from `lsb` index
                    // And `&` will reset the last bit at `lsb` index
                    b &= b - 1;
                }

References
  1. Use explicitly-sized types like u32 for everything, avoid architecture-specific usize. (link)

}

return out[0..i];
}
};
}

Expand Down Expand Up @@ -382,7 +407,7 @@ test "BitVectorType - intersectValues" {
var values: [16]u8 = undefined;
for (0..tc.bit_len) |i| values[i] = @intCast(i);

var actual = try b.intersectValues(u8, allocator, &values);
var actual = try b.intersectValuesAlloc(u8, allocator, &values);
defer actual.deinit();
try std.testing.expectEqualSlices(u8, tc.expected, actual.items);
}
Expand Down
2 changes: 1 addition & 1 deletion src/state_transition/block/process_block.zig
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn processBlock(
try processEth1Data(fork, state, body.eth1Data());
try processOperations(fork, allocator, config, epoch_cache, state, slashings_cache, block_type, body, opts);
if (comptime fork.gte(.altair)) {
try processSyncAggregate(fork, allocator, config, epoch_cache, state, body.syncAggregate(), opts.verify_signature);
try processSyncAggregate(fork, config, epoch_cache, state, body.syncAggregate(), opts.verify_signature);
}

if (comptime fork.gte(.deneb)) {
Expand Down
22 changes: 10 additions & 12 deletions src/state_transition/block/process_sync_committee.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const decreaseBalance = balance_utils.decreaseBalance;

pub fn processSyncAggregate(
comptime fork: ForkSeq,
allocator: Allocator,
config: *const BeaconConfig,
epoch_cache: *const EpochCache,
state: *BeaconState(fork),
Expand All @@ -37,23 +36,23 @@ pub fn processSyncAggregate(

// different from the spec but not sure how to get through signature verification for default/empty SyncAggregate in the spec test
if (verify_signatures) {
const participant_indices = try sync_committee_bits.intersectValues(
var participant_buf: [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex = undefined;
const participant_indices = sync_committee_bits.intersectValues(
ValidatorIndex,
allocator,
committee_indices,
&participant_buf,
);
defer participant_indices.deinit();

// When there's no participation we cons ider the signature valid and just ignore it
if (participant_indices.items.len > 0) {
if (participant_indices.len > 0) {
const previous_slot = @max(try state.slot(), 1) - 1;
const root_signed = try getBlockRootAtSlot(fork, state, previous_slot);
const domain = try config.getDomain(epoch_cache.epoch, c.DOMAIN_SYNC_COMMITTEE, previous_slot);

const pubkeys = try allocator.alloc(bls.PublicKey, participant_indices.items.len);
defer allocator.free(pubkeys);
for (0..participant_indices.items.len) |i| {
pubkeys[i] = epoch_cache.index_to_pubkey.items[participant_indices.items[i]];
var pubkeys_buf: [preset.SYNC_COMMITTEE_SIZE]bls.PublicKey = undefined;
const pubkeys = pubkeys_buf[0..participant_indices.len];
for (0..participant_indices.len) |i| {
pubkeys[i] = epoch_cache.index_to_pubkey.items[participant_indices[i]];
}

var signing_root: Root = undefined;
Expand Down Expand Up @@ -123,9 +122,10 @@ pub fn getSyncCommitteeSignatureSet(
) !?AggregatedSignatureSet {
const signature = sync_aggregate.sync_committee_signature;

var participant_buf: [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex = undefined;
const participant_indices_ = if (participant_indices) |pi| pi else blk: {
const committee_indices = @as(*const [preset.SYNC_COMMITTEE_SIZE]u64, @ptrCast(epoch_cache.current_sync_committee_indexed.get().getValidatorIndices()));
break :blk (try sync_aggregate.sync_committee_bits.intersectValues(ValidatorIndex, allocator, committee_indices)).items;
break :blk sync_aggregate.sync_committee_bits.intersectValues(ValidatorIndex, committee_indices, &participant_buf);
};
Comment on lines +125 to 129

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

To minimize the scope of participant_buf and adhere to style guide rules 104 and 356, it should be declared inside the else block where it is used.

    const participant_indices_ = if (participant_indices) |pi| pi else blk: {
        const committee_indices = @as(*const [preset.SYNC_COMMITTEE_SIZE]u64, @ptrCast(epoch_cache.current_sync_committee_indexed.get().getValidatorIndices()));
        var participant_buf: [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex = undefined;
        break :blk sync_aggregate.sync_committee_bits.intersectValues(ValidatorIndex, committee_indices, &participant_buf);
    };
References
  1. Declare variables at the smallest possible scope, and minimize the number of variables in scope, to reduce the probability that variables are misused. (link)
  2. Calculate or check variables close to where/when they are used. Don't introduce variables before they are needed. Don't leave them around where they are not. (link)

// When there's no participation we consider the signature valid and just ignore it
if (participant_indices_.len == 0) {
Expand Down Expand Up @@ -208,7 +208,6 @@ test "process sync aggregate - sanity" {

const res = processSyncAggregate(
.electra,
allocator,
config,
epoch_cache,
fork_state,
Expand All @@ -221,7 +220,6 @@ test "process sync aggregate - sanity" {
try sync_aggregate.sync_committee_bits.set(1, true);
try processSyncAggregate(
.electra,
allocator,
config,
epoch_cache,
fork_state,
Expand Down
1 change: 0 additions & 1 deletion test/spec/runner/operations.zig
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ pub fn TestCase(comptime fork: ForkSeq, comptime operation: Operation) type {
const epoch_cache = cached_state.epoch_cache;
try state_transition.processSyncAggregate(
fork,
allocator,
config,
epoch_cache,
state,
Expand Down
Loading