Use a Validator Reader When Computing Unrealized Balances (#13656)

* employ a val reader to prevent constant copies

* clean it up and fix tests

* gaz

* radek's review

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
Nishant Das 2024-02-24 20:10:00 +08:00 committed by GitHub
parent 1d5a09c05d
commit 5735379963
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 182 additions and 14 deletions

View File

@ -58,9 +58,9 @@ func (b *BeaconState) UnrealizedCheckpointBalances() (uint64, uint64, uint64, er
}
if features.Get().EnableExperimentalState {
return stateutil.UnrealizedCheckpointBalances(cp, pp, b.validatorsVal(), currentEpoch)
return stateutil.UnrealizedCheckpointBalances(cp, pp, stateutil.NewValMultiValueSliceReader(b.validatorsMultiValue, b), currentEpoch)
} else {
return stateutil.UnrealizedCheckpointBalances(cp, pp, b.validators, currentEpoch)
return stateutil.UnrealizedCheckpointBalances(cp, pp, stateutil.NewValSliceReader(b.validators), currentEpoch)
}
}

View File

@ -17,6 +17,7 @@ go_library(
"trie_helpers.go",
"unrealized_justification.go",
"validator_map_handler.go",
"validator_reader.go",
"validator_root.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stateutil",
@ -26,6 +27,7 @@ go_library(
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/multi-value-slice:go_default_library",
"//container/trie:go_default_library",
"//crypto/hash:go_default_library",
"//crypto/hash/htr:go_default_library",
@ -56,6 +58,7 @@ go_test(
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/multi-value-slice:go_default_library",
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",
"//encoding/ssz:go_default_library",

View File

@ -5,7 +5,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/math"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
// UnrealizedCheckpointBalances returns the total current active balance, the
@ -13,17 +12,21 @@ import (
// current epoch correctly attested for target balance. It takes the current and
// previous epoch participation bits as parameters so implicitly only works for
// beacon states post-Altair.
func UnrealizedCheckpointBalances(cp, pp []byte, validators []*ethpb.Validator, currentEpoch primitives.Epoch) (uint64, uint64, uint64, error) {
func UnrealizedCheckpointBalances(cp, pp []byte, validators ValReader, currentEpoch primitives.Epoch) (uint64, uint64, uint64, error) {
targetIdx := params.BeaconConfig().TimelyTargetFlagIndex
activeBalance := uint64(0)
currentTarget := uint64(0)
prevTarget := uint64(0)
if len(cp) < len(validators) || len(pp) < len(validators) {
if len(cp) < validators.Len() || len(pp) < validators.Len() {
return 0, 0, 0, errors.New("participation does not match validator set")
}
var err error
for i, v := range validators {
valLength := validators.Len()
for i := 0; i < valLength; i++ {
v, err := validators.At(i)
if err != nil {
return 0, 0, 0, err
}
active := v.ActivationEpoch <= currentEpoch && currentEpoch < v.ExitEpoch
if active && !v.Slashed {
activeBalance, err = math.Add64(activeBalance, v.EffectiveBalance)

View File

@ -4,6 +4,7 @@ import (
"testing"
"github.com/prysmaticlabs/prysm/v5/config/params"
multi_value_slice "github.com/prysmaticlabs/prysm/v5/container/multi-value-slice"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
@ -25,7 +26,7 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
pp := make([]byte, len(validators))
t.Run("No one voted last two epochs", func(tt *testing.T) {
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, validators, 0)
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValSliceReader(validators), 0)
require.NoError(tt, err)
require.Equal(tt, expectedActive, active)
require.Equal(tt, uint64(0), current)
@ -35,7 +36,7 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
t.Run("bad votes in last two epochs", func(tt *testing.T) {
copy(cp, []byte{0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0x00})
copy(pp, []byte{0x00, 0x00, 0x00, 0x00})
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, validators, 1)
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValSliceReader(validators), 1)
require.NoError(tt, err)
require.Equal(tt, expectedActive, active)
require.Equal(tt, uint64(0), current)
@ -45,7 +46,7 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
t.Run("two votes in last epoch", func(tt *testing.T) {
copy(cp, []byte{0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0x00, 1 << targetFlag, 1 << targetFlag})
copy(pp, []byte{0x00, 0x00, 0x00, 0x00, 0xFF ^ (1 << targetFlag)})
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, validators, 1)
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValSliceReader(validators), 1)
require.NoError(tt, err)
require.Equal(tt, expectedActive, active)
require.Equal(tt, 2*params.BeaconConfig().MaxEffectiveBalance, current)
@ -55,7 +56,7 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
t.Run("two votes in previous epoch", func(tt *testing.T) {
copy(cp, []byte{0x00, 0x00, 0x00, 0x00, 0xFF ^ (1 << targetFlag), 0x00})
copy(pp, []byte{0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0x00, 1 << targetFlag, 1 << targetFlag})
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, validators, 1)
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValSliceReader(validators), 1)
require.NoError(tt, err)
require.Equal(tt, expectedActive, active)
require.Equal(tt, uint64(0), current)
@ -66,7 +67,7 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
validators[0].EffectiveBalance = params.BeaconConfig().MaxEffectiveBalance - params.BeaconConfig().MinDepositAmount
copy(cp, []byte{0xFF, 0xFF, 0x00, 0x00, 0xFF ^ (1 << targetFlag), 0})
copy(pp, []byte{0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0x00, 0xFF, 0xFF})
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, validators, 1)
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValSliceReader(validators), 1)
require.NoError(tt, err)
expectedActive -= params.BeaconConfig().MinDepositAmount
require.Equal(tt, expectedActive, active)
@ -76,7 +77,7 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
t.Run("slash a validator", func(tt *testing.T) {
validators[1].Slashed = true
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, validators, 1)
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValSliceReader(validators), 1)
require.NoError(tt, err)
expectedActive -= params.BeaconConfig().MaxEffectiveBalance
require.Equal(tt, expectedActive, active)
@ -85,7 +86,7 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
})
t.Run("Exit a validator", func(tt *testing.T) {
validators[4].ExitEpoch = 1
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, validators, 2)
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValSliceReader(validators), 2)
require.NoError(tt, err)
expectedActive -= params.BeaconConfig().MaxEffectiveBalance
require.Equal(tt, expectedActive, active)
@ -93,3 +94,105 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
require.Equal(tt, params.BeaconConfig().MaxEffectiveBalance, previous)
})
}
func TestState_MVSlice_UnrealizedCheckpointBalances(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
targetFlag := params.BeaconConfig().TimelyTargetFlagIndex
expectedActive := params.BeaconConfig().MinGenesisActiveValidatorCount * params.BeaconConfig().MaxEffectiveBalance
balances := make([]uint64, params.BeaconConfig().MinGenesisActiveValidatorCount)
for i := 0; i < len(validators); i++ {
validators[i] = &ethpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
}
balances[i] = params.BeaconConfig().MaxEffectiveBalance
}
mv := &multi_value_slice.Slice[*ethpb.Validator]{}
mv.Init(validators)
cp := make([]byte, len(validators))
pp := make([]byte, len(validators))
t.Run("No one voted last two epochs", func(tt *testing.T) {
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValMultiValueSliceReader(mv, &testObject{id: 0}), 0)
require.NoError(tt, err)
require.Equal(tt, expectedActive, active)
require.Equal(tt, uint64(0), current)
require.Equal(tt, uint64(0), previous)
})
t.Run("bad votes in last two epochs", func(tt *testing.T) {
copy(cp, []byte{0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0x00})
copy(pp, []byte{0x00, 0x00, 0x00, 0x00})
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValMultiValueSliceReader(mv, &testObject{id: 0}), 1)
require.NoError(tt, err)
require.Equal(tt, expectedActive, active)
require.Equal(tt, uint64(0), current)
require.Equal(tt, uint64(0), previous)
})
t.Run("two votes in last epoch", func(tt *testing.T) {
copy(cp, []byte{0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0x00, 1 << targetFlag, 1 << targetFlag})
copy(pp, []byte{0x00, 0x00, 0x00, 0x00, 0xFF ^ (1 << targetFlag)})
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValMultiValueSliceReader(mv, &testObject{id: 0}), 1)
require.NoError(tt, err)
require.Equal(tt, expectedActive, active)
require.Equal(tt, 2*params.BeaconConfig().MaxEffectiveBalance, current)
require.Equal(tt, uint64(0), previous)
})
t.Run("two votes in previous epoch", func(tt *testing.T) {
copy(cp, []byte{0x00, 0x00, 0x00, 0x00, 0xFF ^ (1 << targetFlag), 0x00})
copy(pp, []byte{0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0x00, 1 << targetFlag, 1 << targetFlag})
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValMultiValueSliceReader(mv, &testObject{id: 0}), 1)
require.NoError(tt, err)
require.Equal(tt, expectedActive, active)
require.Equal(tt, uint64(0), current)
require.Equal(tt, 2*params.BeaconConfig().MaxEffectiveBalance, previous)
})
t.Run("votes in both epochs, decreased balance in first validator", func(tt *testing.T) {
validators[0].EffectiveBalance = params.BeaconConfig().MaxEffectiveBalance - params.BeaconConfig().MinDepositAmount
copy(cp, []byte{0xFF, 0xFF, 0x00, 0x00, 0xFF ^ (1 << targetFlag), 0})
copy(pp, []byte{0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0xFF ^ (1 << targetFlag), 0x00, 0xFF, 0xFF})
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValMultiValueSliceReader(mv, &testObject{id: 0}), 1)
require.NoError(tt, err)
expectedActive -= params.BeaconConfig().MinDepositAmount
require.Equal(tt, expectedActive, active)
require.Equal(tt, 2*params.BeaconConfig().MaxEffectiveBalance-params.BeaconConfig().MinDepositAmount, current)
require.Equal(tt, 2*params.BeaconConfig().MaxEffectiveBalance, previous)
})
t.Run("slash a validator", func(tt *testing.T) {
validators[1].Slashed = true
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValMultiValueSliceReader(mv, &testObject{id: 0}), 1)
require.NoError(tt, err)
expectedActive -= params.BeaconConfig().MaxEffectiveBalance
require.Equal(tt, expectedActive, active)
require.Equal(tt, params.BeaconConfig().MaxEffectiveBalance-params.BeaconConfig().MinDepositAmount, current)
require.Equal(tt, 2*params.BeaconConfig().MaxEffectiveBalance, previous)
})
t.Run("Exit a validator", func(tt *testing.T) {
validators[4].ExitEpoch = 1
active, previous, current, err := UnrealizedCheckpointBalances(cp, pp, NewValMultiValueSliceReader(mv, &testObject{id: 0}), 2)
require.NoError(tt, err)
expectedActive -= params.BeaconConfig().MaxEffectiveBalance
require.Equal(tt, expectedActive, active)
require.Equal(tt, params.BeaconConfig().MaxEffectiveBalance-params.BeaconConfig().MinDepositAmount, current)
require.Equal(tt, params.BeaconConfig().MaxEffectiveBalance, previous)
})
}
type testObject struct {
id uint64
}
func (o *testObject) Id() uint64 {
return o.id
}
func (o *testObject) SetId(id uint64) {
o.id = id
}

View File

@ -0,0 +1,59 @@
package stateutil
import (
multi_value_slice "github.com/prysmaticlabs/prysm/v5/container/multi-value-slice"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
// ValReader specifies an interface through which we can access the validator registry.
type ValReader interface {
Len() int
At(i int) (*ethpb.Validator, error)
}
// ValSliceReader describes a struct that conforms to the ValReader interface
type ValSliceReader struct {
Validators []*ethpb.Validator
}
// NewValSliceReader constructs a ValSliceReader object.
func NewValSliceReader(vals []*ethpb.Validator) ValSliceReader {
return ValSliceReader{Validators: vals}
}
// Len is the length of the validator registry.
func (v ValSliceReader) Len() int {
return len(v.Validators)
}
// At returns the validator at the provided index.
func (v ValSliceReader) At(i int) (*ethpb.Validator, error) {
return v.Validators[i], nil
}
// ValMultiValueSliceReader describes a struct that conforms to the ValReader interface.
// This struct is specifically designed for accessing validator data from a
// multivalue slice.
type ValMultiValueSliceReader struct {
ValMVSlice *multi_value_slice.Slice[*ethpb.Validator]
Identifier multi_value_slice.Identifiable
}
// NewValMultiValueSliceReader constructs a new val reader object.
func NewValMultiValueSliceReader(valSlice *multi_value_slice.Slice[*ethpb.Validator],
identifier multi_value_slice.Identifiable) ValMultiValueSliceReader {
return ValMultiValueSliceReader{
ValMVSlice: valSlice,
Identifier: identifier,
}
}
// Len is the length of the validator registry.
func (v ValMultiValueSliceReader) Len() int {
return v.ValMVSlice.Len(v.Identifier)
}
// At returns the validator at the provided index.
func (v ValMultiValueSliceReader) At(i int) (*ethpb.Validator, error) {
return v.ValMVSlice.At(v.Identifier, uint64(i))
}