-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Update Earliest available slot when pruning #15694
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
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Updates the beacon chain node to properly maintain the earliest available slot value when pruning operations occur. This ensures peers receive accurate information about data availability after pruning removes older blocks and data columns.
- Integrates custody updater functionality with both pruner and data column storage services
- Modifies pruning logic to update earliest available slot after data removal
- Adds comprehensive test coverage for custody update scenarios
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
changelog/satushh-update-easlot-pruning.md | Documents the feature in changelog |
beacon-chain/node/node.go | Configures P2P service as custody updater for pruner and data column storage |
beacon-chain/db/pruner/pruner.go | Adds custody updater interface and updates earliest available slot after pruning |
beacon-chain/db/pruner/pruner_test.go | Tests custody update functionality with mock implementations |
beacon-chain/db/filesystem/data_column.go | Adds custody updater support to data column storage pruning |
beacon-chain/db/filesystem/data_column_test.go | Tests data column storage custody updates |
beacon-chain/db/pruner/BUILD.bazel | Adds test dependency for assert library |
beacon-chain/db/filesystem/BUILD.bazel | Adds test dependency for assert library |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
beacon-chain/db/pruner/pruner.go
Outdated
custodyGroupCount, err := p.custody.CustodyGroupCount() | ||
if err != nil { | ||
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after pruning") | ||
return nil |
Copilot
AI
Sep 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning nil here prevents the method from properly signaling an error state to callers. This should return the original error instead of nil since the custody update failure represents a genuine error condition that calling code should be aware of.
return nil | |
return errors.Wrap(err, "failed to get custody group count for earliest available slot update") |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the pruning itself is successful.
return | ||
} | ||
|
||
// Get current custody group count to preserve it during update | ||
custodyGroupCount, err := dcs.custody.CustodyGroupCount() | ||
if err != nil { | ||
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after data column pruning") | ||
return |
Copilot
AI
Sep 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early returns from this method prevent the cleanup of muChans map which happens after the custody update logic. The custody update errors should be logged but not cause early returns, or the muChans cleanup should be moved before the custody update logic.
return | |
} | |
// Get current custody group count to preserve it during update | |
custodyGroupCount, err := dcs.custody.CustodyGroupCount() | |
if err != nil { | |
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after data column pruning") | |
return | |
// Do not return early; continue to allow all cleanup logic to run | |
} | |
// Get current custody group count to preserve it during update | |
custodyGroupCount, err := dcs.custody.CustodyGroupCount() | |
if err != nil { | |
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after data column pruning") | |
// Do not return early; continue to allow all cleanup logic to run |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what custodyGroupCount
should be set to here if we can't fetch it.
return | ||
} | ||
|
||
// Get current custody group count to preserve it during update | ||
custodyGroupCount, err := dcs.custody.CustodyGroupCount() | ||
if err != nil { | ||
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after data column pruning") | ||
return |
Copilot
AI
Sep 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early returns from this method prevent the cleanup of muChans map which happens after the custody update logic. The custody update errors should be logged but not cause early returns, or the muChans cleanup should be moved before the custody update logic.
return | |
} | |
// Get current custody group count to preserve it during update | |
custodyGroupCount, err := dcs.custody.CustodyGroupCount() | |
if err != nil { | |
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after data column pruning") | |
return | |
// Do not return early; continue to cleanup | |
} | |
// Get current custody group count to preserve it during update | |
custodyGroupCount, err := dcs.custody.CustodyGroupCount() | |
if err != nil { | |
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after data column pruning") | |
// Do not return early; continue to cleanup |
Copilot uses AI. Check for mistakes.
@@ -0,0 +1,3 @@ | |||
### Added | |||
|
|||
- Update the earliest available slot while pruning. No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a bit more to this changelog? These are used to compile the release notes and this isn't enough context to understand why this change is important
beacon-chain/db/pruner/pruner.go
Outdated
if p.custody != nil { | ||
earliestAvailableSlot := pruneUpto + 1 | ||
|
||
// Get current custody group count to preserve it during update | ||
custodyGroupCount, err := p.custody.CustodyGroupCount() | ||
if err != nil { | ||
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after pruning") | ||
return nil | ||
} | ||
|
||
// Update the custody info with new earliest available slot | ||
_, _, err = p.custody.UpdateCustodyInfo(earliestAvailableSlot, custodyGroupCount) | ||
if err != nil { | ||
log.WithError(err).WithField("earliestAvailableSlot", earliestAvailableSlot). | ||
Error("Failed to update earliest available slot after pruning") | ||
} else { | ||
log.WithField("earliestAvailableSlot", earliestAvailableSlot). | ||
Debug("Updated earliest available slot after pruning") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this should probably into its own method for a better abstraction/separation of concerns.
if p.custody != nil { | |
earliestAvailableSlot := pruneUpto + 1 | |
// Get current custody group count to preserve it during update | |
custodyGroupCount, err := p.custody.CustodyGroupCount() | |
if err != nil { | |
log.WithError(err).Error("Failed to get custody group count, cannot update earliest available slot after pruning") | |
return nil | |
} | |
// Update the custody info with new earliest available slot | |
_, _, err = p.custody.UpdateCustodyInfo(earliestAvailableSlot, custodyGroupCount) | |
if err != nil { | |
log.WithError(err).WithField("earliestAvailableSlot", earliestAvailableSlot). | |
Error("Failed to update earliest available slot after pruning") | |
} else { | |
log.WithField("earliestAvailableSlot", earliestAvailableSlot). | |
Debug("Updated earliest available slot after pruning") | |
} | |
} | |
p.updateEarliestSlot(pruneUpto+1) |
|
||
// Update the earliest available slot via injected updater after pruning. | ||
// The earliest available slot is the first slot after the pruned epochs. | ||
if dcs.custody != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same feedback here about moving the "update earliest slot" logic out of the prune method and into its own method.
require.NoError(t, err) | ||
|
||
// Test the pruning calculation | ||
currentSlot := primitives.Slot(20000) // Reasonable slot number for minimal config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't assume this kind of "reasonable" number. If the ETH spec changes, then this test may break.
Instead, override the config.MinEpochsForBlockRequests
at the start of the test.
Unfortunatly, for some historical reasons, this value is recomputed in the MinEpochsForBlockRequests
function.
This function was defined and used before the corresponding value was introduced in the spec.
So, IMO the cleanest way to do it is:
- In the Prysm codebase, remove the
MinEpochsForBlockRequests
and directly useconfig.MinEpochsForBlockRequests
whereMinEpochsForBlockRequests
where used. - In this test, override
config.MinEpochsForBlockRequests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed here: f324089
beacon-chain/node/node.go
Outdated
} | ||
|
||
// P2P service is required for pruner to update earliest available slot after pruning | ||
p2pService := b.fetchP2P() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check can be done directly in pruner.New
, so responsability is shifter from caller to callee.
beacon-chain/db/kv/custody.go
Outdated
|
||
// UpdateCustodyInfo atomically updates the custody group count only it is greater than the stored one. | ||
// In this case, it also updates the earliest available slot with the provided value. | ||
// UpdateCustodyInfo updates the custody group count if it is greater than the stored one, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why atomically
had been removed?
beacon-chain/db/kv/custody.go
Outdated
// Update earliest available slot if it advanced. | ||
// This is for pruning to work correctly as blocks are pruned, | ||
// the earliest available slot moves forward independently of custody group changes. | ||
if earliestAvailableSlot > storedEarliestAvailableSlot { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Increasing the earliestAvailableSlot
only while not increasing the custodyGroupCount
at the same time should not be allowed is the new value of earliestAvailableSlot
is higher than the minimum slot to be stored.
Else it means that a node could simply choose to put earliestAvailableslot = currentSlot
and do not serve any data to its peers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as #15694 (comment) actually.
beacon-chain/db/pruner/pruner.go
Outdated
// custodyUpdater is a tiny interface that p2p service implements; kept here to avoid | ||
// importing the p2p package and creating a cycle. | ||
type custodyUpdater interface { | ||
CustodyGroupCount(context.Context) (uint64, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CustodyGroupCount
can be renamed into GroupCount
to avoid
...custody.CutodyGroupCount
beacon-chain/db/pruner/pruner.go
Outdated
|
||
// updateEarliestSlot updates the earliest available slot via the injected custody updater | ||
// and also persists it to the database. | ||
func (p *Service) updateEarliestSlot(earliestAvailableSlot primitives.Slot) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateEarliestSlot
==> updateEarliestAvailableSlot
to stick with the spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should have the same checks/protections as UpdateEarliestAvailableSlot
on the P2P side.
"duration": time.Since(tt), | ||
"currentSlot": slot, | ||
"batchSize": defaultPrunableBatchSize, | ||
"numBatches": numBatches, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This log should be displayed after updating the earliest available slot to reflect the real state of the node.
beacon-chain/p2p/custody.go
Outdated
var minRequiredEpoch primitives.Epoch | ||
if currentEpoch > minEpochsForBlocks { | ||
minRequiredEpoch = currentEpoch - minEpochsForBlocks | ||
} else { | ||
minRequiredEpoch = 0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var minRequiredEpoch primitives.Epoch | |
if currentEpoch > minEpochsForBlocks { | |
minRequiredEpoch = currentEpoch - minEpochsForBlocks | |
} else { | |
minRequiredEpoch = 0 | |
} | |
minRequiredEpoch := primitives.Epoch(0) | |
if currentEpoch > minEpochsForBlocks { | |
minRequiredEpoch = currentEpoch - minEpochsForBlocks | |
} |
beacon-chain/db/pruner/pruner.go
Outdated
|
||
// updateEarliestSlot updates the earliest available slot via the injected custody updater | ||
// and also persists it to the database. | ||
func (p *Service) updateEarliestSlot(earliestAvailableSlot primitives.Slot) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should have the same checks/protections as UpdateEarliestAvailableSlot
on the P2P side.
beacon-chain/p2p/custody_test.go
Outdated
params.OverrideBeaconConfig(config) | ||
|
||
t.Run("Valid update", func(t *testing.T) { | ||
const initialSlot primitives.Slot = 50 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to use
const (
initialSlot primitives.Slot = 50
const newSlot primitives.Slot = 100
...
)
grouped declaration.
beacon-chain/db/pruner/pruner.go
Outdated
} | ||
|
||
// Update the earliest available slot | ||
if err := p.custody.UpdateEarliestAvailableSlot(earliestAvailableSlot); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p.custody.UpdateEarliestAvailableSlot
could return the (possibly updated) custody group count to avoid to call p.custody.CustodyGroupCount
later, before saving into the DB.
(As done in UpdateCustodyInfo
.)
…iestSlot. UpdateEarliestAvailableSlot returns cgc
…vailableSlot returns cgc now
defer s.custodyInfoLock.Unlock() | ||
|
||
if s.custodyInfo == nil { | ||
return 0, 0, errors.New("no custody info available") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Add a test case for that.
beacon-chain/db/kv/custody.go
Outdated
// required slot (based on MIN_EPOCHS_FOR_BLOCK_REQUESTS from current time). | ||
// This prevents nodes from arbitrarily refusing to serve mandatory historical data. | ||
if earliestAvailableSlot > storedEarliestAvailableSlot { | ||
// If custody group count is NOT increasing, validate the increase is allowed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to invert the if
condition (and return in the if
) to reduce the indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens in case of backfill? Is the stored earliestAvailableSlot
value decreased in the DB?
…tAvailableSlot) & undo changes made to UpdateCustodyInfo
What type of PR is this?
Bug fix
What does this PR do? Why is it needed?
earliest_available_slot needs to be updated (if required) when pruning occurs. Since peers will think we have data for slot
n
, while in reality, we wiped data up to slotm
withn < m
.Which issues(s) does this PR fix?
Part of #14129
Other notes for review
Steps to reproduce:
Clone the branches from these PRs:
In the
ethereum-genesis-generator
folder, run:docker build -t ethereum-genesis-generator:local .
Build local prysm:
Acknowledgements