package das import ( "github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/time/slots" "github.com/pkg/errors" ) // NeedSpan represents the need for a resource over a span of slots. type NeedSpan struct { Begin primitives.Slot End primitives.Slot } // At returns whether blocks/blobs/columns are needed At the given slot. func (n NeedSpan) At(slot primitives.Slot) bool { return slot >= n.Begin && slot < n.End } // CurrentNeeds fields can be used to check whether the given resource type is needed // at a given slot. The values are based on the current slot, so this value shouldn't // be retained / reused across slots. type CurrentNeeds struct { Block NeedSpan Blob NeedSpan Col NeedSpan } // SyncNeeds holds configuration and state for determining what data is needed // at any given slot during backfill based on the current slot. type SyncNeeds struct { current func() primitives.Slot deneb primitives.Slot fulu primitives.Slot oldestSlotFlagPtr *primitives.Slot validOldestSlotPtr *primitives.Slot blockRetention primitives.Epoch blobRetentionFlag primitives.Epoch blobRetention primitives.Epoch colRetention primitives.Epoch } type CurrentSlotter func() primitives.Slot func NewSyncNeeds(current CurrentSlotter, oldestSlotFlagPtr *primitives.Slot, blobRetentionFlag primitives.Epoch) (SyncNeeds, error) { deneb, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch) if err != nil { return SyncNeeds{}, errors.Wrap(err, "deneb fork slot") } fuluBoundary := min(params.BeaconConfig().FuluForkEpoch, slots.MaxSafeEpoch()) fulu, err := slots.EpochStart(fuluBoundary) if err != nil { return SyncNeeds{}, errors.Wrap(err, "fulu fork slot") } sn := SyncNeeds{ current: func() primitives.Slot { return current() }, deneb: deneb, fulu: fulu, blobRetentionFlag: blobRetentionFlag, } // We apply the --blob-retention-epochs flag to both blob and column retention. sn.blobRetention = max(sn.blobRetentionFlag, params.BeaconConfig().MinEpochsForBlobsSidecarsRequest) sn.colRetention = max(sn.blobRetentionFlag, params.BeaconConfig().MinEpochsForDataColumnSidecarsRequest) // Override spec minimum block retention with user-provided flag only if it is lower than the spec minimum. sn.blockRetention = primitives.Epoch(params.BeaconConfig().MinEpochsForBlockRequests) if oldestSlotFlagPtr != nil { oldestEpoch := slots.ToEpoch(*oldestSlotFlagPtr) if oldestEpoch < sn.blockRetention { sn.validOldestSlotPtr = oldestSlotFlagPtr } else { log.WithField("backfill-oldest-slot", *oldestSlotFlagPtr). WithField("specMinSlot", syncEpochOffset(current(), sn.blockRetention)). Warn("Ignoring user-specified slot > MIN_EPOCHS_FOR_BLOCK_REQUESTS.") } } return sn, nil } // Currently is the main callback given to the different parts of backfill to determine // what resources are needed at a given slot. It assumes the current instance of SyncNeeds // is the result of calling initialize. func (n SyncNeeds) Currently() CurrentNeeds { current := n.current() c := CurrentNeeds{ Block: n.blockSpan(current), Blob: NeedSpan{Begin: syncEpochOffset(current, n.blobRetention), End: n.fulu}, Col: NeedSpan{Begin: syncEpochOffset(current, n.colRetention), End: current}, } // Adjust the minimums forward to the slots where the sidecar types were introduced c.Blob.Begin = max(c.Blob.Begin, n.deneb) c.Col.Begin = max(c.Col.Begin, n.fulu) return c } func (n SyncNeeds) blockSpan(current primitives.Slot) NeedSpan { if n.validOldestSlotPtr != nil { // assumes validation done in initialize() return NeedSpan{Begin: *n.validOldestSlotPtr, End: current} } return NeedSpan{Begin: syncEpochOffset(current, n.blockRetention), End: current} } func (n SyncNeeds) BlobRetentionChecker() RetentionChecker { return func(slot primitives.Slot) bool { current := n.Currently() return current.Blob.At(slot) } } func (n SyncNeeds) DataColumnRetentionChecker() RetentionChecker { return func(slot primitives.Slot) bool { current := n.Currently() return current.Col.At(slot) } } // syncEpochOffset subtracts a number of epochs as slots from the current slot, with underflow checks. // It returns slot 1 if the result would be 0 or underflow. It doesn't return slot 0 because the // genesis block needs to be specially synced (it doesn't have a valid signature). func syncEpochOffset(current primitives.Slot, subtract primitives.Epoch) primitives.Slot { minEpoch := min(subtract, slots.MaxSafeEpoch()) // compute slot offset - offset is a number of slots to go back from current (not an absolute slot). offset := slots.UnsafeEpochStart(minEpoch) // Undeflow protection: slot 0 is the genesis block, therefore the signature in it is invalid. // To prevent us from rejecting a batch, we restrict the minimum backfill batch till only slot 1 if offset >= current { return 1 } return current - offset }