diff --git a/beacon-chain/core/peerdas/reconstruction_helpers_test.go b/beacon-chain/core/peerdas/reconstruction_helpers_test.go index 1a3bc93f69..ed8b108d49 100644 --- a/beacon-chain/core/peerdas/reconstruction_helpers_test.go +++ b/beacon-chain/core/peerdas/reconstruction_helpers_test.go @@ -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. diff --git a/beacon-chain/db/kv/genesis_test.go b/beacon-chain/db/kv/genesis_test.go index fb340174f3..06356cc3f0 100644 --- a/beacon-chain/db/kv/genesis_test.go +++ b/beacon-chain/db/kv/genesis_test.go @@ -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() { diff --git a/beacon-chain/forkchoice/doubly-linked-tree/node.go b/beacon-chain/forkchoice/doubly-linked-tree/node.go index 8ac6884d1b..3b36157f18 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/node.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/node.go @@ -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 } diff --git a/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks_test.go b/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks_test.go index a828e10a3b..34c10cf9d6 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks_test.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/reorg_late_blocks_test.go @@ -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()) }) diff --git a/beacon-chain/forkchoice/doubly-linked-tree/store.go b/beacon-chain/forkchoice/doubly-linked-tree/store.go index c0d314df73..d645ab9f35 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/store.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/store.go @@ -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 diff --git a/beacon-chain/p2p/broadcaster.go b/beacon-chain/p2p/broadcaster.go index 6066a4e052..6e638071ee 100644 --- a/beacon-chain/p2p/broadcaster.go +++ b/beacon-chain/p2p/broadcaster.go @@ -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) diff --git a/beacon-chain/p2p/broadcaster_test.go b/beacon-chain/p2p/broadcaster_test.go index 5ea0c13bcd..81041b0b3a 100644 --- a/beacon-chain/p2p/broadcaster_test.go +++ b/beacon-chain/p2p/broadcaster_test.go @@ -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)) } diff --git a/beacon-chain/p2p/service_test.go b/beacon-chain/p2p/service_test.go index 2596681ca7..f7b180beec 100644 --- a/beacon-chain/p2p/service_test.go +++ b/beacon-chain/p2p/service_test.go @@ -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) diff --git a/beacon-chain/rpc/eth/config/handlers_test.go b/beacon-chain/rpc/eth/config/handlers_test.go index 8f174d80d6..04d11cf9dd 100644 --- a/beacon-chain/rpc/eth/config/handlers_test.go +++ b/beacon-chain/rpc/eth/config/handlers_test.go @@ -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": diff --git a/beacon-chain/sync/rpc_goodbye_test.go b/beacon-chain/sync/rpc_goodbye_test.go index 17b2f8210c..6977e48851 100644 --- a/beacon-chain/sync/rpc_goodbye_test.go +++ b/beacon-chain/sync/rpc_goodbye_test.go @@ -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) diff --git a/beacon-chain/sync/subscriber_test.go b/beacon-chain/sync/subscriber_test.go index 7fc3bf0883..3875dec4ef 100644 --- a/beacon-chain/sync/subscriber_test.go +++ b/beacon-chain/sync/subscriber_test.go @@ -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) diff --git a/beacon-chain/sync/validate_light_client.go b/beacon-chain/sync/validate_light_client.go index d8b7ad2117..80b235c695 100644 --- a/beacon-chain/sync/validate_light_client.go +++ b/beacon-chain/sync/validate_light_client.go @@ -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") diff --git a/beacon-chain/sync/validate_light_client_test.go b/beacon-chain/sync/validate_light_client_test.go index 080ed30b4c..c979ddff47 100644 --- a/beacon-chain/sync/validate_light_client_test.go +++ b/beacon-chain/sync/validate_light_client_test.go @@ -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, diff --git a/changelog/ttsao_use-timing-configs.md b/changelog/ttsao_use-timing-configs.md new file mode 100644 index 0000000000..eebd16db99 --- /dev/null +++ b/changelog/ttsao_use-timing-configs.md @@ -0,0 +1,3 @@ +### Changed + +- Use explicit slot component timing configs \ No newline at end of file diff --git a/config/params/basis_points.go b/config/params/basis_points.go index f829bd21ed..9ff3ba0327 100644 --- a/config/params/basis_points.go +++ b/config/params/basis_points.go @@ -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()) } diff --git a/config/params/config.go b/config/params/config.go index a561164124..17d7a0b4a3 100644 --- a/config/params/config.go +++ b/config/params/config.go @@ -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 } diff --git a/config/params/config_test.go b/config/params/config_test.go index 0968fef2b6..c7102955b1 100644 --- a/config/params/config_test.go +++ b/config/params/config_test.go @@ -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)) diff --git a/config/params/configset_test.go b/config/params/configset_test.go index 773ef9ef73..0a724f1e8d 100644 --- a/config/params/configset_test.go +++ b/config/params/configset_test.go @@ -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) diff --git a/config/params/loader.go b/config/params/loader.go index 25fc18f237..767e17d17e 100644 --- a/config/params/loader.go +++ b/config/params/loader.go @@ -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 { diff --git a/config/params/loader_test.go b/config/params/loader_test.go index 2158702e4b..fd75ed892d 100644 --- a/config/params/loader_test.go +++ b/config/params/loader_test.go @@ -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) diff --git a/config/params/mainnet_config.go b/config/params/mainnet_config.go index 98ce3a0832..af6c47dbe1 100644 --- a/config/params/mainnet_config.go +++ b/config/params/mainnet_config.go @@ -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, diff --git a/config/params/minimal_config.go b/config/params/minimal_config.go index d030cffb29..36851d1258 100644 --- a/config/params/minimal_config.go +++ b/config/params/minimal_config.go @@ -34,6 +34,7 @@ func MinimalSpecConfig() *BeaconChainConfig { // Time parameters minimalConfig.SecondsPerSlot = 6 + minimalConfig.SlotDurationMilliseconds = 6000 minimalConfig.MinAttestationInclusionDelay = 1 minimalConfig.SlotsPerEpoch = 8 minimalConfig.SqrRootSlotsPerEpoch = 2 diff --git a/config/params/testdata/e2e_config.yaml b/config/params/testdata/e2e_config.yaml index c2c57c07b5..c8d0df45fa 100644 --- a/config/params/testdata/e2e_config.yaml +++ b/config/params/testdata/e2e_config.yaml @@ -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 \ No newline at end of file + MAX_BLOBS_PER_BLOCK: 9 diff --git a/config/params/testnet_config_test.go b/config/params/testnet_config_test.go index 70ab4995d6..bdd0285016 100644 --- a/config/params/testnet_config_test.go +++ b/config/params/testnet_config_test.go @@ -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) diff --git a/config/params/testnet_e2e_config.go b/config/params/testnet_e2e_config.go index addc427ca2..ad5ce74a76 100644 --- a/config/params/testnet_e2e_config.go +++ b/config/params/testnet_e2e_config.go @@ -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 diff --git a/time/slots/slottime.go b/time/slots/slottime.go index cfa4bd0adf..6d6bf2f814 100644 --- a/time/slots/slottime.go +++ b/time/slots/slottime.go @@ -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 -} diff --git a/validator/client/BUILD.bazel b/validator/client/BUILD.bazel index 63603c2eec..ea938b1ea6 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -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", diff --git a/validator/client/aggregate.go b/validator/client/aggregate.go index 24cc34e3cd..a261a86209 100644 --- a/validator/client/aggregate.go +++ b/validator/client/aggregate.go @@ -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 diff --git a/validator/client/aggregate_test.go b/validator/client/aggregate_test.go index a59d72bcf4..f50b2fefa4 100644 --- a/validator/client/aggregate_test.go +++ b/validator/client/aggregate_test.go @@ -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()) diff --git a/validator/client/attest.go b/validator/client/attest.go index 8d8aed684e..df35f8d712 100644 --- a/validator/client/attest.go +++ b/validator/client/attest.go @@ -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 diff --git a/validator/client/runner_test.go b/validator/client/runner_test.go index 5915a23397..76fc64afea 100644 --- a/validator/client/runner_test.go +++ b/validator/client/runner_test.go @@ -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) diff --git a/validator/client/sync_committee.go b/validator/client/sync_committee.go index b82a9fcd7e..6c1a761726 100644 --- a/validator/client/sync_committee.go +++ b/validator/client/sync_committee.go @@ -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 { diff --git a/validator/client/wait_for_activation_test.go b/validator/client/wait_for_activation_test.go index 10a894ebfb..3311102f14 100644 --- a/validator/client/wait_for_activation_test.go +++ b/validator/client/wait_for_activation_test.go @@ -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() diff --git a/validator/client/wait_helpers.go b/validator/client/wait_helpers.go new file mode 100644 index 0000000000..d0e7696533 --- /dev/null +++ b/validator/client/wait_helpers.go @@ -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" + } +} diff --git a/validator/client/wait_helpers_test.go b/validator/client/wait_helpers_test.go new file mode 100644 index 0000000000..e62ac053e4 --- /dev/null +++ b/validator/client/wait_helpers_test.go @@ -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") + } +}