Skip to content

refactor: optimize process_slashings#191

Merged
wemeetagain merged 22 commits intomainfrom
stf-slashings-penalties
Feb 2, 2026
Merged

refactor: optimize process_slashings#191
wemeetagain merged 22 commits intomainfrom
stf-slashings-penalties

Conversation

@GrapeBaBa
Copy link
Copy Markdown
Contributor

@GrapeBaBa GrapeBaBa commented Jan 26, 2026

Motivation

  • Align slashing handling with Lodestar TS: compute slashing penalties once and apply them alongside rewards/penalties in a single balance update.
  • Reduce per‑epoch overhead and make cache initialization safer on error paths.

Description

  • processSlashings now returns a read‑only penalties slice when update_balance=false; processRewardsAndPenalties consumes it, and epoch/spec/bench call sites are updated accordingly.
  • Added slashing_penalties to the reused epoch transition cache and hardened ReusedEpochTransitionCache.init with errdefer cleanup.

Fix #195

wemeetagain and others added 19 commits January 19, 2026 19:00
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @GrapeBaBa, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the handling of slashing penalties during the epoch transition process. It introduces a new approach where slashing penalties are first computed by processSlashings and then passed to processRewardsAndPenalties to be applied alongside other rewards and penalties in a single, consolidated balance update. This change aims to align the implementation with the Lodestar TS client, reduce per-epoch processing overhead, and enhance the robustness of cache initialization by adding comprehensive error deferral mechanisms.

Highlights

  • Decoupled Slashing Penalty Application: The processSlashings function has been refactored to calculate slashing penalties and optionally return them as a read-only slice, rather than immediately applying them to validator balances. This is controlled by a new update_balance parameter.
  • Centralized Balance Updates: Slashing penalties are now passed to processRewardsAndPenalties, which consolidates the application of all rewards and penalties (including slashings) in a single balance update operation, aligning with the Lodestar TS client's approach.
  • Enhanced Cache Management: The ReusedEpochTransitionCache now includes a dedicated slashing_penalties field. Its initialization (init function) has been hardened with errdefer cleanup for all allocated resources, significantly improving error safety during cache setup.
  • Optimized Epoch Processing: Call sites in processEpoch, various benchmarks, and spec tests have been updated to reflect the new processSlashings return value and the additional parameter for processRewardsAndPenalties, streamlining the overall epoch transition logic.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors slashing processing to be more efficient by separating penalty calculation from balance updates, aligning with the Lodestar TS implementation. The changes are well-contained and the motivation is clear. The addition of errdefer in ReusedEpochTransitionCache.init is a great improvement for safety. I have a couple of suggestions to further align the code with the repository's style guide, mainly regarding in-place struct initialization and the use of assertions for programmer errors.

