package params_test import ( "bytes" "fmt" "math" "sync" "testing" "github.com/OffchainLabs/prysm/v6/config/params" "github.com/OffchainLabs/prysm/v6/consensus-types/primitives" "github.com/OffchainLabs/prysm/v6/genesis" "github.com/OffchainLabs/prysm/v6/testing/require" "github.com/ethereum/go-ethereum/common/hexutil" ) // Test cases can be executed in an arbitrary order. TestOverrideBeaconConfigTestTeardown checks // that there's no state mutation leak from the previous test, therefore we need a sentinel flag, // to make sure that previous test case has already been completed and check can be run. var testOverrideBeaconConfigExecuted bool func TestConfig_OverrideBeaconConfig(t *testing.T) { // Ensure that param modifications are safe. params.SetupTestConfigCleanup(t) cfg := params.BeaconConfig() cfg.SlotsPerEpoch = 5 params.OverrideBeaconConfig(cfg) if c := params.BeaconConfig(); c.SlotsPerEpoch != 5 { t.Errorf("Shardcount in BeaconConfig incorrect. Wanted %d, got %d", 5, c.SlotsPerEpoch) } testOverrideBeaconConfigExecuted = true } func TestConfig_OverrideBeaconConfigTestTeardown(t *testing.T) { if !testOverrideBeaconConfigExecuted { t.Skip("State leak can occur only if state mutating test has already completed") } cfg := params.BeaconConfig() if cfg.SlotsPerEpoch == 5 { t.Fatal("Parameter update has been leaked out of previous test") } } func TestConfig_DataRace(t *testing.T) { params.SetupTestConfigCleanup(t) wg := new(sync.WaitGroup) for i := 0; i < 10; i++ { wg.Add(2) go func() { defer wg.Done() cfg := params.BeaconConfig() params.OverrideBeaconConfig(cfg) }() go func() uint64 { defer wg.Done() return params.BeaconConfig().MaxDeposits }() } wg.Wait() } func TestConfig_WithinDAPeriod(t *testing.T) { cases := []struct { name string block primitives.Epoch current primitives.Epoch within bool }{ { name: "before", block: 0, current: params.BeaconConfig().MinEpochsForBlobsSidecarsRequest + 1, within: false, }, { name: "same", block: 0, current: 0, within: true, }, { name: "boundary", block: 0, current: params.BeaconConfig().MinEpochsForBlobsSidecarsRequest, within: true, }, { name: "one less", block: params.BeaconConfig().MinEpochsForBlobsSidecarsRequest - 1, current: params.BeaconConfig().MinEpochsForBlobsSidecarsRequest, within: true, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { require.Equal(t, c.within, params.WithinDAPeriod(c.block, c.current)) }) } } func TestConfigGenesisValidatorRoot(t *testing.T) { params.SetActiveTestCleanup(t, params.MainnetBeaconConfig) genesis.StoreEmbeddedDuringTest(t, params.BeaconConfig().ConfigName) g, err := genesis.State() require.NoError(t, err, "failed to load genesis state") if !bytes.Equal(g.GenesisValidatorsRoot(), params.BeaconConfig().GenesisValidatorsRoot[:]) { t.Fatal("mainnet params genesis validator root does not match the mainnet genesis state value") } require.Equal(t, params.BeaconConfig().GenesisValidatorsRoot, genesis.ValidatorsRoot()) } func TestMaxBlobsPerBlock(t *testing.T) { t.Run("Before all forks and no BlobSchedule", func(t *testing.T) { cfg := params.MainnetConfig() cfg.BlobSchedule = nil cfg.ElectraForkEpoch = 100 cfg.FuluForkEpoch = 200 require.Equal(t, cfg.MaxBlobsPerBlock(0), cfg.DeprecatedMaxBlobsPerBlock) }) t.Run("Uses latest matching BlobSchedule entry", func(t *testing.T) { cfg := params.MainnetConfig() cfg.BlobSchedule = []params.BlobScheduleEntry{ {Epoch: 5, MaxBlobsPerBlock: 7}, {Epoch: 10, MaxBlobsPerBlock: 11}, } slot := 11 * cfg.SlotsPerEpoch require.Equal(t, cfg.MaxBlobsPerBlock(slot), 11) }) t.Run("Uses earlier matching BlobSchedule entry", func(t *testing.T) { cfg := params.MainnetConfig() cfg.BlobSchedule = []params.BlobScheduleEntry{ {Epoch: 5, MaxBlobsPerBlock: 7}, {Epoch: 10, MaxBlobsPerBlock: 11}, } slot := 6 * cfg.SlotsPerEpoch require.Equal(t, cfg.MaxBlobsPerBlock(slot), 7) }) t.Run("Before first BlobSchedule entry falls back to fork logic", func(t *testing.T) { cfg := params.MainnetConfig() cfg.FuluForkEpoch = 1 cfg.BlobSchedule = []params.BlobScheduleEntry{ {Epoch: 5, MaxBlobsPerBlock: 7}, } slot := primitives.Slot(2) // Epoch 0 require.Equal(t, cfg.MaxBlobsPerBlock(slot), cfg.DeprecatedMaxBlobsPerBlock) }) t.Run("Unsorted BlobSchedule still picks latest matching entry", func(t *testing.T) { cfg := params.MainnetConfig() cfg.BlobSchedule = []params.BlobScheduleEntry{ {Epoch: 10, MaxBlobsPerBlock: 11}, {Epoch: 5, MaxBlobsPerBlock: 7}, } slot := 11 * cfg.SlotsPerEpoch require.Equal(t, cfg.MaxBlobsPerBlock(slot), 11) }) t.Run("Unsorted BlobSchedule picks earlier matching entry correctly", func(t *testing.T) { cfg := params.MainnetConfig() cfg.BlobSchedule = []params.BlobScheduleEntry{ {Epoch: 10, MaxBlobsPerBlock: 11}, {Epoch: 5, MaxBlobsPerBlock: 7}, } slot := 6 * cfg.SlotsPerEpoch require.Equal(t, cfg.MaxBlobsPerBlock(slot), 7) }) t.Run("Unsorted BlobSchedule falls back to fork logic when epoch is before all entries", func(t *testing.T) { cfg := params.MainnetConfig() cfg.ElectraForkEpoch = 2 cfg.BlobSchedule = []params.BlobScheduleEntry{ {Epoch: 10, MaxBlobsPerBlock: 11}, {Epoch: 5, MaxBlobsPerBlock: 7}, } slot := primitives.Slot(1) // Epoch 0 require.Equal(t, cfg.MaxBlobsPerBlock(slot), cfg.DeprecatedMaxBlobsPerBlock) }) } func Test_TargetBlobCount(t *testing.T) { cfg := params.MainnetConfig() cfg.ElectraForkEpoch = 10 require.Equal(t, cfg.TargetBlobsPerBlock(primitives.Slot(cfg.ElectraForkEpoch)*cfg.SlotsPerEpoch-1), 3) require.Equal(t, cfg.TargetBlobsPerBlock(primitives.Slot(cfg.ElectraForkEpoch)*cfg.SlotsPerEpoch), 6) cfg.ElectraForkEpoch = math.MaxUint64 } func fillGVR(value byte) [32]byte { var gvr [32]byte for i := 0; i < len(gvr); i++ { gvr[i] = value } return gvr } func TestEntryWithForkDigest(t *testing.T) { var zero [32]byte one := fillGVR(byte(1)) two := fillGVR(byte(2)) three := fillGVR(byte(3)) configs := map[[32]byte]*params.BeaconChainConfig{ zero: testConfigForSchedule(zero), one: testConfigForSchedule(one), two: testConfigForSchedule(two), three: testConfigForSchedule(three), } for _, cfg := range configs { cfg.InitializeForkSchedule() } cases := []struct { epoch primitives.Epoch gvr [32]byte expected string }{ {epoch: 9, expected: "0x97b2c268"}, {epoch: 10, expected: "0x97b2c268"}, {epoch: 11, expected: "0x97b2c268"}, {epoch: 99, expected: "0x97b2c268"}, {epoch: 100, expected: "0x44a571e8"}, {epoch: 101, expected: "0x44a571e8"}, {epoch: 150, expected: "0x1171afca"}, {epoch: 199, expected: "0x1171afca"}, {epoch: 200, expected: "0x427a30ab"}, {epoch: 201, expected: "0x427a30ab"}, {epoch: 250, expected: "0xd5310ef1"}, {epoch: 299, expected: "0xd5310ef1"}, {epoch: 300, expected: "0x51d229f7"}, {epoch: 301, expected: "0x51d229f7"}, {epoch: 9, gvr: fillGVR(byte(1)), expected: "0x4a5c3011"}, {epoch: 9, gvr: fillGVR(byte(2)), expected: "0xe8332b52"}, {epoch: 9, gvr: fillGVR(byte(3)), expected: "0x0e38e75e"}, {epoch: 100, gvr: fillGVR(byte(1)), expected: "0xbfe98545"}, {epoch: 100, gvr: fillGVR(byte(2)), expected: "0x9b7e4788"}, {epoch: 100, gvr: fillGVR(byte(3)), expected: "0x8b5ce4af"}, } for _, c := range cases { t.Run(fmt.Sprintf("%d_%s", c.epoch, c.expected), func(t *testing.T) { var expected [4]byte err := hexutil.UnmarshalFixedText("ForkDigest", []byte(c.expected), expected[:]) require.NoError(t, err) cfg := configs[c.gvr] digest := params.ForkDigestUsingConfig(c.epoch, cfg) require.Equal(t, expected, digest) }) } } func testConfigForSchedule(gvr [32]byte) *params.BeaconChainConfig { cfg := params.MinimalSpecConfig().Copy() cfg.AltairForkEpoch = 0 cfg.BellatrixForkEpoch = 0 cfg.CapellaForkEpoch = 0 cfg.DenebForkEpoch = 0 cfg.ElectraForkEpoch = 9 cfg.FuluForkEpoch = 100 cfg.GenesisValidatorsRoot = gvr cfg.BlobSchedule = []params.BlobScheduleEntry{ {Epoch: 100, MaxBlobsPerBlock: 100}, {Epoch: 150, MaxBlobsPerBlock: 175}, {Epoch: 200, MaxBlobsPerBlock: 200}, {Epoch: 250, MaxBlobsPerBlock: 275}, {Epoch: 300, MaxBlobsPerBlock: 300}, } return cfg }