Add ComputeProposerIndex (#6297)

This is described in
https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_proposer_index.

Part of https://github.com/ledgerwatch/erigon/issues/5965

I compared with the Prysm tests to confirm the implementation is
correct:
2e49fdb3d2/beacon-chain/core/helpers/validators_test.go (L506-L614)
This commit is contained in:
Mike Neuder 2022-12-15 18:06:49 -05:00 committed by GitHub
parent cb04e1166c
commit d1f6ed29ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 135 additions and 0 deletions

View File

@ -4,6 +4,9 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes"
) )
const SHUFFLE_ROUND_COUNT = uint8(90) const SHUFFLE_ROUND_COUNT = uint8(90)
@ -50,3 +53,34 @@ func ComputeShuffledIndex(ind, ind_count uint64, seed [32]byte) (uint64, error)
} }
return ind, nil return ind, nil
} }
func ComputePropserIndex(state *cltypes.BeaconStateBellatrix, indices []uint64, seed [32]byte) (uint64, error) {
if len(indices) == 0 {
return 0, fmt.Errorf("must have >0 indices")
}
maxRandomByte := uint64(1<<8 - 1)
i := uint64(0)
total := uint64(len(indices))
hash := sha256.New()
buf := make([]byte, 8)
for {
shuffled, err := ComputeShuffledIndex(i%total, total, seed)
if err != nil {
return 0, err
}
candidateIndex := indices[shuffled]
if candidateIndex >= uint64(len(state.Validators)) {
return 0, fmt.Errorf("candidate index out of range: %d for validator set of length: %d", candidateIndex, len(state.Validators))
}
binary.LittleEndian.PutUint64(buf, i/32)
input := append(seed[:], buf...)
hash.Reset()
hash.Write(input)
randomByte := uint64(hash.Sum(nil)[i%32])
effectiveBalance := state.Validators[candidateIndex].EffectiveBalance
if effectiveBalance*maxRandomByte >= clparams.MainnetBeaconConfig.MaxEffectiveBalance*randomByte {
return candidateIndex, nil
}
i += 1
}
}

View File

@ -2,6 +2,8 @@ package transition
import ( import (
"testing" "testing"
"github.com/ledgerwatch/erigon/cl/cltypes"
) )
func TestComputeShuffledIndex(t *testing.T) { func TestComputeShuffledIndex(t *testing.T) {
@ -34,3 +36,102 @@ func TestComputeShuffledIndex(t *testing.T) {
}) })
} }
} }
func TestComputeProposerIndex(t *testing.T) {
seed := [32]byte{}
copy(seed[:], []byte("seed"))
testCases := []struct {
description string
state *cltypes.BeaconStateBellatrix
indices []uint64
seed [32]byte
expected uint64
wantErr bool
}{
{
description: "success",
state: &cltypes.BeaconStateBellatrix{
Validators: []*cltypes.Validator{
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
},
},
indices: []uint64{0, 1, 2, 3, 4},
seed: seed,
expected: 2,
},
{
description: "single_active_index",
state: &cltypes.BeaconStateBellatrix{
Validators: []*cltypes.Validator{
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
},
},
indices: []uint64{3},
seed: seed,
expected: 3,
},
{
description: "second_half_active",
state: &cltypes.BeaconStateBellatrix{
Validators: []*cltypes.Validator{
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
},
},
indices: []uint64{5, 6, 7, 8, 9},
seed: seed,
expected: 7,
},
{
description: "zero_active_indices",
indices: []uint64{},
seed: seed,
wantErr: true,
},
{
description: "active_index_out_of_range",
indices: []uint64{100},
state: &cltypes.BeaconStateBellatrix{
Validators: []*cltypes.Validator{
{EffectiveBalance: testBeaconConfig.MaxEffectiveBalance},
},
},
seed: seed,
wantErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
got, err := ComputePropserIndex(tc.state, tc.indices, tc.seed)
if tc.wantErr {
if err == nil {
t.Errorf("unexpected success, wanted error")
}
return
}
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if got != tc.expected {
t.Errorf("unexpected result: got %d, want %d", got, tc.expected)
}
})
}
}