Comment on lines 73 to 104
pub fn init(allocator: Allocator, validator_count: usize) !ReusedEpochTransitionCache {
return .{
.allocator = allocator,
.is_active_prev_epoch = try BoolArray.initCapacity(allocator, validator_count),
.is_active_current_epoch = try BoolArray.initCapacity(allocator, validator_count),
.is_active_next_epoch = try BoolArray.initCapacity(allocator, validator_count),
.proposer_indices = try UsizeArray.initCapacity(allocator, validator_count),
.inclusion_delays = try UsizeArray.initCapacity(allocator, validator_count),
.flags = try U8Array.initCapacity(allocator, validator_count),
.next_epoch_shuffling_active_validator_indices = try std.ArrayList(ValidatorIndex).initCapacity(allocator, validator_count),
.is_compounding_validator_arr = try BoolArray.initCapacity(allocator, validator_count),
.previous_epoch_participation = try U8Array.initCapacity(allocator, validator_count),
.current_epoch_participation = try U8Array.initCapacity(allocator, validator_count),
.rewards = try U64Array.initCapacity(allocator, validator_count),
.penalties = try U64Array.initCapacity(allocator, validator_count),
};
var cache: ReusedEpochTransitionCache = undefined;
cache.allocator = allocator;
cache.is_active_prev_epoch = try BoolArray.initCapacity(allocator, validator_count);
errdefer cache.is_active_prev_epoch.deinit();
cache.is_active_current_epoch = try BoolArray.initCapacity(allocator, validator_count);
errdefer cache.is_active_current_epoch.deinit();
cache.is_active_next_epoch = try BoolArray.initCapacity(allocator, validator_count);
errdefer cache.is_active_next_epoch.deinit();
cache.proposer_indices = try UsizeArray.initCapacity(allocator, validator_count);
errdefer cache.proposer_indices.deinit();
cache.inclusion_delays = try UsizeArray.initCapacity(allocator, validator_count);
errdefer cache.inclusion_delays.deinit();
cache.flags = try U8Array.initCapacity(allocator, validator_count);
errdefer cache.flags.deinit();
cache.next_epoch_shuffling_active_validator_indices = try std.ArrayList(ValidatorIndex).initCapacity(allocator, validator_count);
errdefer cache.next_epoch_shuffling_active_validator_indices.deinit();
cache.is_compounding_validator_arr = try BoolArray.initCapacity(allocator, validator_count);
errdefer cache.is_compounding_validator_arr.deinit();
cache.previous_epoch_participation = try U8Array.initCapacity(allocator, validator_count);
errdefer cache.previous_epoch_participation.deinit();
cache.current_epoch_participation = try U8Array.initCapacity(allocator, validator_count);
errdefer cache.current_epoch_participation.deinit();
cache.rewards = try U64Array.initCapacity(allocator, validator_count);
errdefer cache.rewards.deinit();
cache.penalties = try U64Array.initCapacity(allocator, validator_count);
errdefer cache.penalties.deinit();
cache.slashing_penalties = try U64Array.initCapacity(allocator, validator_count);
errdefer cache.slashing_penalties.deinit();

return cache;
}
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

While the addition of errdefer is a great improvement for safety, this init function returns a large struct by value, which can lead to an expensive copy. The repository style guide recommends initializing large structs in-place using an out-pointer to avoid this.

Consider changing the function signature to initialize the struct in-place. This would also require updating the call site in getReusedEpochTransitionCache.

pub fn init(self: *ReusedEpochTransitionCache, allocator: Allocator, validator_count: usize) !void {
    self.* = undefined;
    self.allocator = allocator;
    self.is_active_prev_epoch = try BoolArray.initCapacity(allocator, validator_count);
    errdefer self.is_active_prev_epoch.deinit();
    self.is_active_current_epoch = try BoolArray.initCapacity(allocator, validator_count);
    errdefer self.is_active_current_epoch.deinit();
    self.is_active_next_epoch = try BoolArray.initCapacity(allocator, validator_count);
    errdefer self.is_active_next_epoch.deinit();
    self.proposer_indices = try UsizeArray.initCapacity(allocator, validator_count);
    errdefer self.proposer_indices.deinit();
    self.inclusion_delays = try UsizeArray.initCapacity(allocator, validator_count);
    errdefer self.inclusion_delays.deinit();
    self.flags = try U8Array.initCapacity(allocator, validator_count);
    errdefer self.flags.deinit();
    self.next_epoch_shuffling_active_validator_indices = try std.ArrayList(ValidatorIndex).initCapacity(allocator, validator_count);
    errdefer self.next_epoch_shuffling_active_validator_indices.deinit();
    self.is_compounding_validator_arr = try BoolArray.initCapacity(allocator, validator_count);
    errdefer self.is_compounding_validator_arr.deinit();
    self.previous_epoch_participation = try U8Array.initCapacity(allocator, validator_count);
    errdefer self.previous_epoch_participation.deinit();
    self.current_epoch_participation = try U8Array.initCapacity(allocator, validator_count);
    errdefer self.current_epoch_participation.deinit();
    self.rewards = try U64Array.initCapacity(allocator, validator_count);
    errdefer self.rewards.deinit();
    self.penalties = try U64Array.initCapacity(allocator, validator_count);
    errdefer self.penalties.deinit();
    self.slashing_penalties = try U64Array.initCapacity(allocator, validator_count);
    errdefer self.slashing_penalties.deinit();
}
References
  1. The style guide recommends constructing large structs in-place by passing an out-pointer during initialization to avoid expensive copies. The init function should be modified to follow this pattern. (link)

Comment on lines +35 to +37
if (slashings.len != rewards.len) {
return error.InvalidSlashingPenaltiesLength;
}
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

A length mismatch between slashings and rewards indicates a programming error, as these are generated and passed internally. According to the style guide, programmer errors should be handled with assertions to cause a crash, rather than returning an error.

This helps to detect and fix such bugs early during development and testing.

        assert(slashings.len == rewards.len);
References
  1. The style guide states that assertions should be used to detect programmer errors, and that the only correct way to handle corrupt code is to crash. A length mismatch between internally generated data structures is a programmer error. (link)

Base automatically changed from comptime-fork to main January 29, 2026 15:55
@wemeetagain wemeetagain force-pushed the stf-slashings-penalties branch from 6ba877c to 4bed216 Compare January 29, 2026 16:03
@wemeetagain wemeetagain marked this pull request as ready for review January 29, 2026 16:04
@wemeetagain wemeetagain requested a review from a team as a code owner January 29, 2026 16:04
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bed216c85

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +39 to +41
for (rewards, penalties, balances, 0..) |reward, penalty, *balance, i| {
const total_penalty = penalty +| slashings[i];
const result = balance.* + reward -| total_penalty;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Apply slashing penalty before rewards to preserve spec order

The new combined update applies slashings[i] after rewards by doing balance + reward -| (penalty + slashings[i]). Previously, slashing was applied first via decreaseBalance and only then were rewards added, which differs when a slashing penalty exceeds the pre-reward balance (e.g., balance=5, reward=10, slash=8: old flow yields 10; new flow yields 7). This changes epoch transition results for slashed validators with low balances and can diverge from the spec ordering that applies slashings before rewards.

Useful? React with 👍 / 👎.

@GrapeBaBa GrapeBaBa changed the title refactor: optimize process_slashings refactor(WIP): optimize process_slashings Jan 30, 2026
@GrapeBaBa GrapeBaBa force-pushed the stf-slashings-penalties branch from 4bed216 to b6d7f94 Compare January 31, 2026 08:10
Copilot AI review requested due to automatic review settings January 31, 2026 08:10
Signed-off-by: Chen Kai <281165273grape@gmail.com>
@GrapeBaBa GrapeBaBa changed the title refactor(WIP): optimize process_slashings refactor: optimize process_slashings Jan 31, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors slashing penalty handling to align with Lodestar TypeScript implementation by computing slashing penalties once and applying them alongside rewards/penalties in a single balance update. This optimization reduces per-epoch overhead and improves cache initialization safety.

Changes:

  • Modified processSlashings to optionally return computed penalties instead of directly updating balances
  • Updated processRewardsAndPenalties to accept and apply slashing penalties during the combined balance update
  • Added slashing_penalties field to the epoch transition cache and hardened initialization with errdefer cleanup

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/state_transition/epoch/process_slashings.zig Added update_balance parameter to control whether penalties are applied immediately or returned for later application
src/state_transition/epoch/process_rewards_and_penalties.zig Modified to accept and apply slashing penalties during the balance update, with changed overflow behavior
src/state_transition/epoch/process_epoch.zig Updated to use the new two-phase approach: compute slashing penalties first, then apply with rewards/penalties
src/state_transition/cache/epoch_transition_cache.zig Added slashing_penalties field and improved error handling with errdefer chain in init method
test/spec/runner/epoch_processing.zig Updated test call sites to pass the new parameters
bench/state_transition/process_epoch.zig Updated benchmark call sites to use the new API
src/fork_types/fork_execution_payload.zig New file with "Fork"-prefixed versions of execution payload types (unrelated to PR purpose)
src/fork_types/fork_beacon_state.zig New file with "Fork"-prefixed versions of beacon state types (unrelated to PR purpose)
src/fork_types/fork_beacon_block.zig New file with "Fork"-prefixed versions of beacon block types (unrelated to PR purpose)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@memset(slashing_penalties, 0);
}

// Return early if there no index to slash
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

Typo in comment: "there no index" should be "there are no indices" or "there is no index".

Suggested change
// Return early if there no index to slash
// Return early if there are no indices to slash

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +39
if (slashing_penalties) |slashings| {
for (rewards, penalties, balances, 0..) |reward, penalty, *balance, i| {
const slashing: u64 = if (i < slashings.len) slashings[i] else 0;
balance.* = (try std.math.add(u64, balance.*, reward)) -| penalty -| slashing;
}
} else {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The original code used wrapping addition (balance.* + reward) which would overflow without error if the sum exceeded u64 max. The new code uses std.math.add with try, which will return an error on overflow. While this is stricter and likely correct (rewards should never cause overflow in a valid system), it's a breaking change in error handling behavior. If rewards could legitimately cause overflow in some edge cases, this would cause the epoch transition to fail where it previously would have silently wrapped. Consider whether this change is intentional and document it if so.

Suggested change
if (slashing_penalties) |slashings| {
for (rewards, penalties, balances, 0..) |reward, penalty, *balance, i| {
const slashing: u64 = if (i < slashings.len) slashings[i] else 0;
balance.* = (try std.math.add(u64, balance.*, reward)) -| penalty -| slashing;
}
} else {
if (slashing_penalties) |slashings| {
// NOTE: The original implementation used wrapping addition (e.g. `balance.* + reward`),
// which would silently overflow. We now use checked addition via `std.math.add` and
// propagate any overflow as an error via `try`. Overflow of a validator balance due
// to rewards is considered a violation of protocol invariants, so we intentionally
// fail the epoch transition instead of wrapping on overflow.
for (rewards, penalties, balances, 0..) |reward, penalty, *balance, i| {
const slashing: u64 = if (i < slashings.len) slashings[i] else 0;
balance.* = (try std.math.add(u64, balance.*, reward)) -| penalty -| slashing;
}
} else {
// Same reasoning as above: treat any overflow in `balance + reward` as a hard error
// rather than allowing it to wrap.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the correct behavior in the spec

balance.* = (try std.math.add(u64, balance.*, reward)) -| penalty -| slashing;
}
} else {
for (rewards, penalties, balances) |reward, penalty, *balance| {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The loop at line 35 uses a numeric index but the loop at line 40 doesn't. This creates an inconsistency where the slashing_penalties slice might be shorter than the rewards/penalties/balances slices. If there are more validators than slashing_penalties entries, the check 'if (i < slashings.len)' will treat missing entries as 0, which could be correct behavior. However, this asymmetry creates confusion - consider using the same iteration pattern for both branches or adding a comment explaining why the lengths might differ.

Suggested change
for (rewards, penalties, balances) |reward, penalty, *balance| {
// Use the same indexed iteration pattern as the slashing branch to keep behavior and intent symmetric.
for (rewards, penalties, balances, 0..) |reward, penalty, *balance, _| {

Copilot uses AI. Check for mistakes.
.registry_updates => try state_transition.processRegistryUpdates(fork, config, epoch_cache, fork_state, &epoch_transition_cache),
.rewards_and_penalties => try state_transition.processRewardsAndPenalties(fork, allocator, config, epoch_cache, fork_state, &epoch_transition_cache),
.slashings => try state_transition.processSlashings(fork, allocator, epoch_cache, fork_state, &epoch_transition_cache),
.rewards_and_penalties => try state_transition.processRewardsAndPenalties(fork, allocator, config, epoch_cache, fork_state, &epoch_transition_cache, null),
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

In the test case for slashings, the function is called with update_balance=true, which means it will directly modify balances and return an empty slice. However, the return value is being discarded with '_'. While this is correct behavior for this test, it would be clearer to either remove the assignment or add a comment explaining that when update_balance=true, the function applies penalties directly and returns an empty slice. This helps maintain consistency with the new API contract.

Suggested change
.rewards_and_penalties => try state_transition.processRewardsAndPenalties(fork, allocator, config, epoch_cache, fork_state, &epoch_transition_cache, null),
.rewards_and_penalties => try state_transition.processRewardsAndPenalties(fork, allocator, config, epoch_cache, fork_state, &epoch_transition_cache, null),
// When update_balance = true, processSlashings applies penalties directly and
// returns an empty slice; the result is intentionally discarded here.

Copilot uses AI. Check for mistakes.
}
} else {
for (rewards, penalties, balances) |reward, penalty, *balance| {
balance.* = (try std.math.add(u64, balance.*, reward)) -| penalty;
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

Same behavioral change as line 37: the original code used wrapping addition, while the new code uses checked addition with std.math.add. This change should be applied consistently in both branches (with and without slashing_penalties), but the API contract change should be documented.

Copilot uses AI. Check for mistakes.
// Return early if there no index to slash
if (cache.indices_to_slash.items.len == 0) {
return;
return if (update_balance) empty_penalties else slashing_penalties;
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

When update_balance is false and there are no indices to slash, the function returns the slashing_penalties slice which has been zeroed at line 25. However, when update_balance is true with no indices to slash, it returns empty_penalties (an empty slice). This asymmetry could be confusing. Consider either: (1) always returning slashing_penalties when update_balance=false, even if empty, for consistency, or (2) documenting why the return values differ in this edge case.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +100
pub fn init(self: *ReusedEpochTransitionCache, allocator: Allocator, validator_count: usize) !void {
self.allocator = allocator;
self.is_active_prev_epoch = try BoolArray.initCapacity(allocator, validator_count);
errdefer self.is_active_prev_epoch.deinit();
self.is_active_current_epoch = try BoolArray.initCapacity(allocator, validator_count);
errdefer self.is_active_current_epoch.deinit();
self.is_active_next_epoch = try BoolArray.initCapacity(allocator, validator_count);
errdefer self.is_active_next_epoch.deinit();
self.proposer_indices = try UsizeArray.initCapacity(allocator, validator_count);
errdefer self.proposer_indices.deinit();
self.inclusion_delays = try UsizeArray.initCapacity(allocator, validator_count);
errdefer self.inclusion_delays.deinit();
self.flags = try U8Array.initCapacity(allocator, validator_count);
errdefer self.flags.deinit();
self.next_epoch_shuffling_active_validator_indices = try std.ArrayList(ValidatorIndex).initCapacity(allocator, validator_count);
errdefer self.next_epoch_shuffling_active_validator_indices.deinit();
self.is_compounding_validator_arr = try BoolArray.initCapacity(allocator, validator_count);
errdefer self.is_compounding_validator_arr.deinit();
self.previous_epoch_participation = try U8Array.initCapacity(allocator, validator_count);
errdefer self.previous_epoch_participation.deinit();
self.current_epoch_participation = try U8Array.initCapacity(allocator, validator_count);
errdefer self.current_epoch_participation.deinit();
self.rewards = try U64Array.initCapacity(allocator, validator_count);
errdefer self.rewards.deinit();
self.penalties = try U64Array.initCapacity(allocator, validator_count);
errdefer self.penalties.deinit();
self.slashing_penalties = try U64Array.initCapacity(allocator, validator_count);
errdefer self.slashing_penalties.deinit();
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

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

The errdefer chain for cleanup is incomplete. If allocation of slashing_penalties succeeds (line 99) but then its errdefer (line 100) is triggered, all previously allocated fields from lines 75-97 will be properly cleaned up. However, there's no cleanup of self.allocator if an error occurs. While this is technically correct since allocator is just assigned (not allocated), the pattern is inconsistent. For clarity and to avoid potential future bugs, consider whether self.allocator should be set to undefined in the errdefer handlers, or add a comment explaining why it doesn't need cleanup.

Copilot uses AI. Check for mistakes.
@wemeetagain wemeetagain merged commit 4799592 into main Feb 2, 2026
11 checks passed
twoeths pushed a commit that referenced this pull request Mar 3, 2026
**Motivation**

- Align slashing handling with Lodestar TS: compute slashing penalties
once and apply them alongside rewards/penalties in a single balance
update.
- Reduce per‑epoch overhead and make cache initialization safer on error
paths.

**Description**

- `processSlashings` now returns a read‑only penalties slice when
`update_balance=false`; `processRewardsAndPenalties` consumes it, and
epoch/spec/bench call sites are updated accordingly.
- Added `slashing_penalties` to the reused epoch transition cache and
hardened `ReusedEpochTransitionCache.init` with errdefer cleanup.

Fix #195

---------

Signed-off-by: Chen Kai <281165273grape@gmail.com>
Co-authored-by: Cayman <caymannava@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

compute slashing penalties once and apply them alongside rewards/penalties in a single balance update

3 participants