Use explicit slot component timing configs (#15999)

* Use new timing configs (due BPS)

* Bastin's feedback
This commit is contained in:
terence
2025-11-13 16:55:32 -05:00
committed by GitHub
parent 7ba60d93f2
commit f77b78943a
35 changed files with 402 additions and 100 deletions

View File

@@ -16,11 +16,11 @@ import (
// testBlobSetup holds common test data for blob reconstruction tests.
type testBlobSetup struct {
blobCount int
blobs []kzg.Blob
roBlock blocks.ROBlock
roDataColumnSidecars []blocks.RODataColumn
verifiedRoDataColumnSidecars []blocks.VerifiedRODataColumn
blobCount int
blobs []kzg.Blob
roBlock blocks.ROBlock
roDataColumnSidecars []blocks.RODataColumn
verifiedRoDataColumnSidecars []blocks.VerifiedRODataColumn
}
// setupTestBlobs creates a complete test setup with blobs, cells, proofs, and data column sidecars.

View File

@@ -146,7 +146,7 @@ func TestEnsureEmbeddedGenesis(t *testing.T) {
params.SetupTestConfigCleanup(t)
// Embedded Genesis works with Mainnet config
cfg := params.MainnetConfig()
cfg.SecondsPerSlot = 1
cfg.SlotDurationMilliseconds = 1000
undo, err := params.SetActiveWithUndo(cfg)
require.NoError(t, err)
defer func() {

View File

@@ -134,7 +134,7 @@ func (n *Node) setNodeAndParentValidated(ctx context.Context) error {
// slot will have secs = 3 below.
func (n *Node) arrivedEarly(genesis time.Time) (bool, error) {
sss, err := slots.SinceSlotStart(n.slot, genesis, n.timestamp.Truncate(time.Second)) // Truncate such that 3.9999 seconds will have a value of 3.
votingWindow := time.Duration(params.BeaconConfig().SecondsPerSlot/params.BeaconConfig().IntervalsPerSlot) * time.Second
votingWindow := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().AttestationDueBPS)
return sss < votingWindow, err
}

View File

@@ -134,8 +134,8 @@ func TestForkChoice_GetProposerHead(t *testing.T) {
headRoot, err := f.Head(ctx)
require.NoError(t, err)
require.Equal(t, blk.Root(), headRoot)
orphanLateBlockFirstThreshold := params.BeaconConfig().SecondsPerSlot / params.BeaconConfig().IntervalsPerSlot
f.store.headNode.timestamp.Add(-1 * time.Duration(params.BeaconConfig().SecondsPerSlot-orphanLateBlockFirstThreshold) * time.Second)
orphanLateBlockFirstThreshold := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().AttestationDueBPS)
f.store.headNode.timestamp.Add(-1 * (params.BeaconConfig().SlotDuration() - orphanLateBlockFirstThreshold))
t.Run("head is weak", func(t *testing.T) {
require.Equal(t, parentRoot, f.GetProposerHead())
})

View File

@@ -137,7 +137,7 @@ func (s *Store) insert(ctx context.Context,
if err != nil {
return nil, fmt.Errorf("could not determine time since current slot started: %w", err)
}
boostThreshold := time.Duration(params.BeaconConfig().SecondsPerSlot/params.BeaconConfig().IntervalsPerSlot) * time.Second
boostThreshold := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().AttestationDueBPS)
isFirstBlock := s.proposerBoostRoot == [32]byte{}
if currentSlot == slot && sss < boostThreshold && isFirstBlock {
s.proposerBoostRoot = root

View File

@@ -286,7 +286,7 @@ func (s *Service) BroadcastLightClientOptimisticUpdate(ctx context.Context, upda
return err
}
timeSinceSlotStart := time.Since(slotStart)
expectedDelay := slots.ComponentDuration(primitives.BP(params.BeaconConfig().SyncMessageDueBPS))
expectedDelay := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().SyncMessageDueBPS)
if timeSinceSlotStart < expectedDelay {
waitDuration := expectedDelay - timeSinceSlotStart
<-time.After(waitDuration)
@@ -320,7 +320,7 @@ func (s *Service) BroadcastLightClientFinalityUpdate(ctx context.Context, update
return err
}
timeSinceSlotStart := time.Since(slotStart)
expectedDelay := slots.ComponentDuration(primitives.BP(params.BeaconConfig().SyncMessageDueBPS))
expectedDelay := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().SyncMessageDueBPS)
if timeSinceSlotStart < expectedDelay {
waitDuration := expectedDelay - timeSinceSlotStart
<-time.After(waitDuration)

View File

@@ -22,7 +22,6 @@ import (
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/consensus-types/wrapper"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
@@ -598,7 +597,7 @@ func TestService_BroadcastLightClientOptimisticUpdate(t *testing.T) {
slotStartTime, err := slots.StartTime(p.genesisTime, msg.SignatureSlot())
require.NoError(t, err)
expectedDelay := slots.ComponentDuration(primitives.BP(params.BeaconConfig().SyncMessageDueBPS))
expectedDelay := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().SyncMessageDueBPS)
if time.Now().Before(slotStartTime.Add(expectedDelay)) {
tt.Errorf("Message received too early, now %v, expected at least %v", time.Now(), slotStartTime.Add(expectedDelay))
}
@@ -674,7 +673,7 @@ func TestService_BroadcastLightClientFinalityUpdate(t *testing.T) {
slotStartTime, err := slots.StartTime(p.genesisTime, msg.SignatureSlot())
require.NoError(t, err)
expectedDelay := slots.ComponentDuration(primitives.BP(params.BeaconConfig().SyncMessageDueBPS))
expectedDelay := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().SyncMessageDueBPS)
if time.Now().Before(slotStartTime.Add(expectedDelay)) {
tt.Errorf("Message received too early, now %v, expected at least %v", time.Now(), slotStartTime.Add(expectedDelay))
}

View File

@@ -128,7 +128,7 @@ func TestService_Start_NoDiscoverFlag(t *testing.T) {
beaconCfg.AltairForkEpoch = 0
beaconCfg.BellatrixForkEpoch = 0
beaconCfg.CapellaForkEpoch = 0
beaconCfg.SecondsPerSlot = 1
beaconCfg.SlotDurationMilliseconds = 1000
params.OverrideBeaconConfig(beaconCfg)
exitRoutine := make(chan bool)

View File

@@ -87,6 +87,7 @@ func TestGetSpec(t *testing.T) {
config.ETH1AddressWithdrawalPrefixByte = byte('c')
config.GenesisDelay = 24
config.SecondsPerSlot = 25
config.SlotDurationMilliseconds = 120
config.MinAttestationInclusionDelay = 26
config.SlotsPerEpoch = 27
config.MinSeedLookahead = 28
@@ -129,6 +130,10 @@ func TestGetSpec(t *testing.T) {
config.ProportionalSlashingMultiplierAltair = 69
config.InactivityScoreRecoveryRate = 70
config.MinSyncCommitteeParticipants = 71
config.ProposerReorgCutoffBPS = primitives.BP(121)
config.AttestationDueBPS = primitives.BP(122)
config.AggregrateDueBPS = primitives.BP(123)
config.ContributionDueBPS = primitives.BP(124)
config.TerminalBlockHash = common.HexToHash("TerminalBlockHash")
config.TerminalBlockHashActivationEpoch = 72
config.TerminalTotalDifficulty = "73"
@@ -201,7 +206,7 @@ func TestGetSpec(t *testing.T) {
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
data, ok := resp.Data.(map[string]interface{})
require.Equal(t, true, ok)
assert.Equal(t, 171, len(data))
assert.Equal(t, 176, len(data))
for k, v := range data {
t.Run(k, func(t *testing.T) {
switch k {
@@ -291,6 +296,8 @@ func TestGetSpec(t *testing.T) {
assert.Equal(t, "24", v)
case "SECONDS_PER_SLOT":
assert.Equal(t, "25", v)
case "SLOT_DURATION_MS":
assert.Equal(t, "120", v)
case "MIN_ATTESTATION_INCLUSION_DELAY":
assert.Equal(t, "26", v)
case "SLOTS_PER_EPOCH":
@@ -447,6 +454,14 @@ func TestGetSpec(t *testing.T) {
assert.Equal(t, "20", v)
case "REORG_PARENT_WEIGHT_THRESHOLD":
assert.Equal(t, "160", v)
case "PROPOSER_REORG_CUTOFF_BPS":
assert.Equal(t, "121", v)
case "ATTESTATION_DUE_BPS":
assert.Equal(t, "122", v)
case "AGGREGRATE_DUE_BPS":
assert.Equal(t, "123", v)
case "CONTRIBUTION_DUE_BPS":
assert.Equal(t, "124", v)
case "MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT":
assert.Equal(t, "8", v)
case "MAX_REQUEST_LIGHT_CLIENT_UPDATES":

View File

@@ -23,7 +23,7 @@ import (
func TestGoodByeRPCHandler_Disconnects_With_Peer(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.MainnetConfig()
cfg.SecondsPerSlot = 1
cfg.SlotDurationMilliseconds = 1000
params.OverrideBeaconConfig(cfg)
p1 := p2ptest.NewTestP2P(t)

View File

@@ -130,7 +130,7 @@ func TestSubscribe_UnsubscribeTopic(t *testing.T) {
func TestSubscribe_ReceivesAttesterSlashing(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.MainnetConfig()
cfg.SecondsPerSlot = 1
cfg.SlotDurationMilliseconds = 1000
params.OverrideBeaconConfig(cfg)
p2pService := p2ptest.NewTestP2P(t)
@@ -443,7 +443,7 @@ func Test_wrapAndReportValidation(t *testing.T) {
func TestFilterSubnetPeers(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.MainnetConfig()
cfg.SecondsPerSlot = 1
cfg.SlotDurationMilliseconds = 1000
params.OverrideBeaconConfig(cfg)
gFlags := new(flags.GlobalFlags)
@@ -457,8 +457,9 @@ func TestFilterSubnetPeers(t *testing.T) {
currSlot := primitives.Slot(100)
gt := time.Now()
slotDuration := params.BeaconConfig().SlotDuration()
genPlus100 := func() time.Time {
return gt.Add(time.Second * time.Duration(uint64(currSlot)*params.BeaconConfig().SecondsPerSlot))
return gt.Add(time.Duration(uint64(currSlot)) * slotDuration)
}
chain := &mockChain.ChainService{
Genesis: gt,
@@ -525,7 +526,7 @@ func TestFilterSubnetPeers(t *testing.T) {
func TestSubscribeWithSyncSubnets_DynamicOK(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.MainnetConfig()
cfg.SecondsPerSlot = 1
cfg.SlotDurationMilliseconds = 1000
params.OverrideBeaconConfig(cfg)
p := p2ptest.NewTestP2P(t)

View File

@@ -6,7 +6,6 @@ import (
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
"github.com/OffchainLabs/prysm/v7/time/slots"
@@ -60,7 +59,7 @@ func (s *Service) validateLightClientOptimisticUpdate(ctx context.Context, pid p
return pubsub.ValidationReject, nil
}
earliestValidTime := slotStart.
Add(slots.ComponentDuration(primitives.BP(params.BeaconConfig().SyncMessageDueBPS))).
Add(params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().SyncMessageDueBPS)).
Add(-params.BeaconConfig().MaximumGossipClockDisparityDuration())
if s.cfg.clock.Now().Before(earliestValidTime) {
log.Debug("Newly received light client optimistic update ignored. not enough time passed for block to propagate")
@@ -130,7 +129,7 @@ func (s *Service) validateLightClientFinalityUpdate(ctx context.Context, pid pee
return pubsub.ValidationReject, nil
}
earliestValidTime := slotStart.
Add(slots.ComponentDuration(primitives.BP(params.BeaconConfig().SyncMessageDueBPS))).
Add(params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().SyncMessageDueBPS)).
Add(-params.BeaconConfig().MaximumGossipClockDisparityDuration())
if s.cfg.clock.Now().Before(earliestValidTime) {
log.Debug("Newly received light client finality update ignored. not enough time passed for block to propagate")

View File

@@ -16,11 +16,9 @@ import (
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/testing"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
"github.com/OffchainLabs/prysm/v7/time/slots"
pubsub "github.com/libp2p/go-libp2p-pubsub"
pb "github.com/libp2p/go-libp2p-pubsub/pb"
)
@@ -83,7 +81,7 @@ func TestValidateLightClientOptimisticUpdate(t *testing.T) {
},
{
name: "not enough time passed",
genesisDrift: -int(math.Ceil(float64(slots.ComponentDuration(primitives.BP(params.BeaconConfig().SyncMessageDueBPS))) / float64(time.Second))),
genesisDrift: -int(math.Ceil(float64(params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().SyncMessageDueBPS)) / float64(time.Second))),
oldUpdateOptions: []util.LightClientOption{},
newUpdateOptions: []util.LightClientOption{},
expectedResult: pubsub.ValidationIgnore,
@@ -209,7 +207,7 @@ func TestValidateLightClientFinalityUpdate(t *testing.T) {
},
{
name: "not enough time passed",
genesisDrift: -int(math.Ceil(float64(slots.ComponentDuration(primitives.BP(params.BeaconConfig().SyncMessageDueBPS))) / float64(time.Second))),
genesisDrift: -int(math.Ceil(float64(params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().SyncMessageDueBPS)) / float64(time.Second))),
oldUpdateOptions: []util.LightClientOption{},
newUpdateOptions: []util.LightClientOption{},
expectedResult: pubsub.ValidationIgnore,

View File

@@ -0,0 +1,3 @@
### Changed
- Use explicit slot component timing configs

View File

@@ -4,7 +4,7 @@ import "github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
const BasisPoints = primitives.BP(10000)
// SlotBP returns the basis points for a given slot.
// SlotBP returns the duration of a slot expressed in milliseconds, represented as basis points of a slot.
func SlotBP() primitives.BP {
return primitives.BP(12000)
return primitives.BP(BeaconConfig().SlotDurationMillis())
}

View File

@@ -66,6 +66,7 @@ type BeaconChainConfig struct {
GenesisDelay uint64 `yaml:"GENESIS_DELAY" spec:"true"` // GenesisDelay is the minimum number of seconds to delay starting the Ethereum Beacon Chain genesis. Must be at least 1 second.
MinAttestationInclusionDelay primitives.Slot `yaml:"MIN_ATTESTATION_INCLUSION_DELAY" spec:"true"` // MinAttestationInclusionDelay defines how many slots validator has to wait to include attestation for beacon block.
SecondsPerSlot uint64 `yaml:"SECONDS_PER_SLOT" spec:"true"` // SecondsPerSlot is how many seconds are in a single slot.
SlotDurationMilliseconds uint64 `yaml:"SLOT_DURATION_MS" spec:"true"` // SlotDurationMilliseconds is the slot time expressed in milliseconds.
SlotsPerEpoch primitives.Slot `yaml:"SLOTS_PER_EPOCH" spec:"true"` // SlotsPerEpoch is the number of slots in an epoch.
SqrRootSlotsPerEpoch primitives.Slot // SqrRootSlotsPerEpoch is a hard coded value where we take the square root of `SlotsPerEpoch` and round down.
MinSeedLookahead primitives.Epoch `yaml:"MIN_SEED_LOOKAHEAD" spec:"true"` // MinSeedLookahead is the duration of randao look ahead seed.
@@ -84,6 +85,11 @@ type BeaconChainConfig struct {
ReorgParentWeightThreshold uint64 `yaml:"REORG_PARENT_WEIGHT_THRESHOLD" spec:"true"` // ReorgParentWeightThreshold defines a value that is a % of the committee weight to consider a parent block strong and subject its child to being orphaned.
ReorgMaxEpochsSinceFinalization primitives.Epoch `yaml:"REORG_MAX_EPOCHS_SINCE_FINALIZATION" spec:"true"` // This defines a limit to consider safe to orphan a block if the network is finalizing
IntervalsPerSlot uint64 `yaml:"INTERVALS_PER_SLOT"` // IntervalsPerSlot defines the number of fork choice intervals in a slot defined in the fork choice spec.
ProposerReorgCutoffBPS primitives.BP `yaml:"PROPOSER_REORG_CUTOFF_BPS" spec:"true"` // ProposerReorgCutoffBPS defines the proposer reorg deadline in basis points of the slot.
AttestationDueBPS primitives.BP `yaml:"ATTESTATION_DUE_BPS" spec:"true"` // AttestationDueBPS defines the attestation due time in basis points of the slot.
AggregrateDueBPS primitives.BP `yaml:"AGGREGRATE_DUE_BPS" spec:"true"` // AggregrateDueBPS defines the aggregate due time in basis points of the slot.
SyncMessageDueBPS primitives.BP `yaml:"SYNC_MESSAGE_DUE_BPS" spec:"true"` // SyncMessageDueBPS defines the sync message due time in basis points of the slot.
ContributionDueBPS primitives.BP `yaml:"CONTRIBUTION_DUE_BPS" spec:"true"` // ContributionDueBPS defines the contribution due time in basis points of the slot.
// Ethereum PoW parameters.
DepositChainID uint64 `yaml:"DEPOSIT_CHAIN_ID" spec:"true"` // DepositChainID of the eth1 network. This used for replay protection.
@@ -221,7 +227,6 @@ type BeaconChainConfig struct {
// Light client
MinSyncCommitteeParticipants uint64 `yaml:"MIN_SYNC_COMMITTEE_PARTICIPANTS" spec:"true"` // MinSyncCommitteeParticipants defines the minimum amount of sync committee participants for which the light client acknowledges the signature.
MaxRequestLightClientUpdates uint64 `yaml:"MAX_REQUEST_LIGHT_CLIENT_UPDATES" spec:"true"` // MaxRequestLightClientUpdates defines the maximum amount of light client updates that can be requested in a single request.
SyncMessageDueBPS uint64 `yaml:"SYNC_MESSAGE_DUE_BPS" spec:"true"` // SyncMessageDueBPS defines the due time for a sync message.
// Bellatrix
TerminalBlockHash common.Hash `yaml:"TERMINAL_BLOCK_HASH" spec:"true"` // TerminalBlockHash of beacon chain.
@@ -741,10 +746,29 @@ func SlotsForEpochs(count primitives.Epoch, b *BeaconChainConfig) primitives.Slo
// SlotsDuration returns the time duration of the given number of slots.
func SlotsDuration(count primitives.Slot, b *BeaconChainConfig) time.Duration {
return time.Duration(count) * SecondsPerSlot(b)
return time.Duration(count) * b.SlotDuration()
}
// SecondsPerSlot returns the time duration of a single slot.
func SecondsPerSlot(b *BeaconChainConfig) time.Duration {
return time.Duration(b.SecondsPerSlot) * time.Second
return b.SlotDuration()
}
// SlotDuration returns the configured slot duration as a time.Duration.
func (b *BeaconChainConfig) SlotDuration() time.Duration {
return time.Duration(b.SlotDurationMillis()) * time.Millisecond
}
// SlotDurationMillis returns the configured slot duration in milliseconds.
func (b *BeaconChainConfig) SlotDurationMillis() uint64 {
if b.SlotDurationMilliseconds > 0 {
return b.SlotDurationMilliseconds
}
return b.SecondsPerSlot * 1000
}
// SlotComponentDuration returns the duration representing the given portion (in basis points) of a slot.
func (b *BeaconChainConfig) SlotComponentDuration(bp primitives.BP) time.Duration {
ms := uint64(bp) * b.SlotDurationMillis() / uint64(BasisPoints)
return time.Duration(ms) * time.Millisecond
}

View File

@@ -6,6 +6,7 @@ import (
"math"
"sync"
"testing"
"time"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
@@ -202,6 +203,127 @@ func fillGVR(value byte) [32]byte {
return gvr
}
func TestBeaconChainConfigSlotDuration(t *testing.T) {
t.Parallel()
tests := []struct {
name string
cfg params.BeaconChainConfig
want time.Duration
}{
{
name: "explicit duration",
cfg: params.BeaconChainConfig{SlotDurationMilliseconds: 12_000},
want: 12 * time.Second,
},
{
name: "fallback to seconds per slot",
cfg: params.BeaconChainConfig{SecondsPerSlot: 8},
want: 8 * time.Second,
},
{
name: "milliseconds override seconds per slot",
cfg: params.BeaconChainConfig{
SlotDurationMilliseconds: 7_000,
SecondsPerSlot: 4,
},
want: 7 * time.Second,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, tt.cfg.SlotDuration())
})
}
}
func TestBeaconChainConfigSlotDurationMillis(t *testing.T) {
t.Parallel()
tests := []struct {
name string
cfg params.BeaconChainConfig
want uint64
}{
{
name: "uses slot duration milliseconds when set",
cfg: params.BeaconChainConfig{SlotDurationMilliseconds: 4_800},
want: 4_800,
},
{
name: "derives from seconds per slot when unset",
cfg: params.BeaconChainConfig{SecondsPerSlot: 6},
want: 6_000,
},
{
name: "returns zero when no duration configured",
cfg: params.BeaconChainConfig{},
want: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, tt.cfg.SlotDurationMillis())
})
}
}
func TestBeaconChainConfigSlotComponentDuration(t *testing.T) {
t.Parallel()
tests := []struct {
name string
cfg params.BeaconChainConfig
bp primitives.BP
want time.Duration
}{
{
name: "zero basis points produces zero duration",
cfg: params.BeaconChainConfig{SlotDurationMilliseconds: 12_000},
bp: 0,
want: 0,
},
{
name: "full slot basis points matches slot duration",
cfg: params.BeaconChainConfig{SlotDurationMilliseconds: 12_000},
bp: params.BasisPoints,
want: 12 * time.Second,
},
{
name: "quarter slot with explicit milliseconds",
cfg: params.BeaconChainConfig{SlotDurationMilliseconds: 12_000},
bp: params.BasisPoints / 4,
want: 3 * time.Second,
},
{
name: "fractional slot rounds down",
cfg: params.BeaconChainConfig{SlotDurationMilliseconds: 1_001},
bp: params.BasisPoints / 3,
want: 333 * time.Millisecond,
},
{
name: "uses seconds per slot fallback",
cfg: params.BeaconChainConfig{SecondsPerSlot: 9},
bp: params.BasisPoints / 2,
want: 4500 * time.Millisecond,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, tt.cfg.SlotComponentDuration(tt.bp))
})
}
}
func TestEntryWithForkDigest(t *testing.T) {
var zero [32]byte
one := fillGVR(byte(1))

View File

@@ -102,6 +102,7 @@ func compareConfigs(t *testing.T, expected, actual *BeaconChainConfig) {
require.DeepEqual(t, expected.GenesisDelay, actual.GenesisDelay)
require.DeepEqual(t, expected.MinAttestationInclusionDelay, actual.MinAttestationInclusionDelay)
require.DeepEqual(t, expected.SecondsPerSlot, actual.SecondsPerSlot)
require.DeepEqual(t, expected.SlotDurationMilliseconds, actual.SlotDurationMilliseconds)
require.DeepEqual(t, expected.SlotsPerEpoch, actual.SlotsPerEpoch)
require.DeepEqual(t, expected.SqrRootSlotsPerEpoch, actual.SqrRootSlotsPerEpoch)
require.DeepEqual(t, expected.MinSeedLookahead, actual.MinSeedLookahead)

View File

@@ -187,6 +187,7 @@ func ConfigToYaml(cfg *BeaconChainConfig) []byte {
fmt.Sprintf("GENESIS_FORK_VERSION: %#x", cfg.GenesisForkVersion),
fmt.Sprintf("CHURN_LIMIT_QUOTIENT: %d", cfg.ChurnLimitQuotient),
fmt.Sprintf("SECONDS_PER_SLOT: %d", cfg.SecondsPerSlot),
fmt.Sprintf("SLOT_DURATION_MS: %d", cfg.SlotDurationMilliseconds),
fmt.Sprintf("SLOTS_PER_EPOCH: %d", cfg.SlotsPerEpoch),
fmt.Sprintf("SECONDS_PER_ETH1_BLOCK: %d", cfg.SecondsPerETH1Block),
fmt.Sprintf("ETH1_FOLLOW_DISTANCE: %d", cfg.Eth1FollowDistance),
@@ -241,6 +242,11 @@ func ConfigToYaml(cfg *BeaconChainConfig) []byte {
fmt.Sprintf("MIN_EPOCHS_FOR_BLOCK_REQUESTS: %d", int(cfg.MinEpochsForBlockRequests)),
fmt.Sprintf("MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: %d", cfg.MinPerEpochChurnLimitElectra),
fmt.Sprintf("MAX_BLOBS_PER_BLOCK: %d", cfg.DeprecatedMaxBlobsPerBlock),
fmt.Sprintf("PROPOSER_REORG_CUTOFF_BPS: %d", cfg.ProposerReorgCutoffBPS),
fmt.Sprintf("ATTESTATION_DUE_BPS: %d", cfg.AttestationDueBPS),
fmt.Sprintf("AGGREGRATE_DUE_BPS: %d", cfg.AggregrateDueBPS),
fmt.Sprintf("SYNC_MESSAGE_DUE_BPS: %d", cfg.SyncMessageDueBPS),
fmt.Sprintf("CONTRIBUTION_DUE_BPS: %d", cfg.ContributionDueBPS),
}
if len(cfg.BlobSchedule) > 0 {

View File

@@ -27,11 +27,9 @@ var placeholderFields = []string{
"AGGREGATE_DUE_BPS",
"AGGREGATE_DUE_BPS_GLOAS",
"ATTESTATION_DEADLINE",
"ATTESTATION_DUE_BPS",
"ATTESTATION_DUE_BPS_GLOAS",
"BLOB_SIDECAR_SUBNET_COUNT_FULU",
"CELLS_PER_EXT_BLOB",
"CONTRIBUTION_DUE_BPS",
"CONTRIBUTION_DUE_BPS_GLOAS",
"EIP6110_FORK_EPOCH",
"EIP6110_FORK_VERSION",
@@ -60,10 +58,7 @@ var placeholderFields = []string{
"PAYLOAD_ATTESTATION_DUE_BPS",
"PROPOSER_INCLUSION_LIST_CUTOFF",
"PROPOSER_INCLUSION_LIST_CUTOFF_BPS",
"PROPOSER_REORG_CUTOFF_BPS",
"PROPOSER_SCORE_BOOST_EIP7732",
"PROPOSER_SELECTION_GAP",
"SLOT_DURATION_MS",
"SYNC_MESSAGE_DUE_BPS_GLOAS",
"TARGET_NUMBER_OF_PEERS",
"UPDATE_TIMEOUT",
@@ -101,6 +96,10 @@ func assertEqualConfigs(t *testing.T, name string, fields []string, expected, ac
assert.Equal(t, expected.HysteresisQuotient, actual.HysteresisQuotient, "%s: HysteresisQuotient", name)
assert.Equal(t, expected.HysteresisDownwardMultiplier, actual.HysteresisDownwardMultiplier, "%s: HysteresisDownwardMultiplier", name)
assert.Equal(t, expected.HysteresisUpwardMultiplier, actual.HysteresisUpwardMultiplier, "%s: HysteresisUpwardMultiplier", name)
assert.Equal(t, expected.AttestationDueBPS, actual.AttestationDueBPS, "%s: AttestationDueBPS", name)
assert.Equal(t, expected.AggregrateDueBPS, actual.AggregrateDueBPS, "%s: AggregrateDueBPS", name)
assert.Equal(t, expected.ContributionDueBPS, actual.ContributionDueBPS, "%s: ContributionDueBPS", name)
assert.Equal(t, expected.ProposerReorgCutoffBPS, actual.ProposerReorgCutoffBPS, "%s: ProposerReorgCutoffBPS", name)
assert.Equal(t, expected.SyncMessageDueBPS, actual.SyncMessageDueBPS, "%s: SyncMessageDueBPS", name)
// Validator params.
@@ -129,6 +128,7 @@ func assertEqualConfigs(t *testing.T, name string, fields []string, expected, ac
// Time parameters.
assert.Equal(t, expected.GenesisDelay, actual.GenesisDelay, "%s: GenesisDelay", name)
assert.Equal(t, expected.SecondsPerSlot, actual.SecondsPerSlot, "%s: SecondsPerSlot", name)
assert.Equal(t, expected.SlotDurationMilliseconds, actual.SlotDurationMilliseconds, "%s: SlotDurationMilliseconds", name)
assert.Equal(t, expected.MinAttestationInclusionDelay, actual.MinAttestationInclusionDelay, "%s: MinAttestationInclusionDelay", name)
assert.Equal(t, expected.SlotsPerEpoch, actual.SlotsPerEpoch, "%s: SlotsPerEpoch", name)
assert.Equal(t, expected.MinSeedLookahead, actual.MinSeedLookahead, "%s: MinSeedLookahead", name)

View File

@@ -5,6 +5,7 @@ import (
"time"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
)
@@ -98,6 +99,7 @@ var mainnetBeaconConfig = &BeaconChainConfig{
// Time parameter constants.
MinAttestationInclusionDelay: 1,
SecondsPerSlot: 12,
SlotDurationMilliseconds: 12000,
SlotsPerEpoch: 32,
SqrRootSlotsPerEpoch: 5,
MinSeedLookahead: 1,
@@ -116,6 +118,13 @@ var mainnetBeaconConfig = &BeaconChainConfig{
ReorgMaxEpochsSinceFinalization: 2,
IntervalsPerSlot: 3,
// Time-based protocol parameters.
ProposerReorgCutoffBPS: primitives.BP(1667),
AttestationDueBPS: primitives.BP(3333),
AggregrateDueBPS: primitives.BP(6667),
SyncMessageDueBPS: primitives.BP(3333),
ContributionDueBPS: primitives.BP(6667),
// Ethereum PoW parameters.
DepositChainID: 1, // Chain ID of eth1 mainnet.
DepositNetworkID: 1, // Network ID of eth1 mainnet.
@@ -257,7 +266,6 @@ var mainnetBeaconConfig = &BeaconChainConfig{
// Light client
MinSyncCommitteeParticipants: 1,
MaxRequestLightClientUpdates: 128,
SyncMessageDueBPS: 3333,
// Bellatrix
TerminalBlockHashActivationEpoch: 18446744073709551615,

View File

@@ -34,6 +34,7 @@ func MinimalSpecConfig() *BeaconChainConfig {
// Time parameters
minimalConfig.SecondsPerSlot = 6
minimalConfig.SlotDurationMilliseconds = 6000
minimalConfig.MinAttestationInclusionDelay = 1
minimalConfig.SlotsPerEpoch = 8
minimalConfig.SqrRootSlotsPerEpoch = 2

View File

@@ -56,6 +56,8 @@ FULU_FORK_EPOCH: 18446744073709551615
# ---------------------------------------------------------------
# [customized] Faster for testing purposes
SECONDS_PER_SLOT: 10 # Override for e2e tests
# 10000 milliseconds, 10 seconds
SLOT_DURATION_MS: 10000
# 14 (estimate from Eth1 mainnet)
SECONDS_PER_ETH1_BLOCK: 2 # Override for e2e tests
# [customized] faster time for withdrawals
@@ -133,4 +135,4 @@ BLOB_SCHEDULE:
MAX_BLOBS_PER_BLOCK: 6
# Electra
- EPOCH: 14
MAX_BLOBS_PER_BLOCK: 9
MAX_BLOBS_PER_BLOCK: 9

View File

@@ -58,6 +58,7 @@ func compareConfigs(t *testing.T, expected, actual *params.BeaconChainConfig) {
require.DeepEqual(t, expected.GenesisDelay, actual.GenesisDelay)
require.DeepEqual(t, expected.MinAttestationInclusionDelay, actual.MinAttestationInclusionDelay)
require.DeepEqual(t, expected.SecondsPerSlot, actual.SecondsPerSlot)
require.DeepEqual(t, expected.SlotDurationMilliseconds, actual.SlotDurationMilliseconds)
require.DeepEqual(t, expected.SlotsPerEpoch, actual.SlotsPerEpoch)
require.DeepEqual(t, expected.SqrRootSlotsPerEpoch, actual.SqrRootSlotsPerEpoch)
require.DeepEqual(t, expected.MinSeedLookahead, actual.MinSeedLookahead)

View File

@@ -27,6 +27,7 @@ func E2ETestConfig() *BeaconChainConfig {
// Time parameters.
e2eConfig.SecondsPerSlot = 10
e2eConfig.SlotDurationMilliseconds = 10000
e2eConfig.SlotsPerEpoch = 6
e2eConfig.SqrRootSlotsPerEpoch = 2
e2eConfig.SecondsPerETH1Block = 2
@@ -81,6 +82,7 @@ func E2EMainnetTestConfig() *BeaconChainConfig {
// Time parameters.
e2eConfig.SecondsPerSlot = 6
e2eConfig.SlotDurationMilliseconds = 6000
e2eConfig.SqrRootSlotsPerEpoch = 5
e2eConfig.SecondsPerETH1Block = 2
e2eConfig.ShardCommitteePeriod = 4

View File

@@ -40,14 +40,17 @@ func EpochsSinceGenesis(genesis time.Time) primitives.Epoch {
// in milliseconds, useful for dividing values such as 1 second into
// millisecond-based durations.
func DivideSlotBy(timesPerSlot int64) time.Duration {
return time.Duration(int64(params.BeaconConfig().SecondsPerSlot*1000)/timesPerSlot) * time.Millisecond
if timesPerSlot == 0 {
return 0
}
return params.BeaconConfig().SlotDuration() / time.Duration(timesPerSlot)
}
// MultiplySlotBy multiplies the SECONDS_PER_SLOT configuration
// parameter by a specified number. It returns a value of time.Duration
// in millisecond-based durations.
func MultiplySlotBy(times int64) time.Duration {
return time.Duration(int64(params.BeaconConfig().SecondsPerSlot)*times) * time.Second
return params.BeaconConfig().SlotDuration() * time.Duration(times)
}
// AbsoluteValueSlotDifference between two slots.
@@ -175,12 +178,11 @@ func VerifyTime(genesis time.Time, slot primitives.Slot, timeTolerance time.Dura
// StartTime takes the given slot and genesis time to determine the start time of the slot.
// This method returns an error if the product of the slot duration * slot overflows int64.
func StartTime(genesis time.Time, slot primitives.Slot) (time.Time, error) {
_, err := slot.SafeMul(params.BeaconConfig().SecondsPerSlot)
ms, err := slot.SafeMul(params.BeaconConfig().SlotDurationMillis())
if err != nil {
return time.Unix(0, 0), fmt.Errorf("slot (%d) is in the far distant future: %w", slot, err)
}
sd := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Duration(slot)
return genesis.Add(sd), nil
return genesis.Add(time.Duration(ms) * time.Millisecond), nil
}
// CurrentSlot returns the current slot as determined by the local clock and
@@ -194,7 +196,7 @@ func At(genesis, tm time.Time) primitives.Slot {
if tm.Before(genesis) {
return 0
}
return primitives.Slot(tm.Sub(genesis) / time.Second / time.Duration(params.BeaconConfig().SecondsPerSlot))
return primitives.Slot(tm.Sub(genesis) / params.BeaconConfig().SlotDuration())
}
// Duration computes the span of time between two instants, represented as Slots.
@@ -202,7 +204,7 @@ func Duration(start, end time.Time) primitives.Slot {
if end.Before(start) {
return 0
}
return primitives.Slot(uint64(end.Unix()-start.Unix()) / params.BeaconConfig().SecondsPerSlot)
return primitives.Slot((end.Sub(start)) / params.BeaconConfig().SlotDuration())
}
// ValidateClock validates a provided slot against the local
@@ -231,7 +233,7 @@ func RoundUpToNearestEpoch(slot primitives.Slot) primitives.Slot {
// depending on the provided genesis and current slot.
func VotingPeriodStartTime(genesis uint64, slot primitives.Slot) uint64 {
slots := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().EpochsPerEth1VotingPeriod))
startTime := uint64((slot - slot.ModSlot(slots)).Mul(params.BeaconConfig().SecondsPerSlot))
startTime := uint64((slot - slot.ModSlot(slots)).Mul(params.BeaconConfig().SlotDurationMillis())) / 1000
return genesis + startTime
}
@@ -267,7 +269,7 @@ func SyncCommitteePeriodStartEpoch(e primitives.Epoch) (primitives.Epoch, error)
// given slot start time. This method returns an error if the timestamp happens
// before the given slot start time.
func SinceSlotStart(s primitives.Slot, genesis time.Time, timestamp time.Time) (time.Duration, error) {
limit := genesis.Add(time.Duration(uint64(s)*params.BeaconConfig().SecondsPerSlot) * time.Second)
limit := genesis.Add(time.Duration(uint64(s)) * params.BeaconConfig().SlotDuration())
if timestamp.Before(limit) {
return 0, fmt.Errorf("could not compute seconds since slot %d start: invalid timestamp, got %s < want %s", s, timestamp, limit)
}
@@ -277,8 +279,8 @@ func SinceSlotStart(s primitives.Slot, genesis time.Time, timestamp time.Time) (
// WithinVotingWindow returns whether the current time is within the voting window
// (eg. 4 seconds on mainnet) of the current slot.
func WithinVotingWindow(genesis time.Time, slot primitives.Slot) bool {
votingWindow := params.BeaconConfig().SecondsPerSlot / params.BeaconConfig().IntervalsPerSlot
return time.Since(UnsafeStartTime(genesis, slot)) < time.Duration(votingWindow)*time.Second
votingWindow := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().AttestationDueBPS)
return time.Since(UnsafeStartTime(genesis, slot)) < votingWindow
}
// MaxSafeEpoch gives the largest epoch value that can be safely converted to a slot.
@@ -307,9 +309,3 @@ func SecondsUntilNextEpochStart(genesis time.Time) (uint64, error) {
}).Debugf("%d seconds until next epoch", waitTime)
return waitTime, nil
}
// ComponentDuration calculates the duration of a slot component in milliseconds.
func ComponentDuration(component primitives.BP) time.Duration {
ms := (component * params.SlotBP()) / params.BasisPoints
return time.Duration(ms) * time.Millisecond
}

View File

@@ -17,6 +17,7 @@ go_library(
"sync_committee.go",
"validator.go",
"wait_for_activation.go",
"wait_helpers.go",
],
importpath = "github.com/OffchainLabs/prysm/v7/validator/client",
visibility = [
@@ -115,6 +116,7 @@ go_test(
"sync_committee_test.go",
"validator_test.go",
"wait_for_activation_test.go",
"wait_helpers_test.go",
],
data = [
"@eip3076_spec_tests//:test_data",

View File

@@ -4,20 +4,17 @@ import (
"context"
"fmt"
"net/http"
"time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/crypto/bls"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
"github.com/OffchainLabs/prysm/v7/network/httputil"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
validatorpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1/validator-client"
"github.com/OffchainLabs/prysm/v7/runtime/version"
prysmTime "github.com/OffchainLabs/prysm/v7/time"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
@@ -210,32 +207,7 @@ func (v *validator) signSlotWithSelectionProof(ctx context.Context, pubKey [fiel
// such that any attestations from this slot have time to reach the beacon node
// before creating the aggregated attestation.
func (v *validator) waitToSlotTwoThirds(ctx context.Context, slot primitives.Slot) {
ctx, span := trace.StartSpan(ctx, "validator.waitToSlotTwoThirds")
defer span.End()
oneThird := slots.DivideSlotBy(3 /* one third of slot duration */)
twoThird := oneThird + oneThird
delay := twoThird
startTime, err := slots.StartTime(v.genesisTime, slot)
if err != nil {
log.WithError(err).WithField("slot", slot).Error("Slot overflows, unable to wait for slot two thirds!")
return
}
finalTime := startTime.Add(delay)
wait := prysmTime.Until(finalTime)
if wait <= 0 {
return
}
t := time.NewTimer(wait)
defer t.Stop()
select {
case <-ctx.Done():
tracing.AnnotateError(span, ctx.Err())
return
case <-t.C:
return
}
v.waitUntilSlotComponent(ctx, slot, params.BeaconConfig().AggregrateDueBPS)
}
// This returns the signature of validator signing over aggregate and

View File

@@ -16,7 +16,6 @@ import (
"github.com/OffchainLabs/prysm/v7/testing/assert"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/OffchainLabs/prysm/v7/validator/client/iface"
logTest "github.com/sirupsen/logrus/hooks/test"
"go.uber.org/mock/gomock"
@@ -256,9 +255,9 @@ func TestWaitForSlotTwoThird_WaitCorrectly(t *testing.T) {
defer finish()
currentTime := time.Now()
numOfSlots := primitives.Slot(4)
validator.genesisTime = currentTime.Add(-1 * time.Duration(numOfSlots.Mul(params.BeaconConfig().SecondsPerSlot)) * time.Second)
oneThird := slots.DivideSlotBy(3 /* one third of slot duration */)
timeToSleep := oneThird + oneThird
slotDuration := params.BeaconConfig().SlotDuration()
validator.genesisTime = currentTime.Add(-slotDuration * time.Duration(numOfSlots))
timeToSleep := params.BeaconConfig().SlotComponentDuration(params.BeaconConfig().AggregrateDueBPS)
twoThirdTime := currentTime.Add(timeToSleep)
validator.waitToSlotTwoThirds(t.Context(), numOfSlots)
@@ -275,7 +274,8 @@ func TestWaitForSlotTwoThird_DoneContext_ReturnsImmediately(t *testing.T) {
defer finish()
currentTime := time.Now()
numOfSlots := primitives.Slot(4)
validator.genesisTime = currentTime.Add(-1 * time.Duration(numOfSlots.Mul(params.BeaconConfig().SecondsPerSlot)) * time.Second)
slotDuration := params.BeaconConfig().SlotDuration()
validator.genesisTime = currentTime.Add(-slotDuration * time.Duration(numOfSlots))
expectedTime := time.Now()
ctx, cancel := context.WithCancel(t.Context())

View File

@@ -280,13 +280,11 @@ func (v *validator) waitOneThirdOrValidBlock(ctx context.Context, slot primitive
return
}
delay := slots.DivideSlotBy(3 /* a third of the slot duration */)
startTime, err := slots.StartTime(v.genesisTime, slot)
finalTime, err := v.slotComponentDeadline(slot, params.BeaconConfig().AttestationDueBPS)
if err != nil {
log.WithError(err).WithField("slot", slot).Error("Slot overflows, unable to wait for slot two thirds!")
log.WithError(err).WithField("slot", slot).Error("Slot overflows, unable to wait for attestation deadline")
return
}
finalTime := startTime.Add(delay)
wait := prysmTime.Until(finalTime)
if wait <= 0 {
return

View File

@@ -373,7 +373,7 @@ func TestRunnerPushesProposerSettings_ValidContext(t *testing.T) {
logrus.SetOutput(tlogger{t})
cfg := params.BeaconConfig()
cfg.SecondsPerSlot = 1
cfg.SlotDurationMilliseconds = 1000
params.SetActiveTestCleanup(t, cfg)
timedCtx, cancel := context.WithTimeout(t.Context(), 1*time.Minute)

View File

@@ -127,7 +127,7 @@ func (v *validator) SubmitSignedContributionAndProof(ctx context.Context, slot p
return
}
v.waitToSlotTwoThirds(ctx, slot)
v.waitUntilSlotComponent(ctx, slot, params.BeaconConfig().ContributionDueBPS)
coveredSubnets := make(map[uint64]bool)
for i, comIdx := range indexRes.Indices {

View File

@@ -54,7 +54,7 @@ func TestWaitForActivation_RefetchKeys(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.MainnetConfig()
cfg.ConfigName = "test"
cfg.SecondsPerSlot = 1
cfg.SlotDurationMilliseconds = 1000
params.OverrideBeaconConfig(cfg)
hook := logTest.NewGlobal()
ctrl := gomock.NewController(t)
@@ -247,7 +247,7 @@ func TestWaitForActivation_AttemptsReconnectionOnFailure(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.MainnetConfig()
cfg.ConfigName = "test"
cfg.SecondsPerSlot = 1
cfg.SlotDurationMilliseconds = 1000
params.OverrideBeaconConfig(cfg)
ctrl := gomock.NewController(t)
defer ctrl.Finish()

View File

@@ -0,0 +1,65 @@
package client
import (
"context"
"time"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
prysmTime "github.com/OffchainLabs/prysm/v7/time"
"github.com/OffchainLabs/prysm/v7/time/slots"
)
// slotComponentDeadline returns the absolute time corresponding to the provided slot component.
func (v *validator) slotComponentDeadline(slot primitives.Slot, component primitives.BP) (time.Time, error) {
startTime, err := slots.StartTime(v.genesisTime, slot)
if err != nil {
return time.Time{}, err
}
delay := params.BeaconConfig().SlotComponentDuration(component)
return startTime.Add(delay), nil
}
func (v *validator) waitUntilSlotComponent(ctx context.Context, slot primitives.Slot, component primitives.BP) {
ctx, span := trace.StartSpan(ctx, v.slotComponentSpanName(component))
defer span.End()
finalTime, err := v.slotComponentDeadline(slot, component)
if err != nil {
log.WithError(err).WithField("slot", slot).Error("Slot overflows, unable to wait for slot component deadline")
return
}
wait := prysmTime.Until(finalTime)
if wait <= 0 {
return
}
t := time.NewTimer(wait)
defer t.Stop()
select {
case <-ctx.Done():
tracing.AnnotateError(span, ctx.Err())
return
case <-t.C:
return
}
}
func (v *validator) slotComponentSpanName(component primitives.BP) string {
cfg := params.BeaconConfig()
switch component {
case cfg.AttestationDueBPS:
return "validator.waitAttestationWindow"
case cfg.AggregrateDueBPS:
return "validator.waitAggregateWindow"
case cfg.SyncMessageDueBPS:
return "validator.waitSyncMessageWindow"
case cfg.ContributionDueBPS:
return "validator.waitContributionWindow"
case cfg.ProposerReorgCutoffBPS:
return "validator.waitProposerReorgWindow"
default:
return "validator.waitSlotComponent"
}
}

View File

@@ -0,0 +1,87 @@
package client
import (
"context"
"testing"
"time"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/testing/assert"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/time/slots"
)
func TestSlotComponentDeadline(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
v := &validator{genesisTime: time.Unix(1700000000, 0)}
slot := primitives.Slot(5)
component := cfg.AttestationDueBPS
got, err := v.slotComponentDeadline(slot, component)
require.NoError(t, err)
startTime, err := slots.StartTime(v.genesisTime, slot)
require.NoError(t, err)
expected := startTime.Add(cfg.SlotComponentDuration(component))
require.Equal(t, expected, got)
}
func TestSlotComponentSpanName(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
v := &validator{}
tests := []struct {
name string
component primitives.BP
expected string
}{
{
name: "attestation",
component: cfg.AttestationDueBPS,
expected: "validator.waitAttestationWindow",
},
{
name: "aggregate",
component: cfg.AggregrateDueBPS,
expected: "validator.waitAggregateWindow",
},
{
name: "default",
component: cfg.AttestationDueBPS + 7,
expected: "validator.waitSlotComponent",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, v.slotComponentSpanName(tt.component))
})
}
}
func TestWaitUntilSlotComponent_ContextCancelReturnsImmediately(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.SlotDurationMilliseconds = 10000
params.OverrideBeaconConfig(cfg)
v := &validator{genesisTime: time.Now()}
ctx, cancel := context.WithCancel(context.Background())
cancel()
done := make(chan struct{})
go func() {
v.waitUntilSlotComponent(ctx, 1, cfg.AttestationDueBPS)
close(done)
}()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("waitUntilSlotComponent did not return after context cancellation")
}
}