Skip to content

Commit

Permalink
Added checkpoints and justification bits processing (post-altair) (er…
Browse files Browse the repository at this point in the history
…igontech#6699)

* Added processing of checkpoints
* Unit tests rigorously imported from prysm
* They all pass :)
  • Loading branch information
Giulio2002 authored Jan 25, 2023
1 parent 20a865b commit ff21ef7
Show file tree
Hide file tree
Showing 17 changed files with 451 additions and 23 deletions.
15 changes: 12 additions & 3 deletions cl/clparams/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ var CheckpointSyncEndpoints = map[NetworkType][]string{

// BeaconChainConfig contains constant configs for node to participate in beacon chain.
type BeaconChainConfig struct {
// Constants (non-configurable)
GenesisSlot uint64 `yaml:"GENESIS_SLOT"` // GenesisSlot represents the first canonical slot number of the beacon chain.
GenesisEpoch uint64 `yaml:"GENESIS_EPOCH"` // GenesisEpoch represents the first canonical epoch number of the beacon chain.
FarFutureEpoch uint64 `yaml:"FAR_FUTURE_EPOCH"` // FarFutureEpoch represents a epoch extremely far away in the future used as the default penalization epoch for validators.
FarFutureSlot uint64 `yaml:"FAR_FUTURE_SLOT"` // FarFutureSlot represents a slot extremely far away in the future.
BaseRewardsPerEpoch uint64 `yaml:"BASE_REWARDS_PER_EPOCH"` // BaseRewardsPerEpoch is used to calculate the per epoch rewards.
DepositContractTreeDepth uint64 `yaml:"DEPOSIT_CONTRACT_TREE_DEPTH"` // DepositContractTreeDepth depth of the Merkle trie of deposits in the validator deposit contract on the PoW chain.
JustificationBitsLength uint64 `yaml:"JUSTIFICATION_BITS_LENGTH"` // JustificationBitsLength defines number of epochs to track when implementing k-finality in Casper FFG.

// Misc constants.
PresetBase string `yaml:"PRESET_BASE" spec:"true"` // PresetBase represents the underlying spec preset this config is based on.
ConfigName string `yaml:"CONFIG_NAME" spec:"true"` // ConfigName for allowing an easy human-readable way of knowing what chain is being used.
Expand Down Expand Up @@ -441,11 +450,11 @@ func configForkNames(b *BeaconChainConfig) map[[VersionLength]byte]string {

var MainnetBeaconConfig BeaconChainConfig = BeaconChainConfig{
// Constants (Non-configurable)
/*FarFutureEpoch: math.MaxUint64,
FarFutureEpoch: math.MaxUint64,
FarFutureSlot: math.MaxUint64,
BaseRewardsPerEpoch: 4,
DepositContractTreeDepth: 32,*/
GenesisDelay: 604800, // 1 week.
DepositContractTreeDepth: 32,
GenesisDelay: 604800, // 1 week.

// Misc constant.
TargetCommitteeSize: 128,
Expand Down
35 changes: 35 additions & 0 deletions cl/cltypes/justification_bits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cltypes

import "github.com/ledgerwatch/erigon/cl/utils"

const JustificationBitsLength = 4

type JustificationBits [JustificationBitsLength]bool // Bit vector of size 4

func (j JustificationBits) Byte() (out byte) {
for i, bit := range j {
if !bit {
continue
}
out += byte(utils.PowerOf2(uint64(i)))
}
return
}

func (j *JustificationBits) FromByte(b byte) {
j[0] = b&1 > 0
j[1] = b&2 > 0
j[2] = b&4 > 0
j[3] = b&8 > 0
}

// CheckRange checks if bits in certain range are all enabled.
func (j JustificationBits) CheckRange(start int, end int) bool {
checkBits := j[start:end]
for _, bit := range checkBits {
if !bit {
return false
}
}
return true
}
15 changes: 15 additions & 0 deletions cl/cltypes/justification_bits_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cltypes_test

import (
"testing"

"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/stretchr/testify/require"
)

func TestParticipationBits(t *testing.T) {
bits := cltypes.JustificationBits{}
bits.FromByte(2)
require.Equal(t, bits, cltypes.JustificationBits{false, true, false, false})
require.Equal(t, bits.Byte(), byte(2))
}
34 changes: 34 additions & 0 deletions cl/cltypes/participation_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cltypes

import (
"github.com/ledgerwatch/erigon/cl/utils"
)

type ParticipationFlags byte

func (f ParticipationFlags) Add(index int) ParticipationFlags {
return f | ParticipationFlags(utils.PowerOf2(uint64(index)))
}

func (f ParticipationFlags) HasFlag(index int) bool {
flag := ParticipationFlags(utils.PowerOf2(uint64(index)))
return f&flag == flag
}

type ParticipationFlagsList []ParticipationFlags

func (p ParticipationFlagsList) Bytes() []byte {
b := make([]byte, len(p))
for i := range p {
b[i] = byte(p[i])
}
return b
}

func ParticipationFlagsListFromBytes(buf []byte) ParticipationFlagsList {
flagsList := make([]ParticipationFlags, len(buf))
for i := range flagsList {
flagsList[i] = ParticipationFlags(buf[i])
}
return flagsList
}
14 changes: 14 additions & 0 deletions cl/cltypes/participation_flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cltypes_test

import (
"testing"

"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/stretchr/testify/require"
)

func TestParticipationFlags(t *testing.T) {
flagsList := cltypes.ParticipationFlagsListFromBytes([]byte{0, 0, 0, 0})
flagsList[0] = flagsList[0].Add(4) // Turn on fourth bit
require.True(t, flagsList[0].HasFlag(4))
}
5 changes: 5 additions & 0 deletions cl/cltypes/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,8 @@ func (v *Validator) HashSSZ() ([32]byte, error) {
leaves[7] = merkle_tree.Uint64Root(v.WithdrawableEpoch)
return merkle_tree.ArraysRoot(leaves, 8)
}

// Active returns if validator is active for given epoch
func (v *Validator) Active(epoch uint64) bool {
return v.ActivationEpoch <= epoch && epoch < v.ExitEpoch
}
2 changes: 1 addition & 1 deletion cmd/erigon-cl/core/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func RetrieveBeaconState(ctx context.Context, beaconConfig *clparams.BeaconChain

epoch := utils.GetCurrentEpoch(genesisConfig.GenesisTime, beaconConfig.SecondsPerSlot, beaconConfig.SlotsPerEpoch)

beaconState := &state.BeaconState{}
beaconState := state.New(beaconConfig)
fmt.Println(int(beaconConfig.GetCurrentStateVersion(epoch)))
err = beaconState.DecodeSSZWithVersion(marshaled, int(beaconConfig.GetCurrentStateVersion(epoch)))
if err != nil {
Expand Down
98 changes: 98 additions & 0 deletions cmd/erigon-cl/core/state/accessors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package state

import (
"fmt"

libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/cltypes"
)

// GetActiveValidatorsIndices returns the list of validator indices active for the given epoch.
func (b *BeaconState) GetActiveValidatorsIndices(epoch uint64) (indicies []uint64) {
for i, validator := range b.validators {
if !validator.Active(epoch) {
continue
}
indicies = append(indicies, uint64(i))
}
return
}

// Epoch returns current epoch.
func (b *BeaconState) Epoch() uint64 {
return b.slot / b.beaconConfig.SlotsPerEpoch // Return current state epoch
}

// PreviousEpoch returns previous epoch.
func (b *BeaconState) PreviousEpoch() uint64 {
epoch := b.Epoch()
if epoch == 0 {
return epoch
}
return epoch - 1
}

// getUnslashedParticipatingIndices returns set of currently unslashed participating indexes
func (b *BeaconState) GetUnslashedParticipatingIndices(flagIndex int, epoch uint64) (validatorSet []uint64, err error) {
var participation cltypes.ParticipationFlagsList
// Must be either previous or current epoch
switch epoch {
case b.Epoch():
participation = b.currentEpochParticipation
case b.PreviousEpoch():
participation = b.previousEpochParticipation
default:
return nil, fmt.Errorf("getUnslashedParticipatingIndices: only epoch and previous epoch can be used")
}
// Iterate over all validators and include the active ones that have flag_index enabled and are not slashed.
for i, validator := range b.Validators() {
if !validator.Active(epoch) ||
!participation[i].HasFlag(flagIndex) ||
validator.Slashed {
continue
}
validatorSet = append(validatorSet, uint64(i))
}
return
}

// GetTotalBalance return the sum of all balances within the given validator set.
func (b *BeaconState) GetTotalBalance(validatorSet []uint64) (uint64, error) {
var (
total uint64
validatorsSize = uint64(len(b.validators))
)
for _, validatorIndex := range validatorSet {
// Should be in bounds.
if validatorIndex >= validatorsSize {
return 0, fmt.Errorf("GetTotalBalance: out of bounds validator index")
}
total += b.validators[validatorIndex].EffectiveBalance
}
// Always minimum set to EffectiveBalanceIncrement
if total < b.beaconConfig.EffectiveBalanceIncrement {
total = b.beaconConfig.EffectiveBalanceIncrement
}
return total, nil
}

// GetTotalActiveBalance return the sum of all balances within active validators.
func (b *BeaconState) GetTotalActiveBalance() (uint64, error) {
return b.GetTotalBalance(b.GetActiveValidatorsIndices(b.Epoch()))
}

// GetBlockRoot returns blook root at start of a given epoch
func (b *BeaconState) GetBlockRoot(epoch uint64) (libcommon.Hash, error) {
return b.GetBlockRootAtSlot(epoch * b.beaconConfig.SlotsPerEpoch)
}

// GetBlockRootAtSlot returns the block root at a given slot
func (b *BeaconState) GetBlockRootAtSlot(slot uint64) (libcommon.Hash, error) {
if slot >= b.slot {
return libcommon.Hash{}, fmt.Errorf("GetBlockRootAtSlot: slot in the future")
}
if b.slot > slot+b.beaconConfig.SlotsPerHistoricalRoot {
return libcommon.Hash{}, fmt.Errorf("GetBlockRootAtSlot: slot too much far behind")
}
return b.blockRoots[slot%b.beaconConfig.SlotsPerHistoricalRoot], nil
}
50 changes: 50 additions & 0 deletions cmd/erigon-cl/core/state/accessors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package state_test

import (
"testing"

"github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state"
"github.com/stretchr/testify/require"
)

func TestActiveValidatorIndices(t *testing.T) {
epoch := uint64(2)
testState := state.GetEmptyBeaconState()
// Not Active validator
testState.AddValidator(&cltypes.Validator{
ActivationEpoch: 3,
ExitEpoch: 9,
EffectiveBalance: 2e9,
})
// Active Validator
testState.AddValidator(&cltypes.Validator{
ActivationEpoch: 1,
ExitEpoch: 9,
EffectiveBalance: 2e9,
})
testState.SetSlot(epoch * 32) // Epoch
testFlags := cltypes.ParticipationFlagsListFromBytes([]byte{1, 1})
testState.SetCurrentEpochParticipation(testFlags)
// Only validator at index 1 (second validator) is active.
require.Equal(t, testState.GetActiveValidatorsIndices(epoch), []uint64{1})
set, err := testState.GetUnslashedParticipatingIndices(0x00, epoch)
require.NoError(t, err)
require.Equal(t, set, []uint64{1})
// Check if balances are retrieved correctly
totalBalance, err := testState.GetTotalActiveBalance()
require.NoError(t, err)
require.Equal(t, totalBalance, uint64(2e9))
}

func TestGetBlockRoot(t *testing.T) {
epoch := uint64(2)
testState := state.GetEmptyBeaconState()
root := common.HexToHash("ff")
testState.SetSlot(100)
testState.SetBlockRootAt(int(epoch*32), root)
retrieved, err := testState.GetBlockRoot(epoch)
require.NoError(t, err)
require.Equal(t, retrieved, root)
}
11 changes: 8 additions & 3 deletions cmd/erigon-cl/core/state/getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package state
import (
libcommon "github.com/ledgerwatch/erigon-lib/common"

"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/ledgerwatch/erigon/core/types"
)
Expand Down Expand Up @@ -77,15 +78,15 @@ func (b *BeaconState) SlashingSegmentAt(pos int) uint64 {
return b.slashings[pos]
}

func (b *BeaconState) PreviousEpochParticipation() []byte {
func (b *BeaconState) PreviousEpochParticipation() cltypes.ParticipationFlagsList {
return b.previousEpochParticipation
}

func (b *BeaconState) CurrentEpochParticipation() []byte {
func (b *BeaconState) CurrentEpochParticipation() cltypes.ParticipationFlagsList {
return b.currentEpochParticipation
}

func (b *BeaconState) JustificationBits() byte {
func (b *BeaconState) JustificationBits() cltypes.JustificationBits {
return b.justificationBits
}

Expand Down Expand Up @@ -124,3 +125,7 @@ func (b *BeaconState) NextWithdrawalValidatorIndex() uint64 {
func (b *BeaconState) HistoricalSummaries() []*cltypes.HistoricalSummary {
return b.historicalSummaries
}

func (b *BeaconState) Version() clparams.StateVersion {
return b.version
}
6 changes: 3 additions & 3 deletions cmd/erigon-cl/core/state/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func (b *BeaconState) computeDirtyLeaves() error {
}
// Field(15): PreviousEpochParticipation
if b.isLeafDirty(PreviousEpochParticipationLeafIndex) {
participationRoot, err := merkle_tree.BitlistRootWithLimitForState(b.previousEpochParticipation, state_encoding.ValidatorRegistryLimit)
participationRoot, err := merkle_tree.BitlistRootWithLimitForState(b.previousEpochParticipation.Bytes(), state_encoding.ValidatorRegistryLimit)
if err != nil {
return err
}
Expand All @@ -149,7 +149,7 @@ func (b *BeaconState) computeDirtyLeaves() error {

// Field(16): CurrentEpochParticipation
if b.isLeafDirty(CurrentEpochParticipationLeafIndex) {
participationRoot, err := merkle_tree.BitlistRootWithLimitForState(b.currentEpochParticipation, state_encoding.ValidatorRegistryLimit)
participationRoot, err := merkle_tree.BitlistRootWithLimitForState(b.currentEpochParticipation.Bytes(), state_encoding.ValidatorRegistryLimit)
if err != nil {
return err
}
Expand All @@ -159,7 +159,7 @@ func (b *BeaconState) computeDirtyLeaves() error {
// Field(17): JustificationBits
if b.isLeafDirty(JustificationBitsLeafIndex) {
var root [32]byte
root[0] = b.justificationBits
root[0] = b.justificationBits.Byte()
b.updateLeaf(JustificationBitsLeafIndex, root)
}

Expand Down
6 changes: 3 additions & 3 deletions cmd/erigon-cl/core/state/setters.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,17 @@ func (b *BeaconState) SetSlashingSegmentAt(index int, segment uint64) {
b.slashings[index] = segment
}

func (b *BeaconState) SetPreviousEpochParticipation(previousEpochParticipation []byte) {
func (b *BeaconState) SetPreviousEpochParticipation(previousEpochParticipation []cltypes.ParticipationFlags) {
b.touchedLeaves[PreviousEpochParticipationLeafIndex] = true
b.previousEpochParticipation = previousEpochParticipation
}

func (b *BeaconState) SetCurrentEpochParticipation(currentEpochParticipation []byte) {
func (b *BeaconState) SetCurrentEpochParticipation(currentEpochParticipation []cltypes.ParticipationFlags) {
b.touchedLeaves[CurrentEpochParticipationLeafIndex] = true
b.currentEpochParticipation = currentEpochParticipation
}

func (b *BeaconState) SetJustificationBits(justificationBits byte) {
func (b *BeaconState) SetJustificationBits(justificationBits cltypes.JustificationBits) {
b.touchedLeaves[JustificationBitsLeafIndex] = true
b.justificationBits = justificationBits
}
Expand Down
Loading

0 comments on commit ff21ef7

Please sign in to comment.