Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix phase0 block reward in rewards API #5101

Merged
merged 15 commits into from
Sep 17, 2024
Merged

Conversation

dknopik
Copy link
Contributor

@dknopik dknopik commented Jan 20, 2024

Issues Addressed

Proposed Changes

Instead of relying on compute_block_reward for phase0, a new custom implementation for compute_beacon_block_attestation_reward_base is used, which handles edge cases like attestations from validators that were slashed after attesting and proposals in the epoch before Altair correctly.

Additionally, this PR incorporates and improves the changes proposed in PR #4882.

Additional Info

Some points I'm unsure about:

  • I use BeaconChain::state_at_slot to obtain needed states (at most twice per calculation). Another approach is using the BlockReplayer to advance the passed state, which would avoid (maybe) expensive lookups from the cold DB. However I am unsure if the added complexity is worth it, and maybe this is even a non-issue due to smart caching or something else I am unaware of.
  • I build the committee caches for the states I fetch, but only because I need the total active balance. BeaconState::build_total_active_balance_cache is not pub, is that intentional?

@dapplion dapplion added the work-in-progress PR is a work-in-progress label Jan 29, 2024
- Add test_rewards_base_slashings (testing sigp#5101)
- Improve fix to not include proposer reward in attestation reward API calculation (sigp#4856)
- Adjust test approach for phase0 tests: Pad with empty epochs to include all rewards in calculation
- Simplify and unify code across all reward tests
@dknopik dknopik marked this pull request as ready for review January 31, 2024 01:18
@dknopik
Copy link
Contributor Author

dknopik commented Jan 31, 2024

Ready for review - Happy to receive feedback, especially regarding the points listed in "Additional Info".

@dknopik dknopik changed the title [WIP] Fix phase0 block reward in rewards API (#4929) Fix phase0 block reward in rewards API Jan 31, 2024
@dapplion dapplion added ready-for-review The code is ready for review and removed work-in-progress PR is a work-in-progress labels Mar 18, 2024
@michaelsproul michaelsproul self-requested a review July 23, 2024 07:31
@zack53
Copy link

zack53 commented Sep 6, 2024

@michaelsproul any update on the review for this item?

Also, should we close out #4882 ?

@michaelsproul michaelsproul added the v6.0.0 New major release for hierarchical state diffs label Sep 10, 2024
@michaelsproul
Copy link
Member

I've got the tests passing on this again. Will review properly soon


let sqrt_total_active_balance =
SqrtTotalActiveBalance::new(processing_epoch_end.get_total_active_balance()?);
for attester in get_attesting_indices_from_state(state, attestation)? {
Copy link
Member

Choose a reason for hiding this comment

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

This algorithm is not quite right because it pays out for attestations regardless of whether this proposer was the the first one to include them.

In the spec, the epoch processing function iterates the previous_epoch_attestations and pays the proposer reward to the proposer who includes each attester's attestation with minimal inclusion_delay:

attestation = min([
    a for a in matching_source_attestations
    if index in get_attesting_indices(state, a)
], key=lambda a: a.inclusion_delay)
rewards[attestation.proposer_index] += get_proposer_reward(state, index)

It's quite common that a validator's attestation will be included multiple times in different aggregates, e.g. first at slot 10 and then again at slot 11. Only the proposer of the block of slot 10 gets the reward in this case (the slot 11 proposer either gets nothing for the attestation if it covered no new validators, or just the rewards for the newly covered validators for which this is their first inclusion on chain).

I think to remedy this the simplest fix would be to:

  • For each attester in each attestation included: check whether this is the inclusion with minimal inclusion_delay (usually the first inclusion, but not always). This would involve checking first against state.previous_epoch_attestations/state.current_epoch_attestations as appropriate and then checking against attestations already processed in this block (we don't want to double pay for multi inclusions).

One complication is the min by inclusion_delay. It would be helpful if a property like this held:

If an attestation is included for validator V at slot N in epoch N // 32 then it has a lower inclusion_delay than any other attestation from V included at a slot M > N.

Unfortunately this property does not hold in the case of slashable attestations made by validator V when the chain is not finalising promptly, in which case it could be that:

  • There are 2+ chains with different shufflings that descend from the same source checkpoint (this requires finality to lapse for >2 epochs as shufflings only diverge if their common ancestor block is >2 epochs ago).
  • Validator V signs two slashable attestations in epoch N // 32: one on each chain. Let M = N + 1 for simplicity. On the first chain they attest at e.g. slot N and on the second chain they attest at slot N - 5. The attestation from slot N - 5 is included at N for an inclusion_delay of 5, then the attestation from slot N is included at slot M = N + 1 for an inclusion_delay of 1. So it's possible for an attestation included at a later block to have a lower inclusion_delay, i.e. the property is false.

Therefore in the general case it's not safe to just use the state at the block's slot (state) to infer the rewards. I had hoped that if this property held we could use it to avoid loading the current_epoch_end/next_epoch_end states.

Instead I think we should just proceed with loading all 3 states most of the time and rely on the fact that all the phase0 states are quite small and should load quickly with the soon-to-be-merged hierarchical state diffs:

Copy link
Contributor Author

@dknopik dknopik Sep 11, 2024

Choose a reason for hiding this comment

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

Thanks for the review!
I've implemented a potential fix and a (rather messy) test for this.

I have a question about the second part: While such a pair of attestations would be obviously slashable, would they be really be includable into the "other" chain, respectively? As the shuffling is different, the attesting validator would likely be in a different committee for a different slot in each chain and therefore the validation would fail (for one of the attestations), as the signature does not match the expected validator, as per my understanding.

Copy link
Member

Choose a reason for hiding this comment

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

The beacon node is able to verify attestations from multiple chains, it will load the relevant state & committees and use those to verify the signature. Only in the case where it views the blocks from the other chain as completely invalid will it fail to process the blocks & attestations from that chain.

Copy link
Member

Choose a reason for hiding this comment

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

Oh no actually you're right. We can't include the attestations from the other fork on chain once the shufflings diverge, because process_attestation in the spec uses the committees from the current chain

I think this means the property I stated does hold. Which means we could use the passed in state for all of the calculations 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While I currently also think that your statement holds, I don't see why we can avoid the state loads.

In phase0, attestation rewards are processed at the epoch boundary. Therefore, we need the state, as a validator might be slashed at the epoch boundary but not at slot N where the attestation is included. Furthermore, we need the effective balance and total effective balance from the epoch boundary to properly calculate rewards.

Copy link
Member

Choose a reason for hiding this comment

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

Oooh yes, the slashing thing definitely prevents us from using the pre-state. Good catch.

The effective balances are immutable within an epoch, but you're right they'd also be a problem for the current epoch attestations we include which don't get rewarded until the end of the next epoch. Let's leave it as-is 😅

@michaelsproul michaelsproul added waiting-on-author The reviewer has suggested changes and awaits thier implementation. and removed ready-for-review The code is ready for review labels Sep 11, 2024
- check for attestations with lower inclusion delay
- check for double attestations in block
- add test
@michaelsproul michaelsproul removed the waiting-on-author The reviewer has suggested changes and awaits thier implementation. label Sep 16, 2024
@michaelsproul michaelsproul added the ready-for-review The code is ready for review label Sep 16, 2024
@dknopik
Copy link
Contributor Author

dknopik commented Sep 16, 2024

@michaelsproul test failure seems to be a false negative, can you pls retry?

@michaelsproul michaelsproul added ready-for-merge This PR is ready to merge. and removed ready-for-review The code is ready for review labels Sep 17, 2024
@michaelsproul
Copy link
Member

@mergify queue

Copy link

mergify bot commented Sep 17, 2024

queue

✅ The pull request has been merged automatically

The pull request has been merged automatically at 8b085dd

mergify bot added a commit that referenced this pull request Sep 17, 2024
@mergify mergify bot merged commit 8b085dd into sigp:unstable Sep 17, 2024
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
HTTP-API ready-for-merge This PR is ready to merge. v6.0.0 New major release for hierarchical state diffs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants