package state_native import ( "context" "strconv" "sync" "testing" "github.com/prysmaticlabs/go-bitfield" nativetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/state-native/types" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stateutil" "github.com/prysmaticlabs/prysm/v3/config/features" fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" "github.com/prysmaticlabs/prysm/v3/config/params" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v3/testing/assert" "github.com/prysmaticlabs/prysm/v3/testing/require" ) func TestValidatorMap_DistinctCopy(t *testing.T) { count := uint64(100) vals := make([]*ethpb.Validator, 0, count) for i := uint64(1); i < count; i++ { someRoot := [32]byte{} someKey := [fieldparams.BLSPubkeyLength]byte{} copy(someRoot[:], strconv.Itoa(int(i))) copy(someKey[:], strconv.Itoa(int(i))) vals = append(vals, ðpb.Validator{ PublicKey: someKey[:], WithdrawalCredentials: someRoot[:], EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, Slashed: false, ActivationEligibilityEpoch: 1, ActivationEpoch: 1, ExitEpoch: 1, WithdrawableEpoch: 1, }) } handler := stateutil.NewValMapHandler(vals) newHandler := handler.Copy() wantedPubkey := strconv.Itoa(22) handler.Set(bytesutil.ToBytes48([]byte(wantedPubkey)), 27) val1, _ := handler.Get(bytesutil.ToBytes48([]byte(wantedPubkey))) val2, _ := newHandler.Get(bytesutil.ToBytes48([]byte(wantedPubkey))) assert.NotEqual(t, val1, val2, "Values are supposed to be unequal due to copy") } func TestBeaconState_NoDeadlock_Phase0(t *testing.T) { count := uint64(100) vals := make([]*ethpb.Validator, 0, count) for i := uint64(1); i < count; i++ { someRoot := [32]byte{} someKey := [fieldparams.BLSPubkeyLength]byte{} copy(someRoot[:], strconv.Itoa(int(i))) copy(someKey[:], strconv.Itoa(int(i))) vals = append(vals, ðpb.Validator{ PublicKey: someKey[:], WithdrawalCredentials: someRoot[:], EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, Slashed: false, ActivationEligibilityEpoch: 1, ActivationEpoch: 1, ExitEpoch: 1, WithdrawableEpoch: 1, }) } features.Init(&features.Flags{EnableNativeState: true}) newState, err := InitializeFromProtoUnsafePhase0(ðpb.BeaconState{ Validators: vals, }) assert.NoError(t, err) st, ok := newState.(*BeaconState) require.Equal(t, true, ok) wg := new(sync.WaitGroup) wg.Add(1) go func() { // Continuously lock and unlock the state // by acquiring the lock. for i := 0; i < 1000; i++ { for _, f := range st.stateFieldLeaves { f.Lock() if f.Empty() { f.InsertFieldLayer(make([][]*[32]byte, 10)) } f.Unlock() f.FieldReference().AddRef() } } wg.Done() }() // Constantly read from the offending portion // of the code to ensure there is no possible // recursive read locking. for i := 0; i < 1000; i++ { go func() { _ = st.FieldReferencesCount() }() } // Test will not terminate in the event of a deadlock. wg.Wait() } func TestBeaconState_NoDeadlock_Altair(t *testing.T) { count := uint64(100) vals := make([]*ethpb.Validator, 0, count) for i := uint64(1); i < count; i++ { someRoot := [32]byte{} someKey := [fieldparams.BLSPubkeyLength]byte{} copy(someRoot[:], strconv.Itoa(int(i))) copy(someKey[:], strconv.Itoa(int(i))) vals = append(vals, ðpb.Validator{ PublicKey: someKey[:], WithdrawalCredentials: someRoot[:], EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, Slashed: false, ActivationEligibilityEpoch: 1, ActivationEpoch: 1, ExitEpoch: 1, WithdrawableEpoch: 1, }) } features.Init(&features.Flags{EnableNativeState: true}) st, err := InitializeFromProtoUnsafeAltair(ðpb.BeaconStateAltair{ Validators: vals, }) assert.NoError(t, err) s, ok := st.(*BeaconState) require.Equal(t, true, ok) wg := new(sync.WaitGroup) wg.Add(1) go func() { // Continuously lock and unlock the state // by acquiring the lock. for i := 0; i < 1000; i++ { for _, f := range s.stateFieldLeaves { f.Lock() if f.Empty() { f.InsertFieldLayer(make([][]*[32]byte, 10)) } f.Unlock() f.FieldReference().AddRef() } } wg.Done() }() // Constantly read from the offending portion // of the code to ensure there is no possible // recursive read locking. for i := 0; i < 1000; i++ { go func() { _ = st.FieldReferencesCount() }() } // Test will not terminate in the event of a deadlock. wg.Wait() } func TestBeaconState_NoDeadlock_Bellatrix(t *testing.T) { count := uint64(100) vals := make([]*ethpb.Validator, 0, count) for i := uint64(1); i < count; i++ { someRoot := [32]byte{} someKey := [fieldparams.BLSPubkeyLength]byte{} copy(someRoot[:], strconv.Itoa(int(i))) copy(someKey[:], strconv.Itoa(int(i))) vals = append(vals, ðpb.Validator{ PublicKey: someKey[:], WithdrawalCredentials: someRoot[:], EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, Slashed: false, ActivationEligibilityEpoch: 1, ActivationEpoch: 1, ExitEpoch: 1, WithdrawableEpoch: 1, }) } features.Init(&features.Flags{EnableNativeState: true}) st, err := InitializeFromProtoUnsafeBellatrix(ðpb.BeaconStateBellatrix{ Validators: vals, }) assert.NoError(t, err) s, ok := st.(*BeaconState) require.Equal(t, true, ok) wg := new(sync.WaitGroup) wg.Add(1) go func() { // Continuously lock and unlock the state // by acquiring the lock. for i := 0; i < 1000; i++ { for _, f := range s.stateFieldLeaves { f.Lock() if f.Empty() { f.InsertFieldLayer(make([][]*[32]byte, 10)) } f.Unlock() f.FieldReference().AddRef() } } wg.Done() }() // Constantly read from the offending portion // of the code to ensure there is no possible // recursive read locking. for i := 0; i < 1000; i++ { go func() { _ = st.FieldReferencesCount() }() } // Test will not terminate in the event of a deadlock. wg.Wait() } func TestBeaconState_AppendBalanceWithTrie(t *testing.T) { count := uint64(100) vals := make([]*ethpb.Validator, 0, count) bals := make([]uint64, 0, count) for i := uint64(1); i < count; i++ { someRoot := [32]byte{} someKey := [fieldparams.BLSPubkeyLength]byte{} copy(someRoot[:], strconv.Itoa(int(i))) copy(someKey[:], strconv.Itoa(int(i))) vals = append(vals, ðpb.Validator{ PublicKey: someKey[:], WithdrawalCredentials: someRoot[:], EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, Slashed: false, ActivationEligibilityEpoch: 1, ActivationEpoch: 1, ExitEpoch: 1, WithdrawableEpoch: 1, }) bals = append(bals, params.BeaconConfig().MaxEffectiveBalance) } zeroHash := params.BeaconConfig().ZeroHash mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) for i := 0; i < len(mockblockRoots); i++ { mockblockRoots[i] = zeroHash[:] } mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) for i := 0; i < len(mockstateRoots); i++ { mockstateRoots[i] = zeroHash[:] } mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector) for i := 0; i < len(mockrandaoMixes); i++ { mockrandaoMixes[i] = zeroHash[:] } features.Init(&features.Flags{EnableNativeState: true}) newState, err := InitializeFromProtoPhase0(ðpb.BeaconState{ Slot: 1, GenesisValidatorsRoot: make([]byte, 32), Fork: ðpb.Fork{ PreviousVersion: make([]byte, 4), CurrentVersion: make([]byte, 4), Epoch: 0, }, LatestBlockHeader: ðpb.BeaconBlockHeader{ ParentRoot: make([]byte, fieldparams.RootLength), StateRoot: make([]byte, fieldparams.RootLength), BodyRoot: make([]byte, fieldparams.RootLength), }, Validators: vals, Balances: bals, Eth1Data: ðpb.Eth1Data{ DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32), }, BlockRoots: mockblockRoots, StateRoots: mockstateRoots, RandaoMixes: mockrandaoMixes, JustificationBits: bitfield.NewBitvector4(), PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector), }) assert.NoError(t, err) st, ok := newState.(*BeaconState) require.Equal(t, true, ok) _, err = st.HashTreeRoot(context.Background()) assert.NoError(t, err) for i := 0; i < 100; i++ { if i%2 == 0 { assert.NoError(t, st.UpdateBalancesAtIndex(types.ValidatorIndex(i), 1000)) } if i%3 == 0 { assert.NoError(t, st.AppendBalance(1000)) } } _, err = st.HashTreeRoot(context.Background()) assert.NoError(t, err) newRt := bytesutil.ToBytes32(st.merkleLayers[0][nativetypes.Balances]) wantedRt, err := stateutil.Uint64ListRootWithRegistryLimit(st.Balances()) assert.NoError(t, err) assert.Equal(t, wantedRt, newRt, "state roots are unequal") } func TestBeaconState_ModifyPreviousParticipationBits(t *testing.T) { features.Init(&features.Flags{EnableNativeState: true}) st, err := InitializeFromProtoUnsafePhase0(ðpb.BeaconState{}) assert.NoError(t, err) assert.ErrorContains(t, "ModifyPreviousParticipationBits is not supported", st.ModifyPreviousParticipationBits(func(val []byte) ([]byte, error) { return nil, nil })) } func TestBeaconState_ModifyCurrentParticipationBits(t *testing.T) { features.Init(&features.Flags{EnableNativeState: true}) st, err := InitializeFromProtoUnsafePhase0(ðpb.BeaconState{}) assert.NoError(t, err) assert.ErrorContains(t, "ModifyCurrentParticipationBits is not supported", st.ModifyCurrentParticipationBits(func(val []byte) ([]byte, error) { return nil, nil })) }