Skip to content

Comments

Add Fulu fork boilerplate#14771

Merged
nalepae merged 12 commits intodevelopfrom
fulu-boilerplate
Jan 7, 2025
Merged

Add Fulu fork boilerplate#14771
nalepae merged 12 commits intodevelopfrom
fulu-boilerplate

Conversation

@nalepae
Copy link
Contributor

@nalepae nalepae commented Jan 3, 2025

What type of PR is this?
Other

What does this PR do? Why is it needed?
This PR implements the Fulu fork Boilerplate.
It does not implement any Fulu feature.
Within this PR, the Fulu fork behaves exactly as the Electra Fork.

Other notes for review
For an complete, optimal, and enjoyable experience, please read commit by commit.

The current state of the Fulu specification does not modify neither the beacon state nor the beacon block.
However, this pull request does add a new Fulu beacon state and beacon block definition, which are identical to Electra beacon state and block.

There is no benefit to copy the beacon block and state declaration if there is no change in the Fulu update.
However, I'm pretty confident, knowing the Ethereum history, that before the Fulu fork effectively happens, at least one EIP will be introduced needing at least one beacon state/block modified/added field.

I won't launch a polymarket about that, but all the previous forks both modified the Beacon block and the Beacon state ...
So I chose to do the heavy lifting work now so nobody has to do it later.

If, finally no change occurs the Beacon Block or State, we will just have to delete the proto files, re-run code generation, and quickly fix the newly xxx undefined errors in the codebase.

  • I have read CONTRIBUTING.md.
  • I have made an appropriate entry to CHANGELOG.md.
  • I have added a description to this PR with sufficient context for reviewers to understand this PR.

@nalepae nalepae force-pushed the fulu-boilerplate branch 9 times, most recently from 8da3e00 to bcfaff8 Compare January 5, 2025 15:22
@nalepae nalepae added the fulu label Jan 5, 2025
@nalepae nalepae changed the title Fulu boilerplate Add Fulu fork boilerplate Jan 5, 2025
@nalepae nalepae marked this pull request as ready for review January 6, 2025 10:08
@nalepae nalepae requested a review from a team as a code owner January 6, 2025 10:08
InactivityScores []string `json:"inactivity_scores"`
CurrentSyncCommittee *SyncCommittee `json:"current_sync_committee"`
NextSyncCommittee *SyncCommittee `json:"next_sync_committee"`
LatestExecutionPayloadHeader *ExecutionPayloadHeaderElectra `json:"latest_execution_payload_header"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
LatestExecutionPayloadHeader *ExecutionPayloadHeaderElectra `json:"latest_execution_payload_header"`
LatestExecutionPayloadHeader *ExecutionPayloadHeaderFulu `json:"latest_execution_payload_header"`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -852,30 +862,35 @@ func encodeBlock(blk interfaces.ReadOnlySignedBeaconBlock) ([]byte, error) {

func keyForBlock(blk interfaces.ReadOnlySignedBeaconBlock) ([]byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The reason why we have it backwards is because during normal operations the block/state will always be in the latest fork. Although a micro-optimization, it does make sense to do it this way. I think we should write such code for every if or switch

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a1e7efa.

},
bytesutil.ToBytes4(params.BeaconConfig().FuluForkVersion): func() (interfaces.ReadOnlySignedBeaconBlock, error) {
return blocks.NewSignedBeaconBlock(
&ethpb.SignedBeaconBlockFulu{Block: &ethpb.BeaconBlockFulu{Body: &ethpb.BeaconBlockBodyFulu{ExecutionPayload: &enginev1.ExecutionPayloadDeneb{}}}},
Copy link
Contributor

Choose a reason for hiding this comment

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

You should probably create ExecutionPayloadFulu and ExecutionPayloadHeaderFulu somewhere in the /proto/engine/v1 directory, just as Electra types are created in electra.go. And use it here and everywhere else.

Copy link
Contributor Author

@nalepae nalepae Jan 7, 2025

Choose a reason for hiding this comment

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

Actually my target was to remove ExecutionPayloadElectra and ExecutionPayloadHeaderElectra as well and to do the exact contrary.

I thought I did it, I probably messed up something writing this PR.

Rationale: In all parts of the code, when a struct does not change between fork <n> and fork <n+1>, we just use the struct of fork <n> without aliasing it to the new fork.

Aka, we don't write things such as:

ExecutionPayloadElectra       = ExecutionPayloadDeneb

So, my idea here is to remove those 2 type aliases, and use the Deneb version both in Electra and Fulu.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, looks like that's what we should do

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in b43d1a6.

v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), mock.MatchedBy(func(req *eth.GenericSignedBeaconBlock) bool {
block, ok := req.Block.(*eth.GenericSignedBeaconBlock_Electra)
converted, err := structs.SignedBeaconBlockContentsElectraFromConsensus(block.Electra)
// Convert back Fulu to Electra when there is at least one difference between Electra and Fulu blocks.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand this. Why can't we have a simple Electra test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because of the publishBlock function, which is called in the PublishBlock function, which itself is called in the test.

The way the publishBlock function works is it goes, in reverse, fork after fork to determine if a block if of type Fulu, Electra, Deneb etc...

To decide if a block is an Fulu block, it only looks if the block can be used as an Fulu block (by using unmarshalStrict.)

Currently, there is no difference between a Fulu block and an Electra block. So, when trying to unmarshal an Electra block, it will actually be considered as a Fulu block.

Note that this is absolutely not an issue for the publishBlock function itself: An Electra block is correctly published, and a Fulu block is correctly published as well.

Because this failed test will stop to fail once there is at least one difference between an Electra block and a Fulu block, then I think it's simpler to, at the moment, patch the test. This patch will simply need to be removed once there is at least one difference between and Electra block and a Fulu block.

require.ErrorContains(t, "block cannot be nil", err)
})

// Test for Fulu version
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: These comments don't have any value since the test case is already named after the fork

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hum indeed I over-commented it.

My idea was to have one comment by Fork (and not by test), as fixed in 83b895f.

Why?
Because if a new test appears (or a new fork), I would ensure the next developer to follow this logic, and to group tests by fork, and not by test type (block, blinded block...)

return b, nil
}

// InitializeFromProtoUnsafeElectra directly uses the beacon state protobuf fields
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// InitializeFromProtoUnsafeElectra directly uses the beacon state protobuf fields
// InitializeFromProtoUnsafeFulu directly uses the beacon state protobuf fields

Copy link
Contributor Author

Choose a reason for hiding this comment

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

b.sharedFieldReferences[types.PreviousEpochParticipationBits] = stateutil.NewRef(1)
b.sharedFieldReferences[types.CurrentEpochParticipationBits] = stateutil.NewRef(1)
b.sharedFieldReferences[types.LatestExecutionPayloadHeaderDeneb] = stateutil.NewRef(1) // New in Electra.
b.sharedFieldReferences[types.HistoricalSummaries] = stateutil.NewRef(1) // New in Capella.
Copy link
Contributor

Choose a reason for hiding this comment

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

I know it's not exactly the scope of this PR since the wrong comment had been there before, but HistoricalSummaries were added in Deneb, not in Electra.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hum, it seems it has been introduced in Capella (as stated by the comment).
https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate

Comment on lines 668 to 671
type (
ExecutionPayloadFulu = ExecutionPayloadDeneb
ExecutionPayloadHeaderFulu = ExecutionPayloadHeaderDeneb
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Given the discussion we had, looks like we can remove this (and the Electra equivalent, if there is one)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in b43d1a6.

},
bytesutil.ToBytes4(params.BeaconConfig().FuluForkVersion): func() (interfaces.ReadOnlySignedBeaconBlock, error) {
return blocks.NewSignedBeaconBlock(
&ethpb.SignedBeaconBlockFulu{Block: &ethpb.BeaconBlockFulu{Body: &ethpb.BeaconBlockBodyFulu{ExecutionPayload: &enginev1.ExecutionPayloadDeneb{}}}},
Copy link
Contributor

Choose a reason for hiding this comment

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

Right, looks like that's what we should do

nalepae and others added 9 commits January 7, 2025 19:11
Rationale:
This log is the only one notifying the user a new fork happened.
A new fork is always a little bit stressful for a node operator.
Having at least one log indicating the client switched fork is something useful.
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
rkapka
rkapka previously approved these changes Jan 7, 2025
@nalepae nalepae added this pull request to the merge queue Jan 7, 2025
Merged via the queue into develop with commit c48d409 Jan 7, 2025
6 checks passed
@nalepae nalepae deleted the fulu-boilerplate branch January 7, 2025 20:17
@rkapka rkapka mentioned this pull request Jan 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants