mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 13:58:09 -05:00
Compare commits
57 Commits
readonly-p
...
duplicate-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c236f8c6ce | ||
|
|
de2c866707 | ||
|
|
74ddb84e0a | ||
|
|
0c6a068fd5 | ||
|
|
fad92472d8 | ||
|
|
2a44e8e6ec | ||
|
|
e3d27f29c7 | ||
|
|
e011f05403 | ||
|
|
b8cd77945d | ||
|
|
9a7f521f8a | ||
|
|
102f94f914 | ||
|
|
0c0a497651 | ||
|
|
e0785a8939 | ||
|
|
af098e737e | ||
|
|
1e4ede5585 | ||
|
|
fb2620364a | ||
|
|
68b38b6666 | ||
|
|
ff3e0856a1 | ||
|
|
85f334b663 | ||
|
|
10f520accb | ||
|
|
836608537e | ||
|
|
13e09c58f6 | ||
|
|
600ca08aa8 | ||
|
|
0ed74b3c4a | ||
|
|
7c69a9aa1c | ||
|
|
c50cfb044a | ||
|
|
38d4e179ba | ||
|
|
be80728320 | ||
|
|
09028033c0 | ||
|
|
52c036c3ab | ||
|
|
2fc7cdeba7 | ||
|
|
6f7976766d | ||
|
|
5c369361b0 | ||
|
|
7d48b45152 | ||
|
|
345aabe996 | ||
|
|
5d04b36680 | ||
|
|
cd8907f76c | ||
|
|
b108d5bf54 | ||
|
|
4d823acf45 | ||
|
|
aa868e5e8c | ||
|
|
b1be6cd20b | ||
|
|
fd9321f6ba | ||
|
|
8364226b68 | ||
|
|
49055acf81 | ||
|
|
57ffc12f17 | ||
|
|
d066480a51 | ||
|
|
0e8f98b2a4 | ||
|
|
3a734f51e0 | ||
|
|
c7e2d709cf | ||
|
|
8b4b3a269b | ||
|
|
2f76ba542f | ||
|
|
637cbc88e8 | ||
|
|
fadff022a0 | ||
|
|
05784a6c28 | ||
|
|
5a48e002dd | ||
|
|
d6f86269a4 | ||
|
|
422438f515 |
@@ -2,7 +2,10 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["event_stream.go"],
|
||||
srcs = [
|
||||
"event_stream.go",
|
||||
"utils.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/event",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
@@ -15,7 +18,10 @@ go_library(
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["event_stream_test.go"],
|
||||
srcs = [
|
||||
"event_stream_test.go",
|
||||
"utils_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//testing/require:go_default_library",
|
||||
|
||||
@@ -102,6 +102,8 @@ func (h *EventStream) Subscribe(eventsChannel chan<- *Event) {
|
||||
}()
|
||||
// Create a new scanner to read lines from the response body
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
// Set the split function for the scanning operation
|
||||
scanner.Split(scanLinesWithCarriage)
|
||||
|
||||
var eventType, data string // Variables to store event type and data
|
||||
|
||||
@@ -113,7 +115,7 @@ func (h *EventStream) Subscribe(eventsChannel chan<- *Event) {
|
||||
close(eventsChannel)
|
||||
return
|
||||
default:
|
||||
line := scanner.Text() // TODO(13730): scanner does not handle /r and does not fully adhere to https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface
|
||||
line := scanner.Text()
|
||||
// Handle the event based on your specific format
|
||||
if line == "" {
|
||||
// Empty line indicates the end of an event
|
||||
|
||||
@@ -43,8 +43,9 @@ func TestEventStream(t *testing.T) {
|
||||
mux.HandleFunc("/eth/v1/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
flusher, ok := w.(http.Flusher)
|
||||
require.Equal(t, true, ok)
|
||||
for i := 1; i <= 2; i++ {
|
||||
_, err := fmt.Fprintf(w, "event: head\ndata: data%d\n\n", i)
|
||||
for i := 1; i <= 3; i++ {
|
||||
events := [3]string{"event: head\ndata: data%d\n\n", "event: head\rdata: data%d\r\r", "event: head\r\ndata: data%d\r\n\r\n"}
|
||||
_, err := fmt.Fprintf(w, events[i-1], i)
|
||||
require.NoError(t, err)
|
||||
flusher.Flush() // Trigger flush to simulate streaming data
|
||||
time.Sleep(100 * time.Millisecond) // Simulate delay between events
|
||||
@@ -62,7 +63,7 @@ func TestEventStream(t *testing.T) {
|
||||
// Collect events
|
||||
var events []*Event
|
||||
|
||||
for len(events) != 2 {
|
||||
for len(events) != 3 {
|
||||
select {
|
||||
case event := <-eventsChannel:
|
||||
log.Info(event)
|
||||
@@ -71,7 +72,7 @@ func TestEventStream(t *testing.T) {
|
||||
}
|
||||
|
||||
// Assertions to verify the events content
|
||||
expectedData := []string{"data1", "data2"}
|
||||
expectedData := []string{"data1", "data2", "data3"}
|
||||
for i, event := range events {
|
||||
if string(event.Data) != expectedData[i] {
|
||||
t.Errorf("Expected event data %q, got %q", expectedData[i], string(event.Data))
|
||||
|
||||
36
api/client/event/utils.go
Normal file
36
api/client/event/utils.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// adapted from ScanLines in scan.go to handle carriage return characters as separators
|
||||
func scanLinesWithCarriage(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
if i, j := bytes.IndexByte(data, '\n'), bytes.IndexByte(data, '\r'); i >= 0 || j >= 0 {
|
||||
in := i
|
||||
// Select the first index of \n or \r or the second index of \r if it is followed by \n
|
||||
if i < 0 || (i > j && i != j+1 && j >= 0) {
|
||||
in = j
|
||||
}
|
||||
|
||||
// We have a full newline-terminated line.
|
||||
return in + 1, dropCR(data[0:in]), nil
|
||||
}
|
||||
// If we're at EOF, we have a final, non-terminated line. Return it.
|
||||
if atEOF {
|
||||
return len(data), dropCR(data), nil
|
||||
}
|
||||
// Request more data.
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
// dropCR drops a terminal \r from the data.
|
||||
func dropCR(data []byte) []byte {
|
||||
if len(data) > 0 && data[len(data)-1] == '\r' {
|
||||
return data[0 : len(data)-1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
97
api/client/event/utils_test.go
Normal file
97
api/client/event/utils_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func TestScanLinesWithCarriage(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "LF line endings",
|
||||
input: "line1\nline2\nline3",
|
||||
expected: []string{"line1", "line2", "line3"},
|
||||
},
|
||||
{
|
||||
name: "CR line endings",
|
||||
input: "line1\rline2\rline3",
|
||||
expected: []string{"line1", "line2", "line3"},
|
||||
},
|
||||
{
|
||||
name: "CRLF line endings",
|
||||
input: "line1\r\nline2\r\nline3",
|
||||
expected: []string{"line1", "line2", "line3"},
|
||||
},
|
||||
{
|
||||
name: "Mixed line endings",
|
||||
input: "line1\nline2\rline3\r\nline4",
|
||||
expected: []string{"line1", "line2", "line3", "line4"},
|
||||
},
|
||||
{
|
||||
name: "Empty lines",
|
||||
input: "line1\n\nline2\r\rline3",
|
||||
expected: []string{"line1", "", "line2", "", "line3"},
|
||||
},
|
||||
{
|
||||
name: "Empty lines 2",
|
||||
input: "line1\n\rline2\n\rline3",
|
||||
expected: []string{"line1", "", "line2", "", "line3"},
|
||||
},
|
||||
{
|
||||
name: "No line endings",
|
||||
input: "single line without ending",
|
||||
expected: []string{"single line without ending"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
scanner := bufio.NewScanner(bytes.NewReader([]byte(tc.input)))
|
||||
scanner.Split(scanLinesWithCarriage)
|
||||
|
||||
var lines []string
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
require.NoError(t, scanner.Err())
|
||||
require.Equal(t, len(tc.expected), len(lines), "Number of lines does not match")
|
||||
for i, line := range lines {
|
||||
require.Equal(t, tc.expected[i], line, "Line %d does not match", i)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestScanLinesWithCarriageEdgeCases tests edge cases and potential error scenarios
|
||||
func TestScanLinesWithCarriageEdgeCases(t *testing.T) {
|
||||
t.Run("Empty input", func(t *testing.T) {
|
||||
scanner := bufio.NewScanner(bytes.NewReader([]byte("")))
|
||||
scanner.Split(scanLinesWithCarriage)
|
||||
require.Equal(t, scanner.Scan(), false)
|
||||
require.NoError(t, scanner.Err())
|
||||
})
|
||||
|
||||
t.Run("Very long line", func(t *testing.T) {
|
||||
longLine := bytes.Repeat([]byte("a"), bufio.MaxScanTokenSize+1)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(longLine))
|
||||
scanner.Split(scanLinesWithCarriage)
|
||||
require.Equal(t, scanner.Scan(), false)
|
||||
require.NotNil(t, scanner.Err())
|
||||
})
|
||||
|
||||
t.Run("Line ending at max token size", func(t *testing.T) {
|
||||
input := append(bytes.Repeat([]byte("a"), bufio.MaxScanTokenSize-1), '\n')
|
||||
scanner := bufio.NewScanner(bytes.NewReader(input))
|
||||
scanner.Split(scanLinesWithCarriage)
|
||||
require.Equal(t, scanner.Scan(), true)
|
||||
require.Equal(t, string(bytes.Repeat([]byte("a"), bufio.MaxScanTokenSize-1)), scanner.Text())
|
||||
})
|
||||
}
|
||||
@@ -196,3 +196,48 @@ type DepositSnapshot struct {
|
||||
ExecutionBlockHash string `json:"execution_block_hash"`
|
||||
ExecutionBlockHeight string `json:"execution_block_height"`
|
||||
}
|
||||
|
||||
type GetIndividualVotesRequest struct {
|
||||
Epoch string `json:"epoch"`
|
||||
PublicKeys []string `json:"public_keys,omitempty"`
|
||||
Indices []string `json:"indices,omitempty"`
|
||||
}
|
||||
|
||||
type GetIndividualVotesResponse struct {
|
||||
IndividualVotes []*IndividualVote `json:"individual_votes"`
|
||||
}
|
||||
|
||||
type IndividualVote struct {
|
||||
Epoch string `json:"epoch"`
|
||||
PublicKey string `json:"public_keys,omitempty"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
IsSlashed bool `json:"is_slashed"`
|
||||
IsWithdrawableInCurrentEpoch bool `json:"is_withdrawable_in_current_epoch"`
|
||||
IsActiveInCurrentEpoch bool `json:"is_active_in_current_epoch"`
|
||||
IsActiveInPreviousEpoch bool `json:"is_active_in_previous_epoch"`
|
||||
IsCurrentEpochAttester bool `json:"is_current_epoch_attester"`
|
||||
IsCurrentEpochTargetAttester bool `json:"is_current_epoch_target_attester"`
|
||||
IsPreviousEpochAttester bool `json:"is_previous_epoch_attester"`
|
||||
IsPreviousEpochTargetAttester bool `json:"is_previous_epoch_target_attester"`
|
||||
IsPreviousEpochHeadAttester bool `json:"is_previous_epoch_head_attester"`
|
||||
CurrentEpochEffectiveBalanceGwei string `json:"current_epoch_effective_balance_gwei"`
|
||||
InclusionSlot string `json:"inclusion_slot"`
|
||||
InclusionDistance string `json:"inclusion_distance"`
|
||||
InactivityScore string `json:"inactivity_score"`
|
||||
}
|
||||
|
||||
type ChainHead struct {
|
||||
HeadSlot string `json:"head_slot"`
|
||||
HeadEpoch string `json:"head_epoch"`
|
||||
HeadBlockRoot string `json:"head_block_root"`
|
||||
FinalizedSlot string `json:"finalized_slot"`
|
||||
FinalizedEpoch string `json:"finalized_epoch"`
|
||||
FinalizedBlockRoot string `json:"finalized_block_root"`
|
||||
JustifiedSlot string `json:"justified_slot"`
|
||||
JustifiedEpoch string `json:"justified_epoch"`
|
||||
JustifiedBlockRoot string `json:"justified_block_root"`
|
||||
PreviousJustifiedSlot string `json:"previous_justified_slot"`
|
||||
PreviousJustifiedEpoch string `json:"previous_justified_epoch"`
|
||||
PreviousJustifiedBlockRoot string `json:"previous_justified_block_root"`
|
||||
OptimisticStatus bool `json:"optimistic_status"`
|
||||
}
|
||||
|
||||
@@ -118,3 +118,34 @@ type GetValidatorPerformanceResponse struct {
|
||||
MissingValidators [][]byte `json:"missing_validators,omitempty"`
|
||||
InactivityScores []uint64 `json:"inactivity_scores,omitempty"`
|
||||
}
|
||||
|
||||
type GetValidatorParticipationResponse struct {
|
||||
Epoch string `json:"epoch"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Participation *ValidatorParticipation `json:"participation"`
|
||||
}
|
||||
|
||||
type ValidatorParticipation struct {
|
||||
GlobalParticipationRate string `json:"global_participation_rate" deprecated:"true"`
|
||||
VotedEther string `json:"voted_ether" deprecated:"true"`
|
||||
EligibleEther string `json:"eligible_ether" deprecated:"true"`
|
||||
CurrentEpochActiveGwei string `json:"current_epoch_active_gwei"`
|
||||
CurrentEpochAttestingGwei string `json:"current_epoch_attesting_gwei"`
|
||||
CurrentEpochTargetAttestingGwei string `json:"current_epoch_target_attesting_gwei"`
|
||||
PreviousEpochActiveGwei string `json:"previous_epoch_active_gwei"`
|
||||
PreviousEpochAttestingGwei string `json:"previous_epoch_attesting_gwei"`
|
||||
PreviousEpochTargetAttestingGwei string `json:"previous_epoch_target_attesting_gwei"`
|
||||
PreviousEpochHeadAttestingGwei string `json:"previous_epoch_head_attesting_gwei"`
|
||||
}
|
||||
|
||||
type ActiveSetChanges struct {
|
||||
Epoch string `json:"epoch"`
|
||||
ActivatedPublicKeys []string `json:"activated_public_keys"`
|
||||
ActivatedIndices []string `json:"activated_indices"`
|
||||
ExitedPublicKeys []string `json:"exited_public_keys"`
|
||||
ExitedIndices []string `json:"exited_indices"`
|
||||
SlashedPublicKeys []string `json:"slashed_public_keys"`
|
||||
SlashedIndices []string `json:"slashed_indices"`
|
||||
EjectedPublicKeys []string `json:"ejected_public_keys"`
|
||||
EjectedIndices []string `json:"ejected_indices"`
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ type ForkchoiceFetcher interface {
|
||||
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
|
||||
NewSlot(context.Context, primitives.Slot) error
|
||||
ProposerBoost() [32]byte
|
||||
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
|
||||
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
|
||||
}
|
||||
|
||||
// TimeFetcher retrieves the Ethereum consensus data that's related to time.
|
||||
|
||||
@@ -99,3 +99,10 @@ func (s *Service) FinalizedBlockHash() [32]byte {
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.FinalizedPayloadBlockHash()
|
||||
}
|
||||
|
||||
// ParentRoot wraps a call to the corresponding method in forkchoice
|
||||
func (s *Service) ParentRoot(root [32]byte) ([32]byte, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.ParentRoot(root)
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
|
||||
|
||||
var attr payloadattribute.Attributer
|
||||
switch st.Version() {
|
||||
case version.Deneb:
|
||||
case version.Deneb, version.Electra:
|
||||
withdrawals, _, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get expected withdrawals to get payload attribute")
|
||||
|
||||
@@ -855,41 +855,63 @@ func Test_GetPayloadAttributeV2(t *testing.T) {
|
||||
require.Equal(t, 0, len(a))
|
||||
}
|
||||
|
||||
func Test_GetPayloadAttributeDeneb(t *testing.T) {
|
||||
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
ctx := tr.ctx
|
||||
func Test_GetPayloadAttributeV3(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
st bstate.BeaconState
|
||||
}{
|
||||
{
|
||||
name: "deneb",
|
||||
st: func() bstate.BeaconState {
|
||||
st, _ := util.DeterministicGenesisStateDeneb(t, 1)
|
||||
return st
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "electra",
|
||||
st: func() bstate.BeaconState {
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, 1)
|
||||
return st
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
st, _ := util.DeterministicGenesisStateDeneb(t, 1)
|
||||
attr := service.getPayloadAttribute(ctx, st, 0, []byte{})
|
||||
require.Equal(t, true, attr.IsEmpty())
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
ctx := tr.ctx
|
||||
|
||||
// Cache hit, advance state, no fee recipient
|
||||
slot := primitives.Slot(1)
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0})
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
||||
a, err := attr.Withdrawals()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(a))
|
||||
attr := service.getPayloadAttribute(ctx, test.st, 0, []byte{})
|
||||
require.Equal(t, true, attr.IsEmpty())
|
||||
|
||||
// Cache hit, advance state, has fee recipient
|
||||
suggestedAddr := common.HexToAddress("123")
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0})
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
|
||||
a, err = attr.Withdrawals()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(a))
|
||||
// Cache hit, advance state, no fee recipient
|
||||
slot := primitives.Slot(1)
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0})
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
||||
a, err := attr.Withdrawals()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(a))
|
||||
|
||||
attrV3, err := attr.PbV3()
|
||||
require.NoError(t, err)
|
||||
hr := service.headRoot()
|
||||
require.Equal(t, hr, [32]byte(attrV3.ParentBeaconBlockRoot))
|
||||
// Cache hit, advance state, has fee recipient
|
||||
suggestedAddr := common.HexToAddress("123")
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0})
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
|
||||
a, err = attr.Withdrawals()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(a))
|
||||
|
||||
attrV3, err := attr.PbV3()
|
||||
require.NoError(t, err)
|
||||
hr := service.headRoot()
|
||||
require.Equal(t, hr, [32]byte(attrV3.ParentBeaconBlockRoot))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UpdateLastValidatedCheckpoint(t *testing.T) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
finalityBranchNumOfLeaves = 6
|
||||
FinalityBranchNumOfLeaves = 6
|
||||
)
|
||||
|
||||
// CreateLightClientFinalityUpdate - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_finality_update
|
||||
@@ -215,8 +215,8 @@ func NewLightClientFinalityUpdateFromBeaconState(
|
||||
BodyRoot: make([]byte, 32),
|
||||
}
|
||||
|
||||
finalityBranch = make([][]byte, finalityBranchNumOfLeaves)
|
||||
for i := 0; i < finalityBranchNumOfLeaves; i++ {
|
||||
finalityBranch = make([][]byte, FinalityBranchNumOfLeaves)
|
||||
for i := 0; i < FinalityBranchNumOfLeaves; i++ {
|
||||
finalityBranch[i] = make([]byte, 32)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
|
||||
require.DeepSSZEqual(t, zeroHash, update.FinalizedHeader.ParentRoot, "Finalized header parent root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, update.FinalizedHeader.StateRoot, "Finalized header state root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, update.FinalizedHeader.BodyRoot, "Finalized header body root is not zero")
|
||||
require.Equal(t, finalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
require.Equal(t, FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
for _, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, zeroHash, leaf, "Leaf is not zero")
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (s *Service) OnAttestation(ctx context.Context, a ethpb.Att, disparity time
|
||||
if err := helpers.ValidateSlotTargetEpoch(a.GetData()); err != nil {
|
||||
return err
|
||||
}
|
||||
tgt := ethpb.CopyCheckpoint(a.GetData().Target)
|
||||
tgt := a.GetData().Target.Copy()
|
||||
|
||||
// Note that target root check is ignored here because it was performed in sync's validation pipeline:
|
||||
// validate_aggregate_proof.go and validate_beacon_attestation.go
|
||||
|
||||
@@ -142,7 +142,7 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
b := blks[0].Block()
|
||||
|
||||
// Retrieve incoming block's pre state.
|
||||
if err := s.verifyBlkPreState(ctx, b); err != nil {
|
||||
if err := s.verifyBlkPreState(ctx, b.ParentRoot()); err != nil {
|
||||
return err
|
||||
}
|
||||
preState, err := s.cfg.StateGen.StateByRootInitialSync(ctx, b.ParentRoot())
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
@@ -285,7 +286,7 @@ func (s *Service) getBlockPreState(ctx context.Context, b interfaces.ReadOnlyBea
|
||||
defer span.End()
|
||||
|
||||
// Verify incoming block has a valid pre state.
|
||||
if err := s.verifyBlkPreState(ctx, b); err != nil {
|
||||
if err := s.verifyBlkPreState(ctx, b.ParentRoot()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -311,11 +312,10 @@ func (s *Service) getBlockPreState(ctx context.Context, b interfaces.ReadOnlyBea
|
||||
}
|
||||
|
||||
// verifyBlkPreState validates input block has a valid pre-state.
|
||||
func (s *Service) verifyBlkPreState(ctx context.Context, b interfaces.ReadOnlyBeaconBlock) error {
|
||||
func (s *Service) verifyBlkPreState(ctx context.Context, parentRoot [field_params.RootLength]byte) error {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.verifyBlkPreState")
|
||||
defer span.End()
|
||||
|
||||
parentRoot := b.ParentRoot()
|
||||
// Loosen the check to HasBlock because state summary gets saved in batches
|
||||
// during initial syncing. There's no risk given a state summary object is just a
|
||||
// subset of the block object.
|
||||
|
||||
@@ -117,7 +117,7 @@ func TestCachedPreState_CanGetFromStateSummary(t *testing.T) {
|
||||
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Slot: 1, Root: root[:]}))
|
||||
require.NoError(t, service.cfg.StateGen.SaveState(ctx, root, st))
|
||||
require.NoError(t, service.verifyBlkPreState(ctx, wsb.Block()))
|
||||
require.NoError(t, service.verifyBlkPreState(ctx, wsb.Block().ParentRoot()))
|
||||
}
|
||||
|
||||
func TestFillForkChoiceMissingBlocks_CanSave(t *testing.T) {
|
||||
@@ -2044,7 +2044,11 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
|
||||
st, err = service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
b, err := util.GenerateFullBlockElectra(st, keys, util.DefaultBlockGenConfig(), 1)
|
||||
defaultConfig := util.DefaultBlockGenConfig()
|
||||
defaultConfig.NumWithdrawalRequests = 1
|
||||
defaultConfig.NumDepositRequests = 2
|
||||
defaultConfig.NumConsolidationRequests = 1
|
||||
b, err := util.GenerateFullBlockElectra(st, keys, defaultConfig, 1)
|
||||
require.NoError(t, err)
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
@@ -2059,7 +2063,7 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
|
||||
st, err = service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
b, err = util.GenerateFullBlockElectra(st, keys, util.DefaultBlockGenConfig(), 2)
|
||||
b, err = util.GenerateFullBlockElectra(st, keys, defaultConfig, 2)
|
||||
require.NoError(t, err)
|
||||
wsb, err = consensusblocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
@@ -2067,7 +2071,7 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
// prepare another block that is not inserted
|
||||
st3, err := transition.ExecuteStateTransition(ctx, st, wsb)
|
||||
require.NoError(t, err)
|
||||
b3, err := util.GenerateFullBlockElectra(st3, keys, util.DefaultBlockGenConfig(), 3)
|
||||
b3, err := util.GenerateFullBlockElectra(st3, keys, defaultConfig, 3)
|
||||
require.NoError(t, err)
|
||||
wsb3, err := consensusblocks.NewSignedBeaconBlock(b3)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -77,59 +77,20 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rob, err := blocks.NewROBlockWithRoot(block, blockRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preState, err := s.getBlockPreState(ctx, blockCopy.Block())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get block's prestate")
|
||||
}
|
||||
// Save current justified and finalized epochs for future use.
|
||||
currStoreJustifiedEpoch := s.CurrentJustifiedCheckpt().Epoch
|
||||
currStoreFinalizedEpoch := s.FinalizedCheckpt().Epoch
|
||||
currentEpoch := coreTime.CurrentEpoch(preState)
|
||||
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
currentCheckpoints := s.saveCurrentCheckpoints(preState)
|
||||
postState, isValidPayload, err := s.validateExecutionAndConsensus(ctx, preState, blockCopy, blockRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
var postState state.BeaconState
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
postState, err = s.validateStateTransition(ctx, preState, blockCopy)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to validate consensus state transition function")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
var isValidPayload bool
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
isValidPayload, err = s.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, blockCopy, blockRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not notify the engine of the new payload")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err := eg.Wait(); err != nil {
|
||||
daWaitedTime, err := s.handleDA(ctx, blockCopy, blockRoot, avs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daStartTime := time.Now()
|
||||
if avs != nil {
|
||||
if err := avs.IsDataAvailable(ctx, s.CurrentSlot(), rob); err != nil {
|
||||
return errors.Wrap(err, "could not validate blob data availability (AvailabilityStore.IsDataAvailable)")
|
||||
}
|
||||
} else {
|
||||
if err := s.isDataAvailable(ctx, blockRoot, blockCopy); err != nil {
|
||||
return errors.Wrap(err, "could not validate blob data availability")
|
||||
}
|
||||
}
|
||||
daWaitedTime := time.Since(daStartTime)
|
||||
dataAvailWaitedTime.Observe(float64(daWaitedTime.Milliseconds()))
|
||||
|
||||
// Defragment the state before continuing block processing.
|
||||
s.defragmentState(postState)
|
||||
|
||||
@@ -151,29 +112,9 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
}
|
||||
if coreTime.CurrentEpoch(postState) > currentEpoch && s.cfg.ForkChoiceStore.IsCanonical(blockRoot) {
|
||||
headSt, err := s.HeadState(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get head state")
|
||||
}
|
||||
if err := reportEpochMetrics(ctx, postState, headSt); err != nil {
|
||||
log.WithError(err).Error("could not report epoch metrics")
|
||||
}
|
||||
if err := s.updateCheckpoints(ctx, currentCheckpoints, preState, postState, blockRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.updateJustificationOnBlock(ctx, preState, postState, currStoreJustifiedEpoch); err != nil {
|
||||
return errors.Wrap(err, "could not update justified checkpoint")
|
||||
}
|
||||
|
||||
newFinalized, err := s.updateFinalizationOnBlock(ctx, preState, postState, currStoreFinalizedEpoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not update finalized checkpoint")
|
||||
}
|
||||
// Send finalized events and finalized deposits in the background
|
||||
if newFinalized {
|
||||
// hook to process all post state finalization tasks
|
||||
s.executePostFinalizationTasks(ctx, postState)
|
||||
}
|
||||
|
||||
// If slasher is configured, forward the attestations in the block via an event feed for processing.
|
||||
if features.Get().EnableSlasher {
|
||||
go s.sendBlockAttestationsToSlasher(blockCopy, preState)
|
||||
@@ -193,31 +134,140 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
if err := s.handleCaches(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.reportPostBlockProcessing(blockCopy, blockRoot, receivedTime, daWaitedTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
type ffgCheckpoints struct {
|
||||
j, f, c primitives.Epoch
|
||||
}
|
||||
|
||||
func (s *Service) saveCurrentCheckpoints(state state.BeaconState) (cp ffgCheckpoints) {
|
||||
// Save current justified and finalized epochs for future use.
|
||||
cp.j = s.CurrentJustifiedCheckpt().Epoch
|
||||
cp.f = s.FinalizedCheckpt().Epoch
|
||||
cp.c = coreTime.CurrentEpoch(state)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) updateCheckpoints(
|
||||
ctx context.Context,
|
||||
cp ffgCheckpoints,
|
||||
preState, postState state.BeaconState,
|
||||
blockRoot [32]byte,
|
||||
) error {
|
||||
if coreTime.CurrentEpoch(postState) > cp.c && s.cfg.ForkChoiceStore.IsCanonical(blockRoot) {
|
||||
headSt, err := s.HeadState(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get head state")
|
||||
}
|
||||
if err := reportEpochMetrics(ctx, postState, headSt); err != nil {
|
||||
log.WithError(err).Error("could not report epoch metrics")
|
||||
}
|
||||
}
|
||||
if err := s.updateJustificationOnBlock(ctx, preState, postState, cp.j); err != nil {
|
||||
return errors.Wrap(err, "could not update justified checkpoint")
|
||||
}
|
||||
|
||||
newFinalized, err := s.updateFinalizationOnBlock(ctx, preState, postState, cp.f)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not update finalized checkpoint")
|
||||
}
|
||||
// Send finalized events and finalized deposits in the background
|
||||
if newFinalized {
|
||||
// hook to process all post state finalization tasks
|
||||
s.executePostFinalizationTasks(ctx, postState)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) validateExecutionAndConsensus(
|
||||
ctx context.Context,
|
||||
preState state.BeaconState,
|
||||
block interfaces.SignedBeaconBlock,
|
||||
blockRoot [32]byte,
|
||||
) (state.BeaconState, bool, error) {
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
var postState state.BeaconState
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
postState, err = s.validateStateTransition(ctx, preState, block)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to validate consensus state transition function")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
var isValidPayload bool
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
isValidPayload, err = s.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, block, blockRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not notify the engine of the new payload")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return postState, isValidPayload, nil
|
||||
}
|
||||
|
||||
func (s *Service) handleDA(
|
||||
ctx context.Context,
|
||||
block interfaces.SignedBeaconBlock,
|
||||
blockRoot [32]byte,
|
||||
avs das.AvailabilityStore,
|
||||
) (time.Duration, error) {
|
||||
daStartTime := time.Now()
|
||||
if avs != nil {
|
||||
rob, err := blocks.NewROBlockWithRoot(block, blockRoot)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := avs.IsDataAvailable(ctx, s.CurrentSlot(), rob); err != nil {
|
||||
return 0, errors.Wrap(err, "could not validate blob data availability (AvailabilityStore.IsDataAvailable)")
|
||||
}
|
||||
} else {
|
||||
if err := s.isDataAvailable(ctx, blockRoot, block); err != nil {
|
||||
return 0, errors.Wrap(err, "could not validate blob data availability")
|
||||
}
|
||||
}
|
||||
daWaitedTime := time.Since(daStartTime)
|
||||
dataAvailWaitedTime.Observe(float64(daWaitedTime.Milliseconds()))
|
||||
return daWaitedTime, nil
|
||||
}
|
||||
|
||||
func (s *Service) reportPostBlockProcessing(
|
||||
block interfaces.SignedBeaconBlock,
|
||||
blockRoot [32]byte,
|
||||
receivedTime time.Time,
|
||||
daWaitedTime time.Duration,
|
||||
) {
|
||||
// Reports on block and fork choice metrics.
|
||||
cp := s.cfg.ForkChoiceStore.FinalizedCheckpoint()
|
||||
finalized := ðpb.Checkpoint{Epoch: cp.Epoch, Root: bytesutil.SafeCopyBytes(cp.Root[:])}
|
||||
reportSlotMetrics(blockCopy.Block().Slot(), s.HeadSlot(), s.CurrentSlot(), finalized)
|
||||
reportSlotMetrics(block.Block().Slot(), s.HeadSlot(), s.CurrentSlot(), finalized)
|
||||
|
||||
// Log block sync status.
|
||||
cp = s.cfg.ForkChoiceStore.JustifiedCheckpoint()
|
||||
justified := ðpb.Checkpoint{Epoch: cp.Epoch, Root: bytesutil.SafeCopyBytes(cp.Root[:])}
|
||||
if err := logBlockSyncStatus(blockCopy.Block(), blockRoot, justified, finalized, receivedTime, uint64(s.genesisTime.Unix()), daWaitedTime); err != nil {
|
||||
if err := logBlockSyncStatus(block.Block(), blockRoot, justified, finalized, receivedTime, uint64(s.genesisTime.Unix()), daWaitedTime); err != nil {
|
||||
log.WithError(err).Error("Unable to log block sync status")
|
||||
}
|
||||
// Log payload data
|
||||
if err := logPayload(blockCopy.Block()); err != nil {
|
||||
if err := logPayload(block.Block()); err != nil {
|
||||
log.WithError(err).Error("Unable to log debug block payload data")
|
||||
}
|
||||
// Log state transition data.
|
||||
if err := logStateTransitionData(blockCopy.Block()); err != nil {
|
||||
if err := logStateTransitionData(block.Block()); err != nil {
|
||||
log.WithError(err).Error("Unable to log state transition data")
|
||||
}
|
||||
|
||||
timeWithoutDaWait := time.Since(receivedTime) - daWaitedTime
|
||||
chainServiceProcessingTime.Observe(float64(timeWithoutDaWait.Milliseconds()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) executePostFinalizationTasks(ctx context.Context, finalizedState state.BeaconState) {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/das"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/voluntaryexits"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
@@ -421,14 +420,9 @@ func Test_sendNewFinalizedEvent(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_executePostFinalizationTasks(t *testing.T) {
|
||||
resetFn := features.InitWithReset(&features.Flags{
|
||||
EIP6110ValidatorIndexCache: true,
|
||||
})
|
||||
defer resetFn()
|
||||
|
||||
logHook := logTest.NewGlobal()
|
||||
|
||||
headState, err := util.NewBeaconState()
|
||||
headState, err := util.NewBeaconStateElectra()
|
||||
require.NoError(t, err)
|
||||
finalizedStRoot, err := headState.HashTreeRoot(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -503,16 +503,15 @@ func TestChainService_EverythingOptimistic(t *testing.T) {
|
||||
|
||||
func TestStartFromSavedState_ValidatorIndexCacheUpdated(t *testing.T) {
|
||||
resetFn := features.InitWithReset(&features.Flags{
|
||||
EnableStartOptimistic: true,
|
||||
EIP6110ValidatorIndexCache: true,
|
||||
EnableStartOptimistic: true,
|
||||
})
|
||||
defer resetFn()
|
||||
|
||||
genesis := util.NewBeaconBlock()
|
||||
genesis := util.NewBeaconBlockElectra()
|
||||
genesisRoot, err := genesis.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
finalizedSlot := params.BeaconConfig().SlotsPerEpoch*2 + 1
|
||||
headBlock := util.NewBeaconBlock()
|
||||
headBlock := util.NewBeaconBlockElectra()
|
||||
headBlock.Block.Slot = finalizedSlot
|
||||
headBlock.Block.ParentRoot = bytesutil.PadTo(genesisRoot[:], 32)
|
||||
headState, err := util.NewBeaconState()
|
||||
|
||||
@@ -58,10 +58,9 @@ func AreEth1DataEqual(a, b *ethpb.Eth1Data) bool {
|
||||
// votes to see if they match the eth1data.
|
||||
func Eth1DataHasEnoughSupport(beaconState state.ReadOnlyBeaconState, data *ethpb.Eth1Data) (bool, error) {
|
||||
voteCount := uint64(0)
|
||||
data = ethpb.CopyETH1Data(data)
|
||||
|
||||
for _, vote := range beaconState.Eth1DataVotes() {
|
||||
if AreEth1DataEqual(vote, data) {
|
||||
if AreEth1DataEqual(vote, data.Copy()) {
|
||||
voteCount++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ func TestProcessEth1Data_SetsCorrectly(t *testing.T) {
|
||||
if len(newETH1DataVotes) <= 1 {
|
||||
t.Error("Expected new ETH1 data votes to have length > 1")
|
||||
}
|
||||
if !proto.Equal(beaconState.Eth1Data(), ethpb.CopyETH1Data(b.Block.Body.Eth1Data)) {
|
||||
if !proto.Equal(beaconState.Eth1Data(), b.Block.Body.Eth1Data.Copy()) {
|
||||
t.Errorf(
|
||||
"Expected latest eth1 data to have been set to %v, received %v",
|
||||
b.Block.Body.Eth1Data,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
package blocks
|
||||
|
||||
var ProcessBLSToExecutionChange = processBLSToExecutionChange
|
||||
|
||||
var VerifyBlobCommitmentCount = verifyBlobCommitmentCount
|
||||
|
||||
@@ -2,11 +2,13 @@ package blocks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
@@ -200,13 +202,13 @@ func ValidatePayload(st state.BeaconState, payload interfaces.ExecutionData) err
|
||||
// block_hash=payload.block_hash,
|
||||
// transactions_root=hash_tree_root(payload.transactions),
|
||||
// )
|
||||
func ProcessPayload(st state.BeaconState, payload interfaces.ExecutionData) (state.BeaconState, error) {
|
||||
var err error
|
||||
if st.Version() >= version.Capella {
|
||||
st, err = ProcessWithdrawals(st, payload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process withdrawals")
|
||||
}
|
||||
func ProcessPayload(st state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) (state.BeaconState, error) {
|
||||
payload, err := body.Execution()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := verifyBlobCommitmentCount(body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ValidatePayloadWhenMergeCompletes(st, payload); err != nil {
|
||||
return nil, err
|
||||
@@ -220,70 +222,20 @@ func ProcessPayload(st state.BeaconState, payload interfaces.ExecutionData) (sta
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// ValidatePayloadHeaderWhenMergeCompletes validates the payload header when the merge completes.
|
||||
func ValidatePayloadHeaderWhenMergeCompletes(st state.BeaconState, header interfaces.ExecutionData) error {
|
||||
// Skip validation if the state is not merge compatible.
|
||||
complete, err := IsMergeTransitionComplete(st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !complete {
|
||||
func verifyBlobCommitmentCount(body interfaces.ReadOnlyBeaconBlockBody) error {
|
||||
if body.Version() < version.Deneb {
|
||||
return nil
|
||||
}
|
||||
// Validate current header's parent hash matches state header's block hash.
|
||||
h, err := st.LatestExecutionPayloadHeader()
|
||||
kzgs, err := body.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(header.ParentHash(), h.BlockHash()) {
|
||||
return ErrInvalidPayloadBlockHash
|
||||
if len(kzgs) > field_params.MaxBlobsPerBlock {
|
||||
return fmt.Errorf("too many kzg commitments in block: %d", len(kzgs))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePayloadHeader validates the payload header.
|
||||
func ValidatePayloadHeader(st state.BeaconState, header interfaces.ExecutionData) error {
|
||||
// Validate header's random mix matches with state in current epoch
|
||||
random, err := helpers.RandaoMix(st, time.CurrentEpoch(st))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(header.PrevRandao(), random) {
|
||||
return ErrInvalidPayloadPrevRandao
|
||||
}
|
||||
|
||||
// Validate header's timestamp matches with state in current slot.
|
||||
t, err := slots.ToTime(st.GenesisTime(), st.Slot())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if header.Timestamp() != uint64(t.Unix()) {
|
||||
return ErrInvalidPayloadTimeStamp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessPayloadHeader processes the payload header.
|
||||
func ProcessPayloadHeader(st state.BeaconState, header interfaces.ExecutionData) (state.BeaconState, error) {
|
||||
var err error
|
||||
if st.Version() >= version.Capella {
|
||||
st, err = ProcessWithdrawals(st, header)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process withdrawals")
|
||||
}
|
||||
}
|
||||
if err := ValidatePayloadHeaderWhenMergeCompletes(st, header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ValidatePayloadHeader(st, header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := st.SetLatestExecutionPayloadHeader(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// GetBlockPayloadHash returns the hash of the execution payload of the block
|
||||
func GetBlockPayloadHash(blk interfaces.ReadOnlyBeaconBlock) ([32]byte, error) {
|
||||
var payloadHash [32]byte
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package blocks_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
@@ -581,14 +583,18 @@ func Test_ProcessPayload(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
wrappedPayload, err := consensusblocks.WrappedExecutionPayload(tt.payload)
|
||||
body, err := consensusblocks.NewBeaconBlockBody(ðpb.BeaconBlockBodyBellatrix{
|
||||
ExecutionPayload: tt.payload,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
st, err := blocks.ProcessPayload(st, wrappedPayload)
|
||||
st, err := blocks.ProcessPayload(st, body)
|
||||
if err != nil {
|
||||
require.Equal(t, tt.err.Error(), err.Error())
|
||||
} else {
|
||||
require.Equal(t, tt.err, err)
|
||||
want, err := consensusblocks.PayloadToHeader(wrappedPayload)
|
||||
payload, err := body.Execution()
|
||||
require.NoError(t, err)
|
||||
want, err := consensusblocks.PayloadToHeader(payload)
|
||||
require.Equal(t, tt.err, err)
|
||||
h, err := st.LatestExecutionPayloadHeader()
|
||||
require.NoError(t, err)
|
||||
@@ -609,13 +615,15 @@ func Test_ProcessPayloadCapella(t *testing.T) {
|
||||
random, err := helpers.RandaoMix(st, time.CurrentEpoch(st))
|
||||
require.NoError(t, err)
|
||||
payload.PrevRandao = random
|
||||
wrapped, err := consensusblocks.WrappedExecutionPayloadCapella(payload)
|
||||
body, err := consensusblocks.NewBeaconBlockBody(ðpb.BeaconBlockBodyCapella{
|
||||
ExecutionPayload: payload,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = blocks.ProcessPayload(st, wrapped)
|
||||
_, err = blocks.ProcessPayload(st, body)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_ProcessPayloadHeader(t *testing.T) {
|
||||
func Test_ProcessPayload_Blinded(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
random, err := helpers.RandaoMix(st, time.CurrentEpoch(st))
|
||||
require.NoError(t, err)
|
||||
@@ -663,7 +671,13 @@ func Test_ProcessPayloadHeader(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st, err := blocks.ProcessPayloadHeader(st, tt.header)
|
||||
p, ok := tt.header.Proto().(*enginev1.ExecutionPayloadHeader)
|
||||
require.Equal(t, true, ok)
|
||||
body, err := consensusblocks.NewBeaconBlockBody(ðpb.BlindedBeaconBlockBodyBellatrix{
|
||||
ExecutionPayloadHeader: p,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
st, err := blocks.ProcessPayload(st, body)
|
||||
if err != nil {
|
||||
require.Equal(t, tt.err.Error(), err.Error())
|
||||
} else {
|
||||
@@ -728,7 +742,7 @@ func Test_ValidatePayloadHeader(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err = blocks.ValidatePayloadHeader(st, tt.header)
|
||||
err = blocks.ValidatePayload(st, tt.header)
|
||||
require.Equal(t, tt.err, err)
|
||||
})
|
||||
}
|
||||
@@ -785,7 +799,7 @@ func Test_ValidatePayloadHeaderWhenMergeCompletes(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err = blocks.ValidatePayloadHeaderWhenMergeCompletes(tt.state, tt.header)
|
||||
err = blocks.ValidatePayloadWhenMergeCompletes(tt.state, tt.header)
|
||||
require.Equal(t, tt.err, err)
|
||||
})
|
||||
}
|
||||
@@ -906,3 +920,15 @@ func emptyPayloadCapella() *enginev1.ExecutionPayloadCapella {
|
||||
Withdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyBlobCommitmentCount(t *testing.T) {
|
||||
b := ðpb.BeaconBlockDeneb{Body: ðpb.BeaconBlockBodyDeneb{}}
|
||||
rb, err := consensusblocks.NewBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, blocks.VerifyBlobCommitmentCount(rb.Body()))
|
||||
|
||||
b = ðpb.BeaconBlockDeneb{Body: ðpb.BeaconBlockBodyDeneb{BlobKzgCommitments: make([][]byte, fieldparams.MaxBlobsPerBlock+1)}}
|
||||
rb, err = consensusblocks.NewBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
require.ErrorContains(t, fmt.Sprintf("too many kzg commitments in block: %d", fieldparams.MaxBlobsPerBlock+1), blocks.VerifyBlobCommitmentCount(rb.Body()))
|
||||
}
|
||||
|
||||
@@ -308,7 +308,6 @@ func ProcessDepositRequests(ctx context.Context, beaconState state.BeaconState,
|
||||
defer span.End()
|
||||
|
||||
if len(requests) == 0 {
|
||||
log.Debug("ProcessDepositRequests: no deposit requests found")
|
||||
return beaconState, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package electra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
@@ -84,7 +85,7 @@ func ProcessRegistryUpdates(ctx context.Context, st state.BeaconState) error {
|
||||
var err error
|
||||
// exitQueueEpoch and churn arguments are not used in electra.
|
||||
st, _, err = validators.InitiateValidatorExit(ctx, st, idx, 0 /*exitQueueEpoch*/, 0 /*churn*/)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, validators.ErrValidatorAlreadyExited) {
|
||||
return fmt.Errorf("failed to initiate validator exit at index %d: %w", idx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,32 @@ func TestProcessRegistryUpdates(t *testing.T) {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Validators are exiting",
|
||||
state: func() state.BeaconState {
|
||||
base := ð.BeaconStateElectra{
|
||||
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
|
||||
FinalizedCheckpoint: ð.Checkpoint{Epoch: finalizedEpoch, Root: make([]byte, fieldparams.RootLength)},
|
||||
}
|
||||
for i := uint64(0); i < 10; i++ {
|
||||
base.Validators = append(base.Validators, ð.Validator{
|
||||
EffectiveBalance: params.BeaconConfig().EjectionBalance - 1,
|
||||
ExitEpoch: 10,
|
||||
WithdrawableEpoch: 20,
|
||||
})
|
||||
}
|
||||
st, err := state_native.InitializeFromProtoElectra(base)
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
// All validators should be exited
|
||||
for i, val := range st.Validators() {
|
||||
require.NotEqual(t, params.BeaconConfig().FarFutureEpoch, val.ExitEpoch, "failed to update exit epoch on validator %d", i)
|
||||
require.NotEqual(t, params.BeaconConfig().FarFutureEpoch, val.WithdrawableEpoch, "failed to update withdrawable epoch on validator %d", i)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -2,6 +2,7 @@ package electra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
@@ -21,29 +22,31 @@ var (
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
// # [Modified in Electra:EIP6110]
|
||||
// # Disable former deposit mechanism once all prior deposits are processed
|
||||
// eth1_deposit_index_limit = min(state.eth1_data.deposit_count, state.deposit_requests_start_index)
|
||||
// if state.eth1_deposit_index < eth1_deposit_index_limit:
|
||||
// assert len(body.deposits) == min(MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index)
|
||||
// else:
|
||||
// assert len(body.deposits) == 0
|
||||
// def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
|
||||
// # [Modified in Electra:EIP6110]
|
||||
// # Disable former deposit mechanism once all prior deposits are processed
|
||||
// eth1_deposit_index_limit = min(state.eth1_data.deposit_count, state.deposit_requests_start_index)
|
||||
// if state.eth1_deposit_index < eth1_deposit_index_limit:
|
||||
// assert len(body.deposits) == min(MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index)
|
||||
// else:
|
||||
// assert len(body.deposits) == 0
|
||||
//
|
||||
// def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
|
||||
// for operation in operations:
|
||||
// fn(state, operation)
|
||||
// def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
|
||||
// for operation in operations:
|
||||
// fn(state, operation)
|
||||
//
|
||||
// for_ops(body.proposer_slashings, process_proposer_slashing)
|
||||
// for_ops(body.attester_slashings, process_attester_slashing)
|
||||
// for_ops(body.attestations, process_attestation) # [Modified in Electra:EIP7549]
|
||||
// for_ops(body.deposits, process_deposit) # [Modified in Electra:EIP7251]
|
||||
// for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251]
|
||||
// for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
|
||||
// # [New in Electra:EIP7002:EIP7251]
|
||||
// for_ops(body.execution_payload.withdrawal_requests, process_execution_layer_withdrawal_request)
|
||||
// for_ops(body.execution_payload.deposit_requests, process_deposit_requests) # [New in Electra:EIP6110]
|
||||
// for_ops(body.consolidations, process_consolidation) # [New in Electra:EIP7251]
|
||||
// for_ops(body.proposer_slashings, process_proposer_slashing)
|
||||
// for_ops(body.attester_slashings, process_attester_slashing)
|
||||
// for_ops(body.attestations, process_attestation) # [Modified in Electra:EIP7549]
|
||||
// for_ops(body.deposits, process_deposit) # [Modified in Electra:EIP7251]
|
||||
// for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in Electra:EIP7251]
|
||||
// for_ops(body.bls_to_execution_changes, process_bls_to_execution_change)
|
||||
// for_ops(body.execution_payload.deposit_requests, process_deposit_request) # [New in Electra:EIP6110]
|
||||
// # [New in Electra:EIP7002:EIP7251]
|
||||
// for_ops(body.execution_payload.withdrawal_requests, process_withdrawal_request)
|
||||
// # [New in Electra:EIP7251]
|
||||
// for_ops(body.execution_payload.consolidation_requests, process_consolidation_request)
|
||||
|
||||
func ProcessOperations(
|
||||
ctx context.Context,
|
||||
st state.BeaconState,
|
||||
@@ -83,16 +86,16 @@ func ProcessOperations(
|
||||
if !ok {
|
||||
return nil, errors.New("could not cast execution data to electra execution data")
|
||||
}
|
||||
st, err = ProcessDepositRequests(ctx, st, exe.DepositRequests())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process deposit receipts")
|
||||
}
|
||||
st, err = ProcessWithdrawalRequests(ctx, st, exe.WithdrawalRequests())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution layer withdrawal requests")
|
||||
}
|
||||
|
||||
st, err = ProcessDepositRequests(ctx, st, exe.DepositRequests()) // TODO: EIP-6110 deposit changes.
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process deposit receipts")
|
||||
if err := ProcessConsolidationRequests(ctx, st, exe.ConsolidationRequests()); err != nil {
|
||||
return nil, fmt.Errorf("could not process consolidation requests: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Process consolidations from execution header.
|
||||
return st, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package electra_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
@@ -47,3 +51,68 @@ func TestVerifyOperationLengths_Electra(t *testing.T) {
|
||||
require.ErrorContains(t, "incorrect outstanding deposits in block body", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessEpoch_CanProcessElectra(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, params.BeaconConfig().MaxValidatorsPerCommittee)
|
||||
require.NoError(t, st.SetSlot(10*params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, st.SetDepositBalanceToConsume(100))
|
||||
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
|
||||
deps := make([]*ethpb.PendingBalanceDeposit, 20)
|
||||
for i := 0; i < len(deps); i += 1 {
|
||||
deps[i] = ðpb.PendingBalanceDeposit{
|
||||
Amount: uint64(amountAvailForProcessing) / 10,
|
||||
Index: primitives.ValidatorIndex(i),
|
||||
}
|
||||
}
|
||||
require.NoError(t, st.SetPendingBalanceDeposits(deps))
|
||||
require.NoError(t, st.SetPendingConsolidations([]*ethpb.PendingConsolidation{
|
||||
{
|
||||
SourceIndex: 2,
|
||||
TargetIndex: 3,
|
||||
},
|
||||
{
|
||||
SourceIndex: 0,
|
||||
TargetIndex: 1,
|
||||
},
|
||||
}))
|
||||
err := electra.ProcessEpoch(context.Background(), st)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), st.Slashings()[2], "Unexpected slashed balance")
|
||||
|
||||
b := st.Balances()
|
||||
require.Equal(t, params.BeaconConfig().MaxValidatorsPerCommittee, uint64(len(b)))
|
||||
require.Equal(t, uint64(44799839993), b[0])
|
||||
|
||||
s, err := st.InactivityScores()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MaxValidatorsPerCommittee, uint64(len(s)))
|
||||
|
||||
p, err := st.PreviousEpochParticipation()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MaxValidatorsPerCommittee, uint64(len(p)))
|
||||
|
||||
p, err = st.CurrentEpochParticipation()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MaxValidatorsPerCommittee, uint64(len(p)))
|
||||
|
||||
sc, err := st.CurrentSyncCommittee()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().SyncCommitteeSize, uint64(len(sc.Pubkeys)))
|
||||
|
||||
sc, err = st.NextSyncCommittee()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().SyncCommitteeSize, uint64(len(sc.Pubkeys)))
|
||||
|
||||
res, err := st.DepositBalanceToConsume()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Gwei(100), res)
|
||||
|
||||
// Half of the balance deposits should have been processed.
|
||||
remaining, err := st.PendingBalanceDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(remaining))
|
||||
|
||||
num, err := st.NumPendingConsolidations()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(2), num)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ type Validator struct {
|
||||
IsPrevEpochSourceAttester bool
|
||||
// IsPrevEpochTargetAttester is true if the validator attested previous epoch target.
|
||||
IsPrevEpochTargetAttester bool
|
||||
// IsHeadAttester is true if the validator attested head.
|
||||
// IsPrevEpochHeadAttester is true if the validator attested the previous epoch head.
|
||||
IsPrevEpochHeadAttester bool
|
||||
|
||||
// CurrentEpochEffectiveBalance is how much effective balance this validator has current epoch.
|
||||
|
||||
@@ -82,6 +82,47 @@ func AttestationCommittees(ctx context.Context, st state.ReadOnlyBeaconState, at
|
||||
return committees, nil
|
||||
}
|
||||
|
||||
// BeaconCommittees returns the list of all beacon committees for a given state at a given slot.
|
||||
func BeaconCommittees(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot) ([][]primitives.ValidatorIndex, error) {
|
||||
epoch := slots.ToEpoch(slot)
|
||||
activeCount, err := ActiveValidatorCount(ctx, state, epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute active validator count")
|
||||
}
|
||||
committeesPerSlot := SlotCommitteeCount(activeCount)
|
||||
seed, err := Seed(state, epoch, params.BeaconConfig().DomainBeaconAttester)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get seed")
|
||||
}
|
||||
|
||||
committees := make([][]primitives.ValidatorIndex, committeesPerSlot)
|
||||
var activeIndices []primitives.ValidatorIndex
|
||||
|
||||
for idx := primitives.CommitteeIndex(0); idx < primitives.CommitteeIndex(len(committees)); idx++ {
|
||||
committee, err := committeeCache.Committee(ctx, slot, seed, idx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not interface with committee cache")
|
||||
}
|
||||
if committee != nil {
|
||||
committees[idx] = committee
|
||||
continue
|
||||
}
|
||||
|
||||
if len(activeIndices) == 0 {
|
||||
activeIndices, err = ActiveValidatorIndices(ctx, state, epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get active indices")
|
||||
}
|
||||
}
|
||||
committee, err = BeaconCommittee(ctx, activeIndices, seed, slot, idx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute beacon committee")
|
||||
}
|
||||
committees[idx] = committee
|
||||
}
|
||||
return committees, nil
|
||||
}
|
||||
|
||||
// BeaconCommitteeFromState returns the crosslink committee of a given slot and committee index. This
|
||||
// is a spec implementation where state is used as an argument. In case of state retrieval
|
||||
// becomes expensive, consider using BeaconCommittee below.
|
||||
@@ -253,36 +294,22 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
|
||||
if err := verifyAssignmentEpoch(epoch, state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Retrieve active validator count for the specified epoch.
|
||||
activeValidatorCount, err := ActiveValidatorCount(ctx, state, epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Determine the number of committees per slot based on the number of active validator indices.
|
||||
numCommitteesPerSlot := SlotCommitteeCount(activeValidatorCount)
|
||||
|
||||
startSlot, err := slots.EpochStart(epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
assignments := make(map[primitives.ValidatorIndex]*CommitteeAssignment)
|
||||
vals := make(map[primitives.ValidatorIndex]struct{})
|
||||
for _, v := range validators {
|
||||
vals[v] = struct{}{}
|
||||
}
|
||||
|
||||
assignments := make(map[primitives.ValidatorIndex]*CommitteeAssignment)
|
||||
// Compute committee assignments for each slot in the epoch.
|
||||
for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ {
|
||||
// Compute committees for the current slot.
|
||||
for j := uint64(0); j < numCommitteesPerSlot; j++ {
|
||||
committee, err := BeaconCommitteeFromState(ctx, state, slot, primitives.CommitteeIndex(j))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
committees, err := BeaconCommittees(ctx, state, slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute beacon committees")
|
||||
}
|
||||
for j, committee := range committees {
|
||||
for _, vIndex := range committee {
|
||||
if _, ok := vals[vIndex]; !ok { // Skip if the validator is not in the provided validators slice.
|
||||
continue
|
||||
@@ -296,7 +323,6 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return assignments, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
@@ -749,3 +750,27 @@ func TestAttestationCommittees(t *testing.T) {
|
||||
assert.Equal(t, params.BeaconConfig().TargetCommitteeSize, uint64(len(committees[1])))
|
||||
})
|
||||
}
|
||||
|
||||
func TestBeaconCommittees(t *testing.T) {
|
||||
prevConfig := params.BeaconConfig().Copy()
|
||||
defer params.OverrideBeaconConfig(prevConfig)
|
||||
c := params.BeaconConfig().Copy()
|
||||
c.MinGenesisActiveValidatorCount = 128
|
||||
c.SlotsPerEpoch = 4
|
||||
c.TargetCommitteeSize = 16
|
||||
params.OverrideBeaconConfig(c)
|
||||
|
||||
state, _ := util.DeterministicGenesisState(t, 256)
|
||||
|
||||
activeCount, err := helpers.ActiveValidatorCount(context.Background(), state, 0)
|
||||
require.NoError(t, err)
|
||||
committeesPerSlot := helpers.SlotCommitteeCount(activeCount)
|
||||
committees, err := helpers.BeaconCommittees(context.Background(), state, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, committeesPerSlot, uint64(len(committees)))
|
||||
for idx := primitives.CommitteeIndex(0); idx < primitives.CommitteeIndex(len(committees)); idx++ {
|
||||
committee, err := helpers.BeaconCommitteeFromState(context.Background(), state, 0, idx)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, committees[idx], committee)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestExecuteAltairStateTransitionNoVerify_FullProcess(t *testing.T) {
|
||||
}
|
||||
indices, err := altair.NextSyncCommitteeIndices(context.Background(), beaconState)
|
||||
require.NoError(t, err)
|
||||
h := ethpb.CopyBeaconBlockHeader(beaconState.LatestBlockHeader())
|
||||
h := beaconState.LatestBlockHeader().Copy()
|
||||
prevStateRoot, err := beaconState.HashTreeRoot(context.Background())
|
||||
require.NoError(t, err)
|
||||
h.StateRoot = prevStateRoot[:]
|
||||
@@ -157,7 +157,7 @@ func TestExecuteAltairStateTransitionNoVerifySignature_CouldNotVerifyStateRoot(t
|
||||
}
|
||||
indices, err := altair.NextSyncCommitteeIndices(context.Background(), beaconState)
|
||||
require.NoError(t, err)
|
||||
h := ethpb.CopyBeaconBlockHeader(beaconState.LatestBlockHeader())
|
||||
h := beaconState.LatestBlockHeader().Copy()
|
||||
prevStateRoot, err := beaconState.HashTreeRoot(context.Background())
|
||||
require.NoError(t, err)
|
||||
h.StateRoot = prevStateRoot[:]
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestExecuteBellatrixStateTransitionNoVerify_FullProcess(t *testing.T) {
|
||||
}
|
||||
indices, err := altair.NextSyncCommitteeIndices(context.Background(), beaconState)
|
||||
require.NoError(t, err)
|
||||
h := ethpb.CopyBeaconBlockHeader(beaconState.LatestBlockHeader())
|
||||
h := beaconState.LatestBlockHeader().Copy()
|
||||
prevStateRoot, err := beaconState.HashTreeRoot(context.Background())
|
||||
require.NoError(t, err)
|
||||
h.StateRoot = prevStateRoot[:]
|
||||
@@ -159,7 +159,7 @@ func TestExecuteBellatrixStateTransitionNoVerifySignature_CouldNotVerifyStateRoo
|
||||
}
|
||||
indices, err := altair.NextSyncCommitteeIndices(context.Background(), beaconState)
|
||||
require.NoError(t, err)
|
||||
h := ethpb.CopyBeaconBlockHeader(beaconState.LatestBlockHeader())
|
||||
h := beaconState.LatestBlockHeader().Copy()
|
||||
prevStateRoot, err := beaconState.HashTreeRoot(context.Background())
|
||||
require.NoError(t, err)
|
||||
h.StateRoot = prevStateRoot[:]
|
||||
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
type customProcessingFn func(context.Context, state.BeaconState) error
|
||||
|
||||
// ExecuteStateTransition defines the procedure for a state transition function.
|
||||
//
|
||||
// Note: This method differs from the spec pseudocode as it uses a batch signature verification.
|
||||
@@ -173,18 +175,7 @@ func ProcessSlotsIfPossible(ctx context.Context, state state.BeaconState, target
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// ProcessSlots process through skip slots and apply epoch transition when it's needed
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
//
|
||||
// def process_slots(state: BeaconState, slot: Slot) -> None:
|
||||
// assert state.slot < slot
|
||||
// while state.slot < slot:
|
||||
// process_slot(state)
|
||||
// # Process epoch on the start slot of the next epoch
|
||||
// if (state.slot + 1) % SLOTS_PER_EPOCH == 0:
|
||||
// process_epoch(state)
|
||||
// state.slot = Slot(state.slot + 1)
|
||||
// ProcessSlots includes core slot processing as well as a cache
|
||||
func ProcessSlots(ctx context.Context, state state.BeaconState, slot primitives.Slot) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "core.state.ProcessSlots")
|
||||
defer span.End()
|
||||
@@ -231,42 +222,63 @@ func ProcessSlots(ctx context.Context, state state.BeaconState, slot primitives.
|
||||
defer func() {
|
||||
SkipSlotCache.MarkNotInProgress(key)
|
||||
}()
|
||||
state, err = ProcessSlotsCore(ctx, span, state, slot, cacheBestBeaconStateOnErrFn(highestSlot, key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if highestSlot < state.Slot() {
|
||||
SkipSlotCache.Put(ctx, key, state)
|
||||
}
|
||||
|
||||
for state.Slot() < slot {
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func cacheBestBeaconStateOnErrFn(highestSlot primitives.Slot, key [32]byte) customProcessingFn {
|
||||
return func(ctx context.Context, state state.BeaconState) error {
|
||||
if ctx.Err() != nil {
|
||||
tracing.AnnotateError(span, ctx.Err())
|
||||
// Cache last best value.
|
||||
if highestSlot < state.Slot() {
|
||||
if SkipSlotCache.Put(ctx, key, state); err != nil {
|
||||
log.WithError(err).Error("Failed to put skip slot cache value")
|
||||
}
|
||||
SkipSlotCache.Put(ctx, key, state)
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessSlotsCore process through skip slots and apply epoch transition when it's needed
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
//
|
||||
// def process_slots(state: BeaconState, slot: Slot) -> None:
|
||||
// assert state.slot < slot
|
||||
// while state.slot < slot:
|
||||
// process_slot(state)
|
||||
// # Process epoch on the start slot of the next epoch
|
||||
// if (state.slot + 1) % SLOTS_PER_EPOCH == 0:
|
||||
// process_epoch(state)
|
||||
// state.slot = Slot(state.slot + 1)
|
||||
func ProcessSlotsCore(ctx context.Context, span *trace.Span, state state.BeaconState, slot primitives.Slot, fn customProcessingFn) (state.BeaconState, error) {
|
||||
var err error
|
||||
for state.Slot() < slot {
|
||||
if fn != nil {
|
||||
if err = fn(ctx, state); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
state, err = ProcessSlot(ctx, state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, errors.Wrap(err, "could not process slot")
|
||||
}
|
||||
if time.CanProcessEpoch(state) {
|
||||
if state.Version() == version.Phase0 {
|
||||
state, err = ProcessEpochPrecompute(ctx, state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, errors.Wrap(err, "could not process epoch with optimizations")
|
||||
}
|
||||
} else if state.Version() <= version.Deneb {
|
||||
if err = altair.ProcessEpoch(ctx, state); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not process %s epoch", version.String(state.Version())))
|
||||
}
|
||||
} else {
|
||||
if err = electra.ProcessEpoch(ctx, state); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not process %s epoch", version.String(state.Version())))
|
||||
}
|
||||
}
|
||||
|
||||
state, err = ProcessEpoch(ctx, state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := state.SetSlot(state.Slot() + 1); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, errors.Wrap(err, "failed to increment state slot")
|
||||
@@ -278,25 +290,46 @@ func ProcessSlots(ctx context.Context, state state.BeaconState, slot primitives.
|
||||
return nil, errors.Wrap(err, "failed to upgrade state")
|
||||
}
|
||||
}
|
||||
|
||||
if highestSlot < state.Slot() {
|
||||
SkipSlotCache.Put(ctx, key, state)
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// ProcessEpoch is a wrapper on fork specific epoch processing
|
||||
func ProcessEpoch(ctx context.Context, state state.BeaconState) (state.BeaconState, error) {
|
||||
var err error
|
||||
if time.CanProcessEpoch(state) {
|
||||
if state.Version() == version.Electra {
|
||||
if err = electra.ProcessEpoch(ctx, state); err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not process %s epoch", version.String(state.Version())))
|
||||
}
|
||||
} else if state.Version() >= version.Altair {
|
||||
if err = altair.ProcessEpoch(ctx, state); err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("could not process %s epoch", version.String(state.Version())))
|
||||
}
|
||||
} else {
|
||||
state, err = ProcessEpochPrecompute(ctx, state)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process epoch with optimizations")
|
||||
}
|
||||
}
|
||||
}
|
||||
return state, err
|
||||
}
|
||||
|
||||
// UpgradeState upgrades the state to the next version if possible.
|
||||
func UpgradeState(ctx context.Context, state state.BeaconState) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "core.state.UpgradeState")
|
||||
defer span.End()
|
||||
|
||||
var err error
|
||||
upgraded := false
|
||||
|
||||
if time.CanUpgradeToAltair(state.Slot()) {
|
||||
state, err = altair.UpgradeToAltair(ctx, state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if time.CanUpgradeToBellatrix(state.Slot()) {
|
||||
@@ -305,6 +338,7 @@ func UpgradeState(ctx context.Context, state state.BeaconState) (state.BeaconSta
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if time.CanUpgradeToCapella(state.Slot()) {
|
||||
@@ -313,6 +347,7 @@ func UpgradeState(ctx context.Context, state state.BeaconState) (state.BeaconSta
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if time.CanUpgradeToDeneb(state.Slot()) {
|
||||
@@ -321,6 +356,7 @@ func UpgradeState(ctx context.Context, state state.BeaconState) (state.BeaconSta
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if time.CanUpgradeToElectra(state.Slot()) {
|
||||
@@ -329,7 +365,13 @@ func UpgradeState(ctx context.Context, state state.BeaconState) (state.BeaconSta
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if upgraded {
|
||||
log.Debugf("upgraded state to %s", version.String(state.Version()))
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition/interop"
|
||||
v "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
@@ -328,20 +327,18 @@ func ProcessBlockForStateRoot(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if blk.IsBlinded() {
|
||||
state, err = b.ProcessPayloadHeader(state, executionData)
|
||||
} else {
|
||||
state, err = b.ProcessPayload(state, executionData)
|
||||
if state.Version() >= version.Capella {
|
||||
state, err = b.ProcessWithdrawals(state, executionData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process withdrawals")
|
||||
}
|
||||
}
|
||||
state, err = b.ProcessPayload(state, blk.Body())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution data")
|
||||
}
|
||||
}
|
||||
|
||||
if err := VerifyBlobCommitmentCount(blk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
randaoReveal := signed.Block().Body().RandaoReveal()
|
||||
state, err = b.ProcessRandaoNoVerify(state, randaoReveal[:])
|
||||
if err != nil {
|
||||
@@ -377,20 +374,6 @@ func ProcessBlockForStateRoot(
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func VerifyBlobCommitmentCount(blk interfaces.ReadOnlyBeaconBlock) error {
|
||||
if blk.Version() < version.Deneb {
|
||||
return nil
|
||||
}
|
||||
kzgs, err := blk.Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(kzgs) > field_params.MaxBlobsPerBlock {
|
||||
return fmt.Errorf("too many kzg commitments in block: %d", len(kzgs))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This calls altair block operations.
|
||||
func altairOperations(
|
||||
ctx context.Context,
|
||||
|
||||
@@ -2,13 +2,11 @@ package transition_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
|
||||
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
@@ -212,15 +210,3 @@ func TestProcessBlockDifferentVersion(t *testing.T) {
|
||||
_, _, err = transition.ProcessBlockNoVerifyAnySig(context.Background(), beaconState, wsb)
|
||||
require.ErrorContains(t, "state and block are different version. 0 != 1", err)
|
||||
}
|
||||
|
||||
func TestVerifyBlobCommitmentCount(t *testing.T) {
|
||||
b := ðpb.BeaconBlockDeneb{Body: ðpb.BeaconBlockBodyDeneb{}}
|
||||
rb, err := blocks.NewBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, transition.VerifyBlobCommitmentCount(rb))
|
||||
|
||||
b = ðpb.BeaconBlockDeneb{Body: ðpb.BeaconBlockBodyDeneb{BlobKzgCommitments: make([][]byte, field_params.MaxBlobsPerBlock+1)}}
|
||||
rb, err = blocks.NewBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
require.ErrorContains(t, fmt.Sprintf("too many kzg commitments in block: %d", field_params.MaxBlobsPerBlock+1), transition.VerifyBlobCommitmentCount(rb))
|
||||
}
|
||||
|
||||
@@ -68,11 +68,11 @@ func isSSZStorageFormat(obj interface{}) bool {
|
||||
return true
|
||||
case *ethpb.BeaconBlock:
|
||||
return true
|
||||
case *ethpb.Attestation:
|
||||
case *ethpb.Attestation, *ethpb.AttestationElectra:
|
||||
return true
|
||||
case *ethpb.Deposit:
|
||||
return true
|
||||
case *ethpb.AttesterSlashing:
|
||||
case *ethpb.AttesterSlashing, *ethpb.AttesterSlashingElectra:
|
||||
return true
|
||||
case *ethpb.ProposerSlashing:
|
||||
return true
|
||||
|
||||
@@ -170,7 +170,7 @@ func (s *Service) NewPayload(ctx context.Context, payload interfaces.ExecutionDa
|
||||
case *pb.ExecutionPayloadElectra:
|
||||
payloadPb, ok := payload.Proto().(*pb.ExecutionPayloadElectra)
|
||||
if !ok {
|
||||
return nil, errors.New("execution data must be a Deneb execution payload")
|
||||
return nil, errors.New("execution data must be a Electra execution payload")
|
||||
}
|
||||
err := s.rpcClient.CallContext(ctx, result, NewPayloadMethodV4, payloadPb, versionedHashes, parentBlockRoot)
|
||||
if err != nil {
|
||||
@@ -612,27 +612,32 @@ func fullPayloadFromPayloadBody(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cr, err := pb.JsonConsolidationRequestsToProto(body.ConsolidationRequests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blocks.WrappedExecutionPayloadElectra(
|
||||
&pb.ExecutionPayloadElectra{
|
||||
ParentHash: header.ParentHash(),
|
||||
FeeRecipient: header.FeeRecipient(),
|
||||
StateRoot: header.StateRoot(),
|
||||
ReceiptsRoot: header.ReceiptsRoot(),
|
||||
LogsBloom: header.LogsBloom(),
|
||||
PrevRandao: header.PrevRandao(),
|
||||
BlockNumber: header.BlockNumber(),
|
||||
GasLimit: header.GasLimit(),
|
||||
GasUsed: header.GasUsed(),
|
||||
Timestamp: header.Timestamp(),
|
||||
ExtraData: header.ExtraData(),
|
||||
BaseFeePerGas: header.BaseFeePerGas(),
|
||||
BlockHash: header.BlockHash(),
|
||||
Transactions: pb.RecastHexutilByteSlice(body.Transactions),
|
||||
Withdrawals: body.Withdrawals,
|
||||
ExcessBlobGas: ebg,
|
||||
BlobGasUsed: bgu,
|
||||
DepositRequests: dr,
|
||||
WithdrawalRequests: wr,
|
||||
ParentHash: header.ParentHash(),
|
||||
FeeRecipient: header.FeeRecipient(),
|
||||
StateRoot: header.StateRoot(),
|
||||
ReceiptsRoot: header.ReceiptsRoot(),
|
||||
LogsBloom: header.LogsBloom(),
|
||||
PrevRandao: header.PrevRandao(),
|
||||
BlockNumber: header.BlockNumber(),
|
||||
GasLimit: header.GasLimit(),
|
||||
GasUsed: header.GasUsed(),
|
||||
Timestamp: header.Timestamp(),
|
||||
ExtraData: header.ExtraData(),
|
||||
BaseFeePerGas: header.BaseFeePerGas(),
|
||||
BlockHash: header.BlockHash(),
|
||||
Transactions: pb.RecastHexutilByteSlice(body.Transactions),
|
||||
Withdrawals: body.Withdrawals,
|
||||
ExcessBlobGas: ebg,
|
||||
BlobGasUsed: bgu,
|
||||
DepositRequests: dr,
|
||||
WithdrawalRequests: wr,
|
||||
ConsolidationRequests: cr,
|
||||
}) // We can't get the block value and don't care about the block value for this instance
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown execution block version for payload %d", bVersion)
|
||||
|
||||
@@ -374,6 +374,17 @@ func TestClient_HTTP(t *testing.T) {
|
||||
require.DeepEqual(t, proofs, resp.BlobsBundle.Proofs)
|
||||
blobs := [][]byte{bytesutil.PadTo([]byte("a"), fieldparams.BlobLength), bytesutil.PadTo([]byte("b"), fieldparams.BlobLength)}
|
||||
require.DeepEqual(t, blobs, resp.BlobsBundle.Blobs)
|
||||
ede, ok := resp.ExecutionData.(interfaces.ExecutionDataElectra)
|
||||
require.Equal(t, true, ok)
|
||||
require.NotNil(t, ede.WithdrawalRequests())
|
||||
wrequestsNotOverMax := len(ede.WithdrawalRequests()) <= int(params.BeaconConfig().MaxWithdrawalRequestsPerPayload)
|
||||
require.Equal(t, true, wrequestsNotOverMax)
|
||||
require.NotNil(t, ede.DepositRequests())
|
||||
drequestsNotOverMax := len(ede.DepositRequests()) <= int(params.BeaconConfig().MaxDepositRequestsPerPayload)
|
||||
require.Equal(t, true, drequestsNotOverMax)
|
||||
require.NotNil(t, ede.ConsolidationRequests())
|
||||
consolidationsNotOverMax := len(ede.ConsolidationRequests()) <= int(params.BeaconConfig().MaxConsolidationsRequestsPerPayload)
|
||||
require.Equal(t, true, consolidationsNotOverMax)
|
||||
})
|
||||
t.Run(ForkchoiceUpdatedMethod+" VALID status", func(t *testing.T) {
|
||||
forkChoiceState := &pb.ForkchoiceState{
|
||||
@@ -1533,6 +1544,20 @@ func fixturesStruct() *payloadFixtures {
|
||||
Index: &idx,
|
||||
}
|
||||
}
|
||||
consolidationRequests := make([]pb.ConsolidationRequestV1, 1)
|
||||
for i := range consolidationRequests {
|
||||
address := &common.Address{}
|
||||
address.SetBytes([]byte{0, 0, byte(i)})
|
||||
sPubkey := pb.BlsPubkey{}
|
||||
copy(sPubkey[:], []byte{0, byte(i)})
|
||||
tPubkey := pb.BlsPubkey{}
|
||||
copy(tPubkey[:], []byte{0, byte(i)})
|
||||
consolidationRequests[i] = pb.ConsolidationRequestV1{
|
||||
SourceAddress: address,
|
||||
SourcePubkey: &sPubkey,
|
||||
TargetPubkey: &tPubkey,
|
||||
}
|
||||
}
|
||||
dr, err := pb.JsonDepositRequestsToProto(depositRequests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -1541,26 +1566,31 @@ func fixturesStruct() *payloadFixtures {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cr, err := pb.JsonConsolidationRequestsToProto(consolidationRequests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
executionPayloadFixtureElectra := &pb.ExecutionPayloadElectra{
|
||||
ParentHash: foo[:],
|
||||
FeeRecipient: bar,
|
||||
StateRoot: foo[:],
|
||||
ReceiptsRoot: foo[:],
|
||||
LogsBloom: baz,
|
||||
PrevRandao: foo[:],
|
||||
BlockNumber: 1,
|
||||
GasLimit: 1,
|
||||
GasUsed: 1,
|
||||
Timestamp: 1,
|
||||
ExtraData: foo[:],
|
||||
BaseFeePerGas: bytesutil.PadTo(baseFeePerGas.Bytes(), fieldparams.RootLength),
|
||||
BlockHash: foo[:],
|
||||
Transactions: [][]byte{foo[:]},
|
||||
Withdrawals: []*pb.Withdrawal{},
|
||||
BlobGasUsed: 2,
|
||||
ExcessBlobGas: 3,
|
||||
DepositRequests: dr,
|
||||
WithdrawalRequests: wr,
|
||||
ParentHash: foo[:],
|
||||
FeeRecipient: bar,
|
||||
StateRoot: foo[:],
|
||||
ReceiptsRoot: foo[:],
|
||||
LogsBloom: baz,
|
||||
PrevRandao: foo[:],
|
||||
BlockNumber: 1,
|
||||
GasLimit: 1,
|
||||
GasUsed: 1,
|
||||
Timestamp: 1,
|
||||
ExtraData: foo[:],
|
||||
BaseFeePerGas: bytesutil.PadTo(baseFeePerGas.Bytes(), fieldparams.RootLength),
|
||||
BlockHash: foo[:],
|
||||
Transactions: [][]byte{foo[:]},
|
||||
Withdrawals: []*pb.Withdrawal{},
|
||||
BlobGasUsed: 2,
|
||||
ExcessBlobGas: 3,
|
||||
DepositRequests: dr,
|
||||
WithdrawalRequests: wr,
|
||||
ConsolidationRequests: cr,
|
||||
}
|
||||
hexUint := hexutil.Uint64(1)
|
||||
executionPayloadWithValueFixtureCapella := &pb.GetPayloadV2ResponseJson{
|
||||
@@ -1614,24 +1644,25 @@ func fixturesStruct() *payloadFixtures {
|
||||
executionPayloadWithValueFixtureElectra := &pb.GetPayloadV4ResponseJson{
|
||||
ShouldOverrideBuilder: true,
|
||||
ExecutionPayload: &pb.ExecutionPayloadElectraJSON{
|
||||
ParentHash: &common.Hash{'a'},
|
||||
FeeRecipient: &common.Address{'b'},
|
||||
StateRoot: &common.Hash{'c'},
|
||||
ReceiptsRoot: &common.Hash{'d'},
|
||||
LogsBloom: &hexutil.Bytes{'e'},
|
||||
PrevRandao: &common.Hash{'f'},
|
||||
BaseFeePerGas: "0x123",
|
||||
BlockHash: &common.Hash{'g'},
|
||||
Transactions: []hexutil.Bytes{{'h'}},
|
||||
Withdrawals: []*pb.Withdrawal{},
|
||||
BlockNumber: &hexUint,
|
||||
GasLimit: &hexUint,
|
||||
GasUsed: &hexUint,
|
||||
Timestamp: &hexUint,
|
||||
BlobGasUsed: &bgu,
|
||||
ExcessBlobGas: &ebg,
|
||||
DepositRequests: depositRequests,
|
||||
WithdrawalRequests: withdrawalRequests,
|
||||
ParentHash: &common.Hash{'a'},
|
||||
FeeRecipient: &common.Address{'b'},
|
||||
StateRoot: &common.Hash{'c'},
|
||||
ReceiptsRoot: &common.Hash{'d'},
|
||||
LogsBloom: &hexutil.Bytes{'e'},
|
||||
PrevRandao: &common.Hash{'f'},
|
||||
BaseFeePerGas: "0x123",
|
||||
BlockHash: &common.Hash{'g'},
|
||||
Transactions: []hexutil.Bytes{{'h'}},
|
||||
Withdrawals: []*pb.Withdrawal{},
|
||||
BlockNumber: &hexUint,
|
||||
GasLimit: &hexUint,
|
||||
GasUsed: &hexUint,
|
||||
Timestamp: &hexUint,
|
||||
BlobGasUsed: &bgu,
|
||||
ExcessBlobGas: &ebg,
|
||||
DepositRequests: depositRequests,
|
||||
WithdrawalRequests: withdrawalRequests,
|
||||
ConsolidationRequests: consolidationRequests,
|
||||
},
|
||||
BlockValue: "0x11fffffffff",
|
||||
BlobsBundle: &pb.BlobBundleJSON{
|
||||
|
||||
@@ -84,11 +84,15 @@ func (s *mockEngine) callCount(method string) int {
|
||||
}
|
||||
|
||||
func mockParseUintList(t *testing.T, data json.RawMessage) []uint64 {
|
||||
var list []uint64
|
||||
var list []string
|
||||
if err := json.Unmarshal(data, &list); err != nil {
|
||||
t.Fatalf("failed to parse uint list: %v", err)
|
||||
}
|
||||
return list
|
||||
uints := make([]uint64, len(list))
|
||||
for i, u := range list {
|
||||
uints[i] = hexutil.MustDecodeUint64(u)
|
||||
}
|
||||
return uints
|
||||
}
|
||||
|
||||
func mockParseHexByteList(t *testing.T, data json.RawMessage) []hexutil.Bytes {
|
||||
@@ -117,7 +121,7 @@ func TestParseRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cases := []struct {
|
||||
method string
|
||||
uintArgs []uint64
|
||||
hexArgs []string // uint64 as hex
|
||||
byteArgs []hexutil.Bytes
|
||||
}{
|
||||
{
|
||||
@@ -135,26 +139,28 @@ func TestParseRequest(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
method: GetPayloadBodiesByRangeV1,
|
||||
uintArgs: []uint64{0, 1},
|
||||
method: GetPayloadBodiesByRangeV1,
|
||||
hexArgs: []string{hexutil.EncodeUint64(0), hexutil.EncodeUint64(1)},
|
||||
},
|
||||
{
|
||||
method: GetPayloadBodiesByRangeV2,
|
||||
uintArgs: []uint64{math.MaxUint64, 1},
|
||||
method: GetPayloadBodiesByRangeV2,
|
||||
hexArgs: []string{hexutil.EncodeUint64(math.MaxUint64), hexutil.EncodeUint64(1)},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.method, func(t *testing.T) {
|
||||
cli, srv := newMockEngine(t)
|
||||
srv.register(c.method, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
|
||||
srv.register(c.method, func(msg *jsonrpcMessage, w http.ResponseWriter, _ *http.Request) {
|
||||
require.Equal(t, c.method, msg.Method)
|
||||
nr := uint64(len(c.byteArgs))
|
||||
if len(c.byteArgs) > 0 {
|
||||
require.DeepEqual(t, c.byteArgs, mockParseHexByteList(t, msg.Params))
|
||||
}
|
||||
if len(c.uintArgs) > 0 {
|
||||
if len(c.hexArgs) > 0 {
|
||||
rang := mockParseUintList(t, msg.Params)
|
||||
require.DeepEqual(t, c.uintArgs, rang)
|
||||
for i, r := range rang {
|
||||
require.Equal(t, c.hexArgs[i], hexutil.EncodeUint64(r))
|
||||
}
|
||||
nr = rang[1]
|
||||
}
|
||||
mockWriteResult(t, w, msg, make([]*pb.ExecutionPayloadBody, nr))
|
||||
@@ -165,18 +171,18 @@ func TestParseRequest(t *testing.T) {
|
||||
if len(c.byteArgs) > 0 {
|
||||
args = []interface{}{c.byteArgs}
|
||||
}
|
||||
if len(c.uintArgs) > 0 {
|
||||
args = make([]interface{}, len(c.uintArgs))
|
||||
for i := range c.uintArgs {
|
||||
args[i] = c.uintArgs[i]
|
||||
if len(c.hexArgs) > 0 {
|
||||
args = make([]interface{}, len(c.hexArgs))
|
||||
for i := range c.hexArgs {
|
||||
args[i] = c.hexArgs[i]
|
||||
}
|
||||
}
|
||||
require.NoError(t, cli.CallContext(ctx, &result, c.method, args...))
|
||||
if len(c.byteArgs) > 0 {
|
||||
require.Equal(t, len(c.byteArgs), len(result))
|
||||
}
|
||||
if len(c.uintArgs) > 0 {
|
||||
require.Equal(t, int(c.uintArgs[1]), len(result))
|
||||
if len(c.hexArgs) > 0 {
|
||||
require.Equal(t, int(hexutil.MustDecodeUint64(c.hexArgs[1])), len(result))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -203,7 +209,7 @@ func TestCallCount(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
t.Run(c.method, func(t *testing.T) {
|
||||
cli, srv := newMockEngine(t)
|
||||
srv.register(c.method, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
|
||||
srv.register(c.method, func(msg *jsonrpcMessage, w http.ResponseWriter, _ *http.Request) {
|
||||
mockWriteResult(t, w, msg, nil)
|
||||
})
|
||||
for i := 0; i < c.count; i++ {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
@@ -160,7 +161,7 @@ func computeRanges(hbns []hashBlockNumber) []byRangeReq {
|
||||
|
||||
func (r *blindedBlockReconstructor) requestBodiesByRange(ctx context.Context, client RPCClient, method string, req byRangeReq) error {
|
||||
result := make([]*pb.ExecutionPayloadBody, 0)
|
||||
if err := client.CallContext(ctx, &result, method, req.start, req.count); err != nil {
|
||||
if err := client.CallContext(ctx, &result, method, hexutil.EncodeUint64(req.start), hexutil.EncodeUint64(req.count)); err != nil {
|
||||
return err
|
||||
}
|
||||
if uint64(len(result)) != req.count {
|
||||
|
||||
@@ -68,6 +68,7 @@ func payloadToBody(t *testing.T, ed interfaces.ExecutionData) *pb.ExecutionPaylo
|
||||
if isElectra {
|
||||
body.DepositRequests = pb.ProtoDepositRequestsToJson(eed.DepositRequests())
|
||||
body.WithdrawalRequests = pb.ProtoWithdrawalRequestsToJson(eed.WithdrawalRequests())
|
||||
body.ConsolidationRequests = pb.ProtoConsolidationRequestsToJson(eed.ConsolidationRequests())
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
@@ -676,3 +676,18 @@ func (f *ForkChoice) TargetRootForEpoch(root [32]byte, epoch primitives.Epoch) (
|
||||
}
|
||||
return f.TargetRootForEpoch(targetNode.root, epoch)
|
||||
}
|
||||
|
||||
// ParentRoot returns the block root of the parent node if it is in forkchoice.
|
||||
// The exception is for the finalized checkpoint root which we return the zero
|
||||
// hash.
|
||||
func (f *ForkChoice) ParentRoot(root [32]byte) ([32]byte, error) {
|
||||
n, ok := f.store.nodeByRoot[root]
|
||||
if !ok || n == nil {
|
||||
return [32]byte{}, ErrNilNode
|
||||
}
|
||||
// Return the zero hash for the tree root
|
||||
if n.parent == nil {
|
||||
return [32]byte{}, nil
|
||||
}
|
||||
return n.parent.root, nil
|
||||
}
|
||||
|
||||
@@ -861,3 +861,29 @@ func TestForkChoiceSlot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Slot(3), slot)
|
||||
}
|
||||
|
||||
func TestForkchoiceParentRoot(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := context.Background()
|
||||
root1 := [32]byte{'a'}
|
||||
st, root, err := prepareForkchoiceState(ctx, 3, root1, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, root))
|
||||
|
||||
root2 := [32]byte{'b'}
|
||||
st, root, err = prepareForkchoiceState(ctx, 3, root2, root1, [32]byte{'A'}, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, root))
|
||||
|
||||
root, err = f.ParentRoot(root2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, root1, root)
|
||||
|
||||
_, err = f.ParentRoot([32]byte{'c'})
|
||||
require.ErrorIs(t, err, ErrNilNode)
|
||||
|
||||
zeroHash := [32]byte{}
|
||||
root, err = f.ParentRoot(zeroHash)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, zeroHash, root)
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ type FastGetter interface {
|
||||
TargetRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
|
||||
UnrealizedJustifiedPayloadBlockHash() [32]byte
|
||||
Weight(root [32]byte) (uint64, error)
|
||||
ParentRoot(root [32]byte) ([32]byte, error)
|
||||
}
|
||||
|
||||
// Setter allows to set forkchoice information
|
||||
|
||||
@@ -169,3 +169,10 @@ func (ro *ROForkChoice) TargetRootForEpoch(root [32]byte, epoch primitives.Epoch
|
||||
defer ro.l.RUnlock()
|
||||
return ro.getter.TargetRootForEpoch(root, epoch)
|
||||
}
|
||||
|
||||
// ParentRoot delegates to the underlying forkchoice call, under a lock.
|
||||
func (ro *ROForkChoice) ParentRoot(root [32]byte) ([32]byte, error) {
|
||||
ro.l.RLock()
|
||||
defer ro.l.RUnlock()
|
||||
return ro.getter.ParentRoot(root)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
slotCalled
|
||||
lastRootCalled
|
||||
targetRootForEpochCalled
|
||||
parentRootCalled
|
||||
)
|
||||
|
||||
func _discard(t *testing.T, e error) {
|
||||
@@ -291,3 +292,8 @@ func (ro *mockROForkchoice) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) (
|
||||
ro.calls = append(ro.calls, targetRootForEpochCalled)
|
||||
return [32]byte{}, nil
|
||||
}
|
||||
|
||||
func (ro *mockROForkchoice) ParentRoot(_ [32]byte) ([32]byte, error) {
|
||||
ro.calls = append(ro.calls, parentRootCalled)
|
||||
return [32]byte{}, nil
|
||||
}
|
||||
|
||||
@@ -73,6 +73,21 @@ func configureBuilderCircuitBreaker(cliCtx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cliCtx.IsSet(flags.MinBuilderBid.Name) {
|
||||
c := params.BeaconConfig().Copy()
|
||||
c.MinBuilderBid = cliCtx.Uint64(flags.MinBuilderBid.Name)
|
||||
if err := params.SetActive(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cliCtx.IsSet(flags.MinBuilderDiff.Name) {
|
||||
c := params.BeaconConfig().Copy()
|
||||
c.MinBuilderDiff = cliCtx.Uint64(flags.MinBuilderDiff.Name)
|
||||
if err := params.SetActive(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ func (c *AttCaches) SaveAggregatedAttestation(att ethpb.Att) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create attestation ID")
|
||||
}
|
||||
copiedAtt := att.Copy()
|
||||
copiedAtt := att.Clone()
|
||||
|
||||
c.aggregatedAttLock.Lock()
|
||||
defer c.aggregatedAttLock.Unlock()
|
||||
|
||||
@@ -33,7 +33,7 @@ func (c *AttCaches) SaveBlockAttestation(att ethpb.Att) error {
|
||||
}
|
||||
}
|
||||
|
||||
c.blockAtt[id] = append(atts, att.Copy())
|
||||
c.blockAtt[id] = append(atts, att.Clone())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (c *AttCaches) ForkchoiceAttestations() []ethpb.Att {
|
||||
|
||||
atts := make([]ethpb.Att, 0, len(c.forkchoiceAtt))
|
||||
for _, att := range c.forkchoiceAtt {
|
||||
atts = append(atts, att.Copy())
|
||||
atts = append(atts, att.Clone())
|
||||
}
|
||||
|
||||
return atts
|
||||
|
||||
@@ -64,7 +64,7 @@ func (c *AttCaches) UnaggregatedAttestations() ([]ethpb.Att, error) {
|
||||
return nil, err
|
||||
}
|
||||
if !seen {
|
||||
atts = append(atts, att.Copy())
|
||||
atts = append(atts, att.Clone())
|
||||
}
|
||||
}
|
||||
return atts, nil
|
||||
|
||||
@@ -67,7 +67,7 @@ func (s *Service) batchForkChoiceAtts(ctx context.Context) error {
|
||||
atts := append(s.cfg.Pool.AggregatedAttestations(), s.cfg.Pool.BlockAttestations()...)
|
||||
atts = append(atts, s.cfg.Pool.ForkchoiceAttestations()...)
|
||||
|
||||
attsByVerAndDataRoot := make(map[attestation.Id][]ethpb.Att, len(atts))
|
||||
attsById := make(map[attestation.Id][]ethpb.Att, len(atts))
|
||||
|
||||
// Consolidate attestations by aggregating them by similar data root.
|
||||
for _, att := range atts {
|
||||
@@ -83,10 +83,10 @@ func (s *Service) batchForkChoiceAtts(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create attestation ID")
|
||||
}
|
||||
attsByVerAndDataRoot[id] = append(attsByVerAndDataRoot[id], att)
|
||||
attsById[id] = append(attsById[id], att)
|
||||
}
|
||||
|
||||
for _, atts := range attsByVerAndDataRoot {
|
||||
for _, atts := range attsById {
|
||||
if err := s.aggregateAndSaveForkChoiceAtts(atts); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func (s *Service) batchForkChoiceAtts(ctx context.Context) error {
|
||||
func (s *Service) aggregateAndSaveForkChoiceAtts(atts []ethpb.Att) error {
|
||||
clonedAtts := make([]ethpb.Att, len(atts))
|
||||
for i, a := range atts {
|
||||
clonedAtts[i] = a.Copy()
|
||||
clonedAtts[i] = a.Clone()
|
||||
}
|
||||
aggregatedAtts, err := attaggregation.Aggregate(clonedAtts)
|
||||
if err != nil {
|
||||
|
||||
@@ -16,6 +16,11 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
MockRawPeerId0 = "16Uiu2HAkyWZ4Ni1TpvDS8dPxsozmHY85KaiFjodQuV6Tz5tkHVeR"
|
||||
MockRawPeerId1 = "16Uiu2HAm4HgJ9N1o222xK61o7LSgToYWoAy1wNTJRkh9gLZapVAy"
|
||||
)
|
||||
|
||||
// MockPeersProvider implements PeersProvider for testing.
|
||||
type MockPeersProvider struct {
|
||||
lock sync.Mutex
|
||||
@@ -50,7 +55,7 @@ func (m *MockPeersProvider) Peers() *peers.Status {
|
||||
},
|
||||
})
|
||||
// Pretend we are connected to two peers
|
||||
id0, err := peer.Decode("16Uiu2HAkyWZ4Ni1TpvDS8dPxsozmHY85KaiFjodQuV6Tz5tkHVeR")
|
||||
id0, err := peer.Decode(MockRawPeerId0)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Cannot decode")
|
||||
}
|
||||
@@ -61,7 +66,7 @@ func (m *MockPeersProvider) Peers() *peers.Status {
|
||||
m.peers.Add(createENR(), id0, ma0, network.DirInbound)
|
||||
m.peers.SetConnectionState(id0, peers.PeerConnected)
|
||||
m.peers.SetChainState(id0, &pb.Status{FinalizedEpoch: 10})
|
||||
id1, err := peer.Decode("16Uiu2HAm4HgJ9N1o222xK61o7LSgToYWoAy1wNTJRkh9gLZapVAy")
|
||||
id1, err := peer.Decode(MockRawPeerId1)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Cannot decode")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"beacon.go",
|
||||
"errors.go",
|
||||
"log.go",
|
||||
"service.go",
|
||||
@@ -20,6 +21,8 @@ go_library(
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/core/validators:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/forkchoice/types:go_default_library",
|
||||
"//beacon-chain/operations/synccommittee:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
@@ -28,6 +31,7 @@ go_library(
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
|
||||
128
beacon-chain/rpc/core/beacon.go
Normal file
128
beacon-chain/rpc/core/beacon.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
// Retrieve chain head information from the DB and the current beacon state.
|
||||
func (s *Service) ChainHead(ctx context.Context) (*ethpb.ChainHead, *RpcError) {
|
||||
headBlock, err := s.HeadFetcher.HeadBlock(ctx)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get head block"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
if err := consensusblocks.BeaconBlockIsNil(headBlock); err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "head block of chain was nil"),
|
||||
Reason: NotFound,
|
||||
}
|
||||
}
|
||||
optimisticStatus, err := s.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get optimistic status"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
headBlockRoot, err := headBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get head block root"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
validGenesis := false
|
||||
validateCP := func(cp *ethpb.Checkpoint, name string) error {
|
||||
if bytesutil.ToBytes32(cp.Root) == params.BeaconConfig().ZeroHash && cp.Epoch == 0 {
|
||||
if validGenesis {
|
||||
return nil
|
||||
}
|
||||
// Retrieve genesis block in the event we have genesis checkpoints.
|
||||
genBlock, err := s.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil || consensusblocks.BeaconBlockIsNil(genBlock) != nil {
|
||||
return errors.New("could not get genesis block")
|
||||
}
|
||||
validGenesis = true
|
||||
return nil
|
||||
}
|
||||
b, err := s.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
|
||||
if err != nil {
|
||||
return errors.Errorf("could not get %s block: %v", name, err)
|
||||
}
|
||||
if err := consensusblocks.BeaconBlockIsNil(b); err != nil {
|
||||
return errors.Errorf("could not get %s block: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
finalizedCheckpoint := s.FinalizedFetcher.FinalizedCheckpt()
|
||||
if err := validateCP(finalizedCheckpoint, "finalized"); err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not get finalized checkpoint"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
justifiedCheckpoint := s.FinalizedFetcher.CurrentJustifiedCheckpt()
|
||||
if err := validateCP(justifiedCheckpoint, "justified"); err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not get current justified checkpoint"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
prevJustifiedCheckpoint := s.FinalizedFetcher.PreviousJustifiedCheckpt()
|
||||
if err := validateCP(prevJustifiedCheckpoint, "prev justified"); err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not get previous justified checkpoint"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
fSlot, err := slots.EpochStart(finalizedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get epoch start slot from finalized checkpoint epoch"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
jSlot, err := slots.EpochStart(justifiedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get epoch start slot from justified checkpoint epoch"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
pjSlot, err := slots.EpochStart(prevJustifiedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not get epoch start slot from prev justified checkpoint epoch"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
return ðpb.ChainHead{
|
||||
HeadSlot: headBlock.Block().Slot(),
|
||||
HeadEpoch: slots.ToEpoch(headBlock.Block().Slot()),
|
||||
HeadBlockRoot: headBlockRoot[:],
|
||||
FinalizedSlot: fSlot,
|
||||
FinalizedEpoch: finalizedCheckpoint.Epoch,
|
||||
FinalizedBlockRoot: finalizedCheckpoint.Root,
|
||||
JustifiedSlot: jSlot,
|
||||
JustifiedEpoch: justifiedCheckpoint.Epoch,
|
||||
JustifiedBlockRoot: justifiedCheckpoint.Root,
|
||||
PreviousJustifiedSlot: pjSlot,
|
||||
PreviousJustifiedEpoch: prevJustifiedCheckpoint.Epoch,
|
||||
PreviousJustifiedBlockRoot: prevJustifiedCheckpoint.Root,
|
||||
OptimisticStatus: optimisticStatus,
|
||||
}, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
opfeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/synccommittee"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
BeaconDB db.ReadOnlyDatabase
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
FinalizedFetcher blockchain.FinalizationFetcher
|
||||
GenesisTimeFetcher blockchain.TimeFetcher
|
||||
@@ -21,5 +24,6 @@ type Service struct {
|
||||
AttestationCache *cache.AttestationCache
|
||||
StateGen stategen.StateManager
|
||||
P2P p2p.Broadcaster
|
||||
ReplayerBuilder stategen.ReplayerBuilder
|
||||
OptimisticModeFetcher blockchain.OptimisticModeFetcher
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
coreTime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators"
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types"
|
||||
beaconState "github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
@@ -211,6 +212,131 @@ func (s *Service) ComputeValidatorPerformance(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IndividualVotes retrieves individual voting status of validators.
|
||||
func (s *Service) IndividualVotes(
|
||||
ctx context.Context,
|
||||
req *ethpb.IndividualVotesRequest,
|
||||
) (*ethpb.IndividualVotesRespond, *RpcError) {
|
||||
currentEpoch := slots.ToEpoch(s.GenesisTimeFetcher.CurrentSlot())
|
||||
if req.Epoch > currentEpoch {
|
||||
return nil, &RpcError{
|
||||
Err: fmt.Errorf("cannot retrieve information about an epoch in the future, current epoch %d, requesting %d\n", currentEpoch, req.Epoch),
|
||||
Reason: BadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
slot, err := slots.EpochEnd(req.Epoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{Err: err, Reason: Internal}
|
||||
}
|
||||
st, err := s.ReplayerBuilder.ReplayerForSlot(slot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "failed to replay blocks for state at epoch %d", req.Epoch),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
// Track filtered validators to prevent duplication in the response.
|
||||
filtered := map[primitives.ValidatorIndex]bool{}
|
||||
filteredIndices := make([]primitives.ValidatorIndex, 0)
|
||||
votes := make([]*ethpb.IndividualVotesRespond_IndividualVote, 0, len(req.Indices)+len(req.PublicKeys))
|
||||
// Filter out assignments by public keys.
|
||||
for _, pubKey := range req.PublicKeys {
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
||||
if !ok {
|
||||
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{PublicKey: pubKey, ValidatorIndex: primitives.ValidatorIndex(^uint64(0))})
|
||||
continue
|
||||
}
|
||||
filtered[index] = true
|
||||
filteredIndices = append(filteredIndices, index)
|
||||
}
|
||||
// Filter out assignments by validator indices.
|
||||
for _, index := range req.Indices {
|
||||
if !filtered[index] {
|
||||
filteredIndices = append(filteredIndices, index)
|
||||
}
|
||||
}
|
||||
sort.Slice(filteredIndices, func(i, j int) bool {
|
||||
return filteredIndices[i] < filteredIndices[j]
|
||||
})
|
||||
|
||||
var v []*precompute.Validator
|
||||
var bal *precompute.Balance
|
||||
if st.Version() == version.Phase0 {
|
||||
v, bal, err = precompute.New(ctx, st)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not set up pre compute instance"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
v, _, err = precompute.ProcessAttestations(ctx, st, v, bal)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not pre compute attestations"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
} else if st.Version() >= version.Altair {
|
||||
v, bal, err = altair.InitializePrecomputeValidators(ctx, st)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not set up altair pre compute instance"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
v, _, err = altair.ProcessEpochParticipation(ctx, st, bal, v)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not pre compute attestations"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "invalid state type retrieved with a version of %d", st.Version()),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
for _, index := range filteredIndices {
|
||||
if uint64(index) >= uint64(len(v)) {
|
||||
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ValidatorIndex: index})
|
||||
continue
|
||||
}
|
||||
val, err := st.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "could not retrieve validator"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
pb := val.PublicKey()
|
||||
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{
|
||||
Epoch: req.Epoch,
|
||||
PublicKey: pb[:],
|
||||
ValidatorIndex: index,
|
||||
IsSlashed: v[index].IsSlashed,
|
||||
IsWithdrawableInCurrentEpoch: v[index].IsWithdrawableCurrentEpoch,
|
||||
IsActiveInCurrentEpoch: v[index].IsActiveCurrentEpoch,
|
||||
IsActiveInPreviousEpoch: v[index].IsActivePrevEpoch,
|
||||
IsCurrentEpochAttester: v[index].IsCurrentEpochAttester,
|
||||
IsCurrentEpochTargetAttester: v[index].IsCurrentEpochTargetAttester,
|
||||
IsPreviousEpochAttester: v[index].IsPrevEpochAttester,
|
||||
IsPreviousEpochTargetAttester: v[index].IsPrevEpochTargetAttester,
|
||||
IsPreviousEpochHeadAttester: v[index].IsPrevEpochHeadAttester,
|
||||
CurrentEpochEffectiveBalanceGwei: v[index].CurrentEpochEffectiveBalance,
|
||||
InclusionSlot: v[index].InclusionSlot,
|
||||
InclusionDistance: v[index].InclusionDistance,
|
||||
InactivityScore: v[index].InactivityScore,
|
||||
})
|
||||
}
|
||||
|
||||
return ðpb.IndividualVotesRespond{
|
||||
IndividualVotes: votes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SubmitSignedContributionAndProof is called by a sync committee aggregator
|
||||
// to submit signed contribution and proof object.
|
||||
func (s *Service) SubmitSignedContributionAndProof(
|
||||
@@ -368,13 +494,18 @@ func (s *Service) GetAttestationData(
|
||||
return nil, &RpcError{Reason: BadRequest, Err: errors.Errorf("invalid request: %v", err)}
|
||||
}
|
||||
|
||||
committeeIndex := primitives.CommitteeIndex(0)
|
||||
if slots.ToEpoch(req.Slot) < params.BeaconConfig().ElectraForkEpoch {
|
||||
committeeIndex = req.CommitteeIndex
|
||||
}
|
||||
|
||||
s.AttestationCache.RLock()
|
||||
res := s.AttestationCache.Get()
|
||||
if res != nil && res.Slot == req.Slot {
|
||||
s.AttestationCache.RUnlock()
|
||||
return ðpb.AttestationData{
|
||||
Slot: res.Slot,
|
||||
CommitteeIndex: req.CommitteeIndex,
|
||||
CommitteeIndex: committeeIndex,
|
||||
BeaconBlockRoot: res.HeadRoot,
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: res.Source.Epoch,
|
||||
@@ -398,7 +529,7 @@ func (s *Service) GetAttestationData(
|
||||
if res != nil && res.Slot == req.Slot {
|
||||
return ðpb.AttestationData{
|
||||
Slot: res.Slot,
|
||||
CommitteeIndex: req.CommitteeIndex,
|
||||
CommitteeIndex: committeeIndex,
|
||||
BeaconBlockRoot: res.HeadRoot,
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: res.Source.Epoch,
|
||||
@@ -458,7 +589,7 @@ func (s *Service) GetAttestationData(
|
||||
|
||||
return ðpb.AttestationData{
|
||||
Slot: req.Slot,
|
||||
CommitteeIndex: req.CommitteeIndex,
|
||||
CommitteeIndex: committeeIndex,
|
||||
BeaconBlockRoot: headRoot,
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: justifiedCheckpoint.Epoch,
|
||||
@@ -622,3 +753,177 @@ func subnetsFromCommittee(pubkey []byte, comm *ethpb.SyncCommittee) []uint64 {
|
||||
}
|
||||
return positions
|
||||
}
|
||||
|
||||
// ValidatorParticipation retrieves the validator participation information for a given epoch,
|
||||
// it returns the information about validator's participation rate in voting on the proof of stake
|
||||
// rules based on their balance compared to the total active validator balance.
|
||||
func (s *Service) ValidatorParticipation(
|
||||
ctx context.Context,
|
||||
requestedEpoch primitives.Epoch,
|
||||
) (
|
||||
*ethpb.ValidatorParticipationResponse,
|
||||
*RpcError,
|
||||
) {
|
||||
currentSlot := s.GenesisTimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(currentSlot)
|
||||
|
||||
if requestedEpoch > currentEpoch {
|
||||
return nil, &RpcError{
|
||||
Err: fmt.Errorf("cannot retrieve information about an epoch greater than current epoch, current epoch %d, requesting %d", currentEpoch, requestedEpoch),
|
||||
Reason: BadRequest,
|
||||
}
|
||||
}
|
||||
// Use the last slot of requested epoch to obtain current and previous epoch attestations.
|
||||
// This ensures that we don't miss previous attestations when input requested epochs.
|
||||
endSlot, err := slots.EpochEnd(requestedEpoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not get slot from requested epoch")}
|
||||
}
|
||||
// Get as close as we can to the end of the current epoch without going past the current slot.
|
||||
// The above check ensures a future *epoch* isn't requested, but the end slot of the requested epoch could still
|
||||
// be past the current slot. In that case, use the current slot as the best approximation of the requested epoch.
|
||||
// Replayer will make sure the slot ultimately used is canonical.
|
||||
if endSlot > currentSlot {
|
||||
endSlot = currentSlot
|
||||
}
|
||||
|
||||
// ReplayerBuilder ensures that a canonical chain is followed to the slot
|
||||
beaconSt, err := s.ReplayerBuilder.ReplayerForSlot(endSlot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, &RpcError{Reason: Internal, Err: errors.Wrapf(err, "error replaying blocks for state at slot %d", endSlot)}
|
||||
}
|
||||
var v []*precompute.Validator
|
||||
var b *precompute.Balance
|
||||
|
||||
if beaconSt.Version() == version.Phase0 {
|
||||
v, b, err = precompute.New(ctx, beaconSt)
|
||||
if err != nil {
|
||||
return nil, &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not set up pre compute instance")}
|
||||
}
|
||||
_, b, err = precompute.ProcessAttestations(ctx, beaconSt, v, b)
|
||||
if err != nil {
|
||||
return nil, &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not pre compute attestations")}
|
||||
}
|
||||
} else if beaconSt.Version() >= version.Altair {
|
||||
v, b, err = altair.InitializePrecomputeValidators(ctx, beaconSt)
|
||||
if err != nil {
|
||||
return nil, &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not set up altair pre compute instance")}
|
||||
}
|
||||
_, b, err = altair.ProcessEpochParticipation(ctx, beaconSt, b, v)
|
||||
if err != nil {
|
||||
return nil, &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not pre compute attestations: %v")}
|
||||
}
|
||||
} else {
|
||||
return nil, &RpcError{Reason: Internal, Err: fmt.Errorf("invalid state type retrieved with a version of %s", version.String(beaconSt.Version()))}
|
||||
}
|
||||
|
||||
cp := s.FinalizedFetcher.FinalizedCheckpt()
|
||||
p := ðpb.ValidatorParticipationResponse{
|
||||
Epoch: requestedEpoch,
|
||||
Finalized: requestedEpoch <= cp.Epoch,
|
||||
Participation: ðpb.ValidatorParticipation{
|
||||
// TODO(7130): Remove these three deprecated fields.
|
||||
GlobalParticipationRate: float32(b.PrevEpochTargetAttested) / float32(b.ActivePrevEpoch),
|
||||
VotedEther: b.PrevEpochTargetAttested,
|
||||
EligibleEther: b.ActivePrevEpoch,
|
||||
CurrentEpochActiveGwei: b.ActiveCurrentEpoch,
|
||||
CurrentEpochAttestingGwei: b.CurrentEpochAttested,
|
||||
CurrentEpochTargetAttestingGwei: b.CurrentEpochTargetAttested,
|
||||
PreviousEpochActiveGwei: b.ActivePrevEpoch,
|
||||
PreviousEpochAttestingGwei: b.PrevEpochAttested,
|
||||
PreviousEpochTargetAttestingGwei: b.PrevEpochTargetAttested,
|
||||
PreviousEpochHeadAttestingGwei: b.PrevEpochHeadAttested,
|
||||
},
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ValidatorActiveSetChanges retrieves the active set changes for a given epoch.
|
||||
//
|
||||
// This data includes any activations, voluntary exits, and involuntary
|
||||
// ejections.
|
||||
func (s *Service) ValidatorActiveSetChanges(
|
||||
ctx context.Context,
|
||||
requestedEpoch primitives.Epoch,
|
||||
) (
|
||||
*ethpb.ActiveSetChanges,
|
||||
*RpcError,
|
||||
) {
|
||||
currentEpoch := slots.ToEpoch(s.GenesisTimeFetcher.CurrentSlot())
|
||||
if requestedEpoch > currentEpoch {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Errorf("cannot retrieve information about an epoch in the future, current epoch %d, requesting %d", currentEpoch, requestedEpoch),
|
||||
Reason: BadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
slot, err := slots.EpochStart(requestedEpoch)
|
||||
if err != nil {
|
||||
return nil, &RpcError{Err: err, Reason: BadRequest}
|
||||
}
|
||||
requestedState, err := s.ReplayerBuilder.ReplayerForSlot(slot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrapf(err, "error replaying blocks for state at slot %d", slot),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
activeValidatorCount, err := helpers.ActiveValidatorCount(ctx, requestedState, coreTime.CurrentEpoch(requestedState))
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not get active validator count"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
vs := requestedState.Validators()
|
||||
activatedIndices := validators.ActivatedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs)
|
||||
exitedIndices, err := validators.ExitedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs, activeValidatorCount)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not determine exited validator indices"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
slashedIndices := validators.SlashedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs)
|
||||
ejectedIndices, err := validators.EjectedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs, activeValidatorCount)
|
||||
if err != nil {
|
||||
return nil, &RpcError{
|
||||
Err: errors.Wrap(err, "could not determine ejected validator indices"),
|
||||
Reason: Internal,
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve public keys for the indices.
|
||||
activatedKeys := make([][]byte, len(activatedIndices))
|
||||
exitedKeys := make([][]byte, len(exitedIndices))
|
||||
slashedKeys := make([][]byte, len(slashedIndices))
|
||||
ejectedKeys := make([][]byte, len(ejectedIndices))
|
||||
for i, idx := range activatedIndices {
|
||||
pubkey := requestedState.PubkeyAtIndex(idx)
|
||||
activatedKeys[i] = pubkey[:]
|
||||
}
|
||||
for i, idx := range exitedIndices {
|
||||
pubkey := requestedState.PubkeyAtIndex(idx)
|
||||
exitedKeys[i] = pubkey[:]
|
||||
}
|
||||
for i, idx := range slashedIndices {
|
||||
pubkey := requestedState.PubkeyAtIndex(idx)
|
||||
slashedKeys[i] = pubkey[:]
|
||||
}
|
||||
for i, idx := range ejectedIndices {
|
||||
pubkey := requestedState.PubkeyAtIndex(idx)
|
||||
ejectedKeys[i] = pubkey[:]
|
||||
}
|
||||
return ðpb.ActiveSetChanges{
|
||||
Epoch: requestedEpoch,
|
||||
ActivatedPublicKeys: activatedKeys,
|
||||
ActivatedIndices: activatedIndices,
|
||||
ExitedPublicKeys: exitedKeys,
|
||||
ExitedIndices: exitedIndices,
|
||||
SlashedPublicKeys: slashedKeys,
|
||||
SlashedIndices: slashedIndices,
|
||||
EjectedPublicKeys: ejectedKeys,
|
||||
EjectedIndices: ejectedIndices,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -68,9 +68,9 @@ func (s *Service) endpoints(
|
||||
endpoints = append(endpoints, s.configEndpoints()...)
|
||||
endpoints = append(endpoints, s.lightClientEndpoints(blocker, stater)...)
|
||||
endpoints = append(endpoints, s.eventsEndpoints()...)
|
||||
endpoints = append(endpoints, s.prysmBeaconEndpoints(ch, stater)...)
|
||||
endpoints = append(endpoints, s.prysmBeaconEndpoints(ch, stater, coreService)...)
|
||||
endpoints = append(endpoints, s.prysmNodeEndpoints()...)
|
||||
endpoints = append(endpoints, s.prysmValidatorEndpoints(coreService)...)
|
||||
endpoints = append(endpoints, s.prysmValidatorEndpoints(stater, coreService)...)
|
||||
if enableDebug {
|
||||
endpoints = append(endpoints, s.debugEndpoints(stater)...)
|
||||
}
|
||||
@@ -904,10 +904,11 @@ func (s *Service) debugEndpoints(stater lookup.Stater) []endpoint {
|
||||
|
||||
func (s *Service) eventsEndpoints() []endpoint {
|
||||
server := &events.Server{
|
||||
StateNotifier: s.cfg.StateNotifier,
|
||||
OperationNotifier: s.cfg.OperationNotifier,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
|
||||
StateNotifier: s.cfg.StateNotifier,
|
||||
OperationNotifier: s.cfg.OperationNotifier,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
|
||||
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
|
||||
}
|
||||
|
||||
const namespace = "events"
|
||||
@@ -925,8 +926,11 @@ func (s *Service) eventsEndpoints() []endpoint {
|
||||
}
|
||||
|
||||
// Prysm custom endpoints
|
||||
|
||||
func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater lookup.Stater) []endpoint {
|
||||
func (s *Service) prysmBeaconEndpoints(
|
||||
ch *stategen.CanonicalHistory,
|
||||
stater lookup.Stater,
|
||||
coreService *core.Service,
|
||||
) []endpoint {
|
||||
server := &beaconprysm.Server{
|
||||
SyncChecker: s.cfg.SyncService,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
@@ -937,6 +941,7 @@ func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater loo
|
||||
Stater: stater,
|
||||
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
|
||||
FinalizationFetcher: s.cfg.FinalizationFetcher,
|
||||
CoreService: coreService,
|
||||
}
|
||||
|
||||
const namespace = "prysm.beacon"
|
||||
@@ -968,6 +973,25 @@ func (s *Service) prysmBeaconEndpoints(ch *stategen.CanonicalHistory, stater loo
|
||||
handler: server.GetValidatorCount,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/prysm/v1/beacon/individual_votes",
|
||||
name: namespace + ".GetIndividualVotes",
|
||||
middleware: []mux.MiddlewareFunc{
|
||||
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetIndividualVotes,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/prysm/v1/beacon/chain_head",
|
||||
name: namespace + ".GetChainHead",
|
||||
middleware: []mux.MiddlewareFunc{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetChainHead,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1045,32 +1069,52 @@ func (s *Service) prysmNodeEndpoints() []endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
func (*Service) prysmValidatorEndpoints(coreService *core.Service) []endpoint {
|
||||
func (s *Service) prysmValidatorEndpoints(stater lookup.Stater, coreService *core.Service) []endpoint {
|
||||
server := &validatorprysm.Server{
|
||||
CoreService: coreService,
|
||||
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
|
||||
Stater: stater,
|
||||
CoreService: coreService,
|
||||
}
|
||||
|
||||
const namespace = "prysm.validator"
|
||||
return []endpoint{
|
||||
{
|
||||
template: "/prysm/validators/performance",
|
||||
name: namespace + ".GetValidatorPerformance",
|
||||
name: namespace + ".GetPerformance",
|
||||
middleware: []mux.MiddlewareFunc{
|
||||
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetValidatorPerformance,
|
||||
handler: server.GetPerformance,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/prysm/v1/validators/performance",
|
||||
name: namespace + ".GetValidatorPerformance",
|
||||
name: namespace + ".GetPerformance",
|
||||
middleware: []mux.MiddlewareFunc{
|
||||
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetValidatorPerformance,
|
||||
handler: server.GetPerformance,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/prysm/v1/validators/participation",
|
||||
name: namespace + ".GetParticipation",
|
||||
middleware: []mux.MiddlewareFunc{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetParticipation,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/prysm/v1/validators/active_set_changes",
|
||||
name: namespace + ".GetActiveSetChanges",
|
||||
middleware: []mux.MiddlewareFunc{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetActiveSetChanges,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ func Test_endpoints(t *testing.T) {
|
||||
"/eth/v1/beacon/pool/sync_committees": {http.MethodPost},
|
||||
"/eth/v1/beacon/pool/voluntary_exits": {http.MethodGet, http.MethodPost},
|
||||
"/eth/v1/beacon/pool/bls_to_execution_changes": {http.MethodGet, http.MethodPost},
|
||||
"/prysm/v1/beacon/individual_votes": {http.MethodPost},
|
||||
}
|
||||
|
||||
lightClientRoutes := map[string][]string{
|
||||
@@ -113,6 +114,7 @@ func Test_endpoints(t *testing.T) {
|
||||
"/prysm/v1/beacon/weak_subjectivity": {http.MethodGet},
|
||||
"/eth/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
|
||||
"/prysm/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
|
||||
"/prysm/v1/beacon/chain_head": {http.MethodGet},
|
||||
}
|
||||
|
||||
prysmNodeRoutes := map[string][]string{
|
||||
@@ -123,8 +125,10 @@ func Test_endpoints(t *testing.T) {
|
||||
}
|
||||
|
||||
prysmValidatorRoutes := map[string][]string{
|
||||
"/prysm/validators/performance": {http.MethodPost},
|
||||
"/prysm/v1/validators/performance": {http.MethodPost},
|
||||
"/prysm/validators/performance": {http.MethodPost},
|
||||
"/prysm/v1/validators/performance": {http.MethodPost},
|
||||
"/prysm/v1/validators/participation": {http.MethodGet},
|
||||
"/prysm/v1/validators/active_set_changes": {http.MethodGet},
|
||||
}
|
||||
|
||||
s := &Service{cfg: &Config{}}
|
||||
|
||||
@@ -116,9 +116,11 @@ func (s *Server) getBlockV2Ssz(w http.ResponseWriter, blk interfaces.ReadOnlySig
|
||||
result, err := s.getBlockResponseBodySsz(blk)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get signed beacon block: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if result == nil {
|
||||
httputil.HandleError(w, fmt.Sprintf("Unknown block type %T", blk), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set(api.VersionHeader, version.String(blk.Version()))
|
||||
httputil.WriteSsz(w, result, "beacon_block.ssz")
|
||||
@@ -149,6 +151,11 @@ func (s *Server) getBlockV2Json(ctx context.Context, w http.ResponseWriter, blk
|
||||
result, err := s.getBlockResponseBodyJson(ctx, blk)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Error processing request: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if result == nil {
|
||||
httputil.HandleError(w, fmt.Sprintf("Unknown block type %T", blk), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set(api.VersionHeader, result.Version)
|
||||
httputil.WriteJson(w, result)
|
||||
|
||||
@@ -12,6 +12,7 @@ go_library(
|
||||
"//api:go_default_library",
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/core/feed:go_default_library",
|
||||
"//beacon-chain/core/feed/operation:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
@@ -37,6 +38,7 @@ go_test(
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/core/feed:go_default_library",
|
||||
"//beacon-chain/core/feed/operation:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
@@ -50,5 +52,6 @@ go_test(
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -439,14 +439,18 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head state proposer index: "+err.Error())
|
||||
}
|
||||
|
||||
feeRecipient := params.BeaconConfig().DefaultFeeRecipient.Bytes()
|
||||
tValidator, exists := s.TrackedValidatorsCache.Validator(proposerIndex)
|
||||
if exists {
|
||||
feeRecipient = tValidator.FeeRecipient[:]
|
||||
}
|
||||
var attributes interface{}
|
||||
switch headState.Version() {
|
||||
case version.Bellatrix:
|
||||
attributes = &structs.PayloadAttributesV1{
|
||||
Timestamp: fmt.Sprintf("%d", t.Unix()),
|
||||
PrevRandao: hexutil.Encode(prevRando),
|
||||
SuggestedFeeRecipient: hexutil.Encode(headPayload.FeeRecipient()),
|
||||
SuggestedFeeRecipient: hexutil.Encode(feeRecipient),
|
||||
}
|
||||
case version.Capella:
|
||||
withdrawals, _, err := headState.ExpectedWithdrawals()
|
||||
@@ -456,7 +460,7 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
attributes = &structs.PayloadAttributesV2{
|
||||
Timestamp: fmt.Sprintf("%d", t.Unix()),
|
||||
PrevRandao: hexutil.Encode(prevRando),
|
||||
SuggestedFeeRecipient: hexutil.Encode(headPayload.FeeRecipient()),
|
||||
SuggestedFeeRecipient: hexutil.Encode(feeRecipient),
|
||||
Withdrawals: structs.WithdrawalsFromConsensus(withdrawals),
|
||||
}
|
||||
case version.Deneb, version.Electra:
|
||||
@@ -471,7 +475,7 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
attributes = &structs.PayloadAttributesV3{
|
||||
Timestamp: fmt.Sprintf("%d", t.Unix()),
|
||||
PrevRandao: hexutil.Encode(prevRando),
|
||||
SuggestedFeeRecipient: hexutil.Encode(headPayload.FeeRecipient()),
|
||||
SuggestedFeeRecipient: hexutil.Encode(feeRecipient),
|
||||
Withdrawals: structs.WithdrawalsFromConsensus(withdrawals),
|
||||
ParentBeaconBlockRoot: hexutil.Encode(parentRoot[:]),
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
mockChain "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state"
|
||||
@@ -280,10 +282,11 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
})
|
||||
t.Run("payload attributes", func(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
getState func() state.BeaconState
|
||||
getBlock func() interfaces.SignedBeaconBlock
|
||||
expected string
|
||||
name string
|
||||
getState func() state.BeaconState
|
||||
getBlock func() interfaces.SignedBeaconBlock
|
||||
expected string
|
||||
SetTrackedValidatorsCache func(*cache.TrackedValidatorsCache)
|
||||
}
|
||||
testCases := []testCase{
|
||||
{
|
||||
@@ -328,6 +331,27 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
},
|
||||
expected: payloadAttributesDenebResult,
|
||||
},
|
||||
{
|
||||
name: "electra",
|
||||
getState: func() state.BeaconState {
|
||||
st, err := util.NewBeaconStateElectra()
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
},
|
||||
getBlock: func() interfaces.SignedBeaconBlock {
|
||||
b, err := blocks.NewSignedBeaconBlock(util.HydrateSignedBeaconBlockElectra(ð.SignedBeaconBlockElectra{}))
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
},
|
||||
expected: payloadAttributesElectraResultWithTVC,
|
||||
SetTrackedValidatorsCache: func(c *cache.TrackedValidatorsCache) {
|
||||
c.Set(cache.TrackedValidator{
|
||||
Active: true,
|
||||
Index: 0,
|
||||
FeeRecipient: primitives.ExecutionAddress(common.HexToAddress("0xd2DBd02e4efe087d7d195de828b9Dd25f19A89C9").Bytes()),
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
st := tc.getState()
|
||||
@@ -343,11 +367,16 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
Block: b,
|
||||
Slot: ¤tSlot,
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
StateNotifier: &mockChain.MockStateNotifier{},
|
||||
OperationNotifier: &mockChain.MockOperationNotifier{},
|
||||
HeadFetcher: mockChainService,
|
||||
ChainInfoFetcher: mockChainService,
|
||||
StateNotifier: &mockChain.MockStateNotifier{},
|
||||
OperationNotifier: &mockChain.MockOperationNotifier{},
|
||||
HeadFetcher: mockChainService,
|
||||
ChainInfoFetcher: mockChainService,
|
||||
TrackedValidatorsCache: cache.NewTrackedValidatorsCache(),
|
||||
}
|
||||
if tc.SetTrackedValidatorsCache != nil {
|
||||
tc.SetTrackedValidatorsCache(s.TrackedValidatorsCache)
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?topics=%s", PayloadAttributesTopic), nil)
|
||||
@@ -439,3 +468,10 @@ event: payload_attributes
|
||||
data: {"version":"deneb","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0x0000000000000000000000000000000000000000","withdrawals":[],"parent_beacon_block_root":"0xbef96cb938fd48b2403d3e662664325abb0102ed12737cbb80d717520e50cf4a"}}}
|
||||
|
||||
`
|
||||
|
||||
const payloadAttributesElectraResultWithTVC = `:
|
||||
|
||||
event: payload_attributes
|
||||
data: {"version":"electra","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0xd2dbd02e4efe087d7d195de828b9dd25f19a89c9","withdrawals":[],"parent_beacon_block_root":"0x66d641f7eae038f2dd28081b09d2ba279462cc47655c7b7e1fd1159a50c8eb32"}}}
|
||||
|
||||
`
|
||||
|
||||
@@ -5,6 +5,7 @@ package events
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
opfeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state"
|
||||
)
|
||||
@@ -12,8 +13,9 @@ import (
|
||||
// Server defines a server implementation of the gRPC events service,
|
||||
// providing RPC endpoints to subscribe to events from the beacon node.
|
||||
type Server struct {
|
||||
StateNotifier statefeed.Notifier
|
||||
OperationNotifier opfeed.Notifier
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
StateNotifier statefeed.Notifier
|
||||
OperationNotifier opfeed.Notifier
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
}
|
||||
|
||||
@@ -34,20 +34,28 @@ go_library(
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["handlers_test.go"],
|
||||
srcs = [
|
||||
"handlers_test.go",
|
||||
"helpers_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/rpc/testutil:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
|
||||
@@ -3,6 +3,7 @@ package lightclient
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
@@ -311,3 +312,64 @@ func newLightClientUpdateToJSON(input *v2.LightClientUpdate) *structs.LightClien
|
||||
SignatureSlot: strconv.FormatUint(uint64(input.SignatureSlot), 10),
|
||||
}
|
||||
}
|
||||
|
||||
func IsSyncCommitteeUpdate(update *v2.LightClientUpdate) bool {
|
||||
nextSyncCommitteeBranch := make([][]byte, fieldparams.NextSyncCommitteeBranchDepth)
|
||||
return !reflect.DeepEqual(update.NextSyncCommitteeBranch, nextSyncCommitteeBranch)
|
||||
}
|
||||
|
||||
func IsFinalityUpdate(update *v2.LightClientUpdate) bool {
|
||||
finalityBranch := make([][]byte, blockchain.FinalityBranchNumOfLeaves)
|
||||
return !reflect.DeepEqual(update.FinalityBranch, finalityBranch)
|
||||
}
|
||||
|
||||
func IsBetterUpdate(newUpdate, oldUpdate *v2.LightClientUpdate) bool {
|
||||
maxActiveParticipants := newUpdate.SyncAggregate.SyncCommitteeBits.Len()
|
||||
newNumActiveParticipants := newUpdate.SyncAggregate.SyncCommitteeBits.Count()
|
||||
oldNumActiveParticipants := oldUpdate.SyncAggregate.SyncCommitteeBits.Count()
|
||||
newHasSupermajority := newNumActiveParticipants*3 >= maxActiveParticipants*2
|
||||
oldHasSupermajority := oldNumActiveParticipants*3 >= maxActiveParticipants*2
|
||||
|
||||
if newHasSupermajority != oldHasSupermajority {
|
||||
return newHasSupermajority
|
||||
}
|
||||
if !newHasSupermajority && newNumActiveParticipants != oldNumActiveParticipants {
|
||||
return newNumActiveParticipants > oldNumActiveParticipants
|
||||
}
|
||||
|
||||
// Compare presence of relevant sync committee
|
||||
newHasRelevantSyncCommittee := IsSyncCommitteeUpdate(newUpdate) && (slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.AttestedHeader.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.SignatureSlot)))
|
||||
oldHasRelevantSyncCommittee := IsSyncCommitteeUpdate(oldUpdate) && (slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.AttestedHeader.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.SignatureSlot)))
|
||||
|
||||
if newHasRelevantSyncCommittee != oldHasRelevantSyncCommittee {
|
||||
return newHasRelevantSyncCommittee
|
||||
}
|
||||
|
||||
// Compare indication of any finality
|
||||
newHasFinality := IsFinalityUpdate(newUpdate)
|
||||
oldHasFinality := IsFinalityUpdate(oldUpdate)
|
||||
if newHasFinality != oldHasFinality {
|
||||
return newHasFinality
|
||||
}
|
||||
|
||||
// Compare sync committee finality
|
||||
if newHasFinality {
|
||||
newHasSyncCommitteeFinality := slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.FinalizedHeader.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.AttestedHeader.Slot))
|
||||
oldHasSyncCommitteeFinality := slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.FinalizedHeader.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.AttestedHeader.Slot))
|
||||
|
||||
if newHasSyncCommitteeFinality != oldHasSyncCommitteeFinality {
|
||||
return newHasSyncCommitteeFinality
|
||||
}
|
||||
}
|
||||
|
||||
// Tiebreaker 1: Sync committee participation beyond supermajority
|
||||
if newNumActiveParticipants != oldNumActiveParticipants {
|
||||
return newNumActiveParticipants > oldNumActiveParticipants
|
||||
}
|
||||
|
||||
// Tiebreaker 2: Prefer older data (fewer changes to best)
|
||||
if newUpdate.AttestedHeader.Slot != oldUpdate.AttestedHeader.Slot {
|
||||
return newUpdate.AttestedHeader.Slot < oldUpdate.AttestedHeader.Slot
|
||||
}
|
||||
return newUpdate.SignatureSlot < oldUpdate.SignatureSlot
|
||||
}
|
||||
|
||||
453
beacon-chain/rpc/eth/light-client/helpers_test.go
Normal file
453
beacon-chain/rpc/eth/light-client/helpers_test.go
Normal file
@@ -0,0 +1,453 @@
|
||||
package lightclient
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
)
|
||||
|
||||
// When the update has relevant sync committee
|
||||
func createNonEmptySyncCommitteeBranch() [][]byte {
|
||||
res := make([][]byte, fieldparams.NextSyncCommitteeBranchDepth)
|
||||
res[0] = []byte("xyz")
|
||||
return res
|
||||
}
|
||||
|
||||
// When the update has finality
|
||||
func createNonEmptyFinalityBranch() [][]byte {
|
||||
res := make([][]byte, blockchain.FinalityBranchNumOfLeaves)
|
||||
res[0] = []byte("xyz")
|
||||
return res
|
||||
}
|
||||
|
||||
func TestIsBetterUpdate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
oldUpdate *ethpbv2.LightClientUpdate
|
||||
newUpdate *ethpbv2.LightClientUpdate
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "new has supermajority but old doesn't",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0]
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b11111100, 0b1}, // [0,0,1,1,1,1,1,1]
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "old has supermajority but new doesn't",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b11111100, 0b1}, // [0,0,1,1,1,1,1,1]
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0]
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "new doesn't have supermajority and newNumActiveParticipants is greater than oldNumActiveParticipants",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0]
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "new doesn't have supermajority and newNumActiveParticipants is lesser than oldNumActiveParticipants",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,0,1,1,1,1,1,0]
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "new has relevant sync committee but old doesn't",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: make([][]byte, fieldparams.NextSyncCommitteeBranchDepth),
|
||||
SignatureSlot: 9999,
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000001,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 1000000,
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "old has relevant sync committee but new doesn't",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000001,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 1000000,
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: make([][]byte, fieldparams.NextSyncCommitteeBranchDepth),
|
||||
SignatureSlot: 9999,
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "new has finality but old doesn't",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: make([][]byte, blockchain.FinalityBranchNumOfLeaves),
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "old has finality but new doesn't",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: make([][]byte, blockchain.FinalityBranchNumOfLeaves),
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "new has finality and sync committee finality both but old doesn't have sync committee finality",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 999999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 999999,
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "new has finality but doesn't have sync committee finality and old has sync committee finality",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 999999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 999999,
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "new has more active participants than old",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,1,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "new has less active participants than old",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,1,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "new's attested header's slot is lesser than old's attested header's slot",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 999999,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "new's attested header's slot is greater than old's attested header's slot",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 999999,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "none of the above conditions are met and new signature's slot is lesser than old signature's slot",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9998,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "none of the above conditions are met and new signature's slot is greater than old signature's slot",
|
||||
oldUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9998,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
SyncAggregate: ðpbv1.SyncAggregate{
|
||||
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
|
||||
},
|
||||
AttestedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 1000000,
|
||||
},
|
||||
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
|
||||
SignatureSlot: 9999,
|
||||
FinalityBranch: createNonEmptyFinalityBranch(),
|
||||
FinalizedHeader: ðpbv1.BeaconBlockHeader{
|
||||
Slot: 9999,
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.expectedResult, IsBetterUpdate(testCase.newUpdate, testCase.oldUpdate))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ go_library(
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
"//beacon-chain/rpc/eth/helpers:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//beacon-chain/rpc/lookup:go_default_library",
|
||||
@@ -29,26 +30,44 @@ go_library(
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["validator_count_test.go"],
|
||||
srcs = [
|
||||
"handlers_test.go",
|
||||
"validator_count_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
"//beacon-chain/rpc/lookup:go_default_library",
|
||||
"//beacon-chain/rpc/testutil:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/state/stategen/mock:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//network/httputil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/network/httputil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
@@ -69,3 +75,111 @@ func (s *Server) GetWeakSubjectivity(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
httputil.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetIndividualVotes returns a list of validators individual vote status of a given epoch.
|
||||
func (s *Server) GetIndividualVotes(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetIndividualVotes")
|
||||
defer span.End()
|
||||
|
||||
var req structs.GetIndividualVotesRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
publicKeyBytes := make([][]byte, len(req.PublicKeys))
|
||||
for i, s := range req.PublicKeys {
|
||||
bs, err := hexutil.Decode(s)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "could not decode public keys: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
publicKeyBytes[i] = bs
|
||||
}
|
||||
epoch, err := strconv.ParseUint(req.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "invalid epoch: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var indices []primitives.ValidatorIndex
|
||||
for _, i := range req.Indices {
|
||||
u, err := strconv.ParseUint(i, 10, 64)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "invalid indices: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
indices = append(indices, primitives.ValidatorIndex(u))
|
||||
}
|
||||
votes, rpcError := s.CoreService.IndividualVotes(
|
||||
ctx,
|
||||
ðpb.IndividualVotesRequest{
|
||||
Epoch: primitives.Epoch(epoch),
|
||||
PublicKeys: publicKeyBytes,
|
||||
Indices: indices,
|
||||
},
|
||||
)
|
||||
|
||||
if rpcError != nil {
|
||||
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
|
||||
return
|
||||
}
|
||||
v := make([]*structs.IndividualVote, 0, len(votes.IndividualVotes))
|
||||
for _, vote := range votes.IndividualVotes {
|
||||
v = append(v, &structs.IndividualVote{
|
||||
Epoch: fmt.Sprintf("%d", vote.Epoch),
|
||||
PublicKey: hexutil.Encode(vote.PublicKey),
|
||||
ValidatorIndex: fmt.Sprintf("%d", vote.ValidatorIndex),
|
||||
IsSlashed: vote.IsSlashed,
|
||||
IsWithdrawableInCurrentEpoch: vote.IsWithdrawableInCurrentEpoch,
|
||||
IsActiveInCurrentEpoch: vote.IsActiveInCurrentEpoch,
|
||||
IsActiveInPreviousEpoch: vote.IsActiveInPreviousEpoch,
|
||||
IsCurrentEpochAttester: vote.IsCurrentEpochAttester,
|
||||
IsCurrentEpochTargetAttester: vote.IsCurrentEpochTargetAttester,
|
||||
IsPreviousEpochAttester: vote.IsPreviousEpochAttester,
|
||||
IsPreviousEpochTargetAttester: vote.IsPreviousEpochTargetAttester,
|
||||
IsPreviousEpochHeadAttester: vote.IsPreviousEpochHeadAttester,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", vote.CurrentEpochEffectiveBalanceGwei),
|
||||
InclusionSlot: fmt.Sprintf("%d", vote.InclusionSlot),
|
||||
InclusionDistance: fmt.Sprintf("%d", vote.InclusionDistance),
|
||||
InactivityScore: fmt.Sprintf("%d", vote.InactivityScore),
|
||||
})
|
||||
}
|
||||
response := &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: v,
|
||||
}
|
||||
httputil.WriteJson(w, response)
|
||||
}
|
||||
|
||||
// GetChainHead retrieves information about the head of the beacon chain from
|
||||
// the view of the beacon chain node.
|
||||
func (s *Server) GetChainHead(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetChainHead")
|
||||
defer span.End()
|
||||
|
||||
ch, rpcError := s.CoreService.ChainHead(ctx)
|
||||
if rpcError != nil {
|
||||
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
|
||||
return
|
||||
}
|
||||
response := &structs.ChainHead{
|
||||
HeadSlot: fmt.Sprintf("%d", ch.HeadSlot),
|
||||
HeadEpoch: fmt.Sprintf("%d", ch.HeadEpoch),
|
||||
HeadBlockRoot: hexutil.Encode(ch.HeadBlockRoot),
|
||||
FinalizedSlot: fmt.Sprintf("%d", ch.FinalizedSlot),
|
||||
FinalizedEpoch: fmt.Sprintf("%d", ch.FinalizedEpoch),
|
||||
FinalizedBlockRoot: hexutil.Encode(ch.FinalizedBlockRoot),
|
||||
JustifiedSlot: fmt.Sprintf("%d", ch.JustifiedSlot),
|
||||
JustifiedEpoch: fmt.Sprintf("%d", ch.JustifiedEpoch),
|
||||
JustifiedBlockRoot: hexutil.Encode(ch.JustifiedBlockRoot),
|
||||
PreviousJustifiedSlot: fmt.Sprintf("%d", ch.PreviousJustifiedSlot),
|
||||
PreviousJustifiedEpoch: fmt.Sprintf("%d", ch.PreviousJustifiedEpoch),
|
||||
PreviousJustifiedBlockRoot: hexutil.Encode(ch.PreviousJustifiedBlockRoot),
|
||||
OptimisticStatus: ch.OptimisticStatus,
|
||||
}
|
||||
httputil.WriteJson(w, response)
|
||||
}
|
||||
|
||||
872
beacon-chain/rpc/prysm/beacon/handlers_test.go
Normal file
872
beacon-chain/rpc/prysm/beacon/handlers_test.go
Normal file
@@ -0,0 +1,872 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
|
||||
chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
mockstategen "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen/mock"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
func individualVotesHelper(t *testing.T, request *structs.GetIndividualVotesRequest, s *Server) (string, *structs.GetIndividualVotesResponse) {
|
||||
var buf bytes.Buffer
|
||||
err := json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(s.GetIndividualVotes))
|
||||
defer srv.Close()
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"http://example.com/eth/v1/beacon/individual_votes",
|
||||
&buf,
|
||||
)
|
||||
client := &http.Client{}
|
||||
rawResp, err := client.Post(srv.URL, "application/json", req.Body)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := rawResp.Body.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
body, err := io.ReadAll(rawResp.Body)
|
||||
require.NoError(t, err)
|
||||
type ErrorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if rawResp.StatusCode != 200 {
|
||||
var errorResponse ErrorResponse
|
||||
err = json.Unmarshal(body, &errorResponse)
|
||||
require.NoError(t, err)
|
||||
return errorResponse.Message, &structs.GetIndividualVotesResponse{}
|
||||
}
|
||||
var votes *structs.GetIndividualVotesResponse
|
||||
err = json.Unmarshal(body, &votes)
|
||||
require.NoError(t, err)
|
||||
return "", votes
|
||||
}
|
||||
|
||||
func TestServer_GetIndividualVotes_RequestFutureSlot(t *testing.T) {
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
GenesisTimeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
request := &structs.GetIndividualVotesRequest{
|
||||
Epoch: fmt.Sprintf("%d", slots.ToEpoch(s.CoreService.GenesisTimeFetcher.CurrentSlot())+1),
|
||||
}
|
||||
errorResp, _ := individualVotesHelper(t, request, s)
|
||||
require.StringContains(t, "cannot retrieve information about an epoch in the future", errorResp)
|
||||
}
|
||||
|
||||
func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) {
|
||||
cc := &mockstategen.CanonicalChecker{Is: true, Err: nil}
|
||||
cs := &mockstategen.CurrentSlotter{Slot: math.MaxUint64 - 1}
|
||||
s.CoreService.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs)
|
||||
}
|
||||
|
||||
func TestServer_GetIndividualVotes_ValidatorsDontExist(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
var slot primitives.Slot = 0
|
||||
validators := uint64(64)
|
||||
stateWithValidators, _ := util.DeterministicGenesisState(t, validators)
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetValidators(stateWithValidators.Validators()))
|
||||
require.NoError(t, beaconState.SetSlot(slot))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = slot
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
gen := stategen.New(beaconDB, doublylinkedtree.New())
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
// Test non exist public key.
|
||||
request := &structs.GetIndividualVotesRequest{
|
||||
PublicKeys: []string{"0xaa"},
|
||||
Epoch: "0",
|
||||
}
|
||||
errStr, resp := individualVotesHelper(t, request, s)
|
||||
require.Equal(t, "", errStr)
|
||||
want := &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: []*structs.IndividualVote{
|
||||
{
|
||||
Epoch: "0",
|
||||
PublicKey: "0xaa",
|
||||
ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)),
|
||||
CurrentEpochEffectiveBalanceGwei: "0",
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
|
||||
// Test non-existent validator index.
|
||||
request = &structs.GetIndividualVotesRequest{
|
||||
Indices: []string{"100"},
|
||||
Epoch: "0",
|
||||
}
|
||||
errStr, resp = individualVotesHelper(t, request, s)
|
||||
require.Equal(t, "", errStr)
|
||||
want = &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: []*structs.IndividualVote{
|
||||
{
|
||||
Epoch: "0",
|
||||
PublicKey: "0x",
|
||||
ValidatorIndex: "100",
|
||||
CurrentEpochEffectiveBalanceGwei: "0",
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
|
||||
// Test both.
|
||||
request = &structs.GetIndividualVotesRequest{
|
||||
PublicKeys: []string{"0xaa", "0xbb"},
|
||||
Indices: []string{"100", "101"},
|
||||
Epoch: "0",
|
||||
}
|
||||
errStr, resp = individualVotesHelper(t, request, s)
|
||||
require.Equal(t, "", errStr)
|
||||
want = &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: []*structs.IndividualVote{
|
||||
{Epoch: "0", PublicKey: "0xaa", ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)), CurrentEpochEffectiveBalanceGwei: "0", InclusionSlot: "0", InclusionDistance: "0", InactivityScore: "0"},
|
||||
{
|
||||
Epoch: "0",
|
||||
PublicKey: "0xbb",
|
||||
ValidatorIndex: fmt.Sprintf("%d", ^uint64(0)),
|
||||
CurrentEpochEffectiveBalanceGwei: "0",
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
{
|
||||
Epoch: "0",
|
||||
PublicKey: "0x",
|
||||
ValidatorIndex: "100",
|
||||
CurrentEpochEffectiveBalanceGwei: "0",
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
{
|
||||
Epoch: "0",
|
||||
PublicKey: "0x",
|
||||
ValidatorIndex: "101",
|
||||
CurrentEpochEffectiveBalanceGwei: "0",
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
}
|
||||
|
||||
func TestServer_GetIndividualVotes_Working(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
validators := uint64(32)
|
||||
stateWithValidators, _ := util.DeterministicGenesisState(t, validators)
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetValidators(stateWithValidators.Validators()))
|
||||
|
||||
bf := bitfield.NewBitlist(validators / uint64(params.BeaconConfig().SlotsPerEpoch))
|
||||
att1 := util.NewAttestation()
|
||||
att1.AggregationBits = bf
|
||||
att2 := util.NewAttestation()
|
||||
att2.AggregationBits = bf
|
||||
rt := [32]byte{'A'}
|
||||
att1.Data.Target.Root = rt[:]
|
||||
att1.Data.BeaconBlockRoot = rt[:]
|
||||
br := beaconState.BlockRoots()
|
||||
newRt := [32]byte{'B'}
|
||||
br[0] = newRt[:]
|
||||
require.NoError(t, beaconState.SetBlockRoots(br))
|
||||
att2.Data.Target.Root = rt[:]
|
||||
att2.Data.BeaconBlockRoot = newRt[:]
|
||||
err = beaconState.AppendPreviousEpochAttestations(ðpb.PendingAttestation{
|
||||
Data: att1.Data, AggregationBits: bf, InclusionDelay: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = beaconState.AppendCurrentEpochAttestations(ðpb.PendingAttestation{
|
||||
Data: att2.Data, AggregationBits: bf, InclusionDelay: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = 0
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
gen := stategen.New(beaconDB, doublylinkedtree.New())
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
request := &structs.GetIndividualVotesRequest{
|
||||
Indices: []string{"0", "1"},
|
||||
Epoch: "0",
|
||||
}
|
||||
errStr, resp := individualVotesHelper(t, request, s)
|
||||
require.Equal(t, "", errStr)
|
||||
want := &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: []*structs.IndividualVote{
|
||||
{
|
||||
Epoch: "0",
|
||||
ValidatorIndex: "0",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot),
|
||||
InclusionDistance: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot),
|
||||
InactivityScore: "0",
|
||||
},
|
||||
{
|
||||
Epoch: "0",
|
||||
ValidatorIndex: "1",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot),
|
||||
InclusionDistance: fmt.Sprintf("%d", params.BeaconConfig().FarFutureSlot),
|
||||
InactivityScore: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
}
|
||||
|
||||
func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
var slot primitives.Slot = 0
|
||||
validators := uint64(32)
|
||||
beaconState, _ := util.DeterministicGenesisStateAltair(t, validators)
|
||||
require.NoError(t, beaconState.SetSlot(slot))
|
||||
|
||||
pb, err := beaconState.CurrentEpochParticipation()
|
||||
require.NoError(t, err)
|
||||
for i := range pb {
|
||||
pb[i] = 0xff
|
||||
}
|
||||
require.NoError(t, beaconState.SetCurrentParticipationBits(pb))
|
||||
require.NoError(t, beaconState.SetPreviousParticipationBits(pb))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = slot
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
gen := stategen.New(beaconDB, doublylinkedtree.New())
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
request := &structs.GetIndividualVotesRequest{
|
||||
Indices: []string{"0", "1"},
|
||||
Epoch: "0",
|
||||
}
|
||||
errStr, resp := individualVotesHelper(t, request, s)
|
||||
require.Equal(t, "", errStr)
|
||||
want := &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: []*structs.IndividualVote{
|
||||
{
|
||||
Epoch: "0",
|
||||
ValidatorIndex: "0",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
IsCurrentEpochTargetAttester: true,
|
||||
IsCurrentEpochAttester: true,
|
||||
IsPreviousEpochAttester: true,
|
||||
IsPreviousEpochHeadAttester: true,
|
||||
IsPreviousEpochTargetAttester: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
{
|
||||
Epoch: "0",
|
||||
ValidatorIndex: "1",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
IsCurrentEpochTargetAttester: true,
|
||||
IsCurrentEpochAttester: true,
|
||||
IsPreviousEpochAttester: true,
|
||||
IsPreviousEpochHeadAttester: true,
|
||||
IsPreviousEpochTargetAttester: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
}
|
||||
|
||||
func TestServer_GetIndividualVotes_AltairEndOfEpoch(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.BeaconConfig())
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
validators := uint64(32)
|
||||
beaconState, _ := util.DeterministicGenesisStateAltair(t, validators)
|
||||
startSlot, err := slots.EpochStart(1)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetSlot(startSlot))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = startSlot
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
gen := stategen.New(beaconDB, doublylinkedtree.New())
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
// Save State at the end of the epoch:
|
||||
endSlot, err := slots.EpochEnd(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
beaconState, _ = util.DeterministicGenesisStateAltair(t, validators)
|
||||
require.NoError(t, beaconState.SetSlot(endSlot))
|
||||
|
||||
pb, err := beaconState.CurrentEpochParticipation()
|
||||
require.NoError(t, err)
|
||||
for i := range pb {
|
||||
pb[i] = 0xff
|
||||
}
|
||||
require.NoError(t, beaconState.SetCurrentParticipationBits(pb))
|
||||
require.NoError(t, beaconState.SetPreviousParticipationBits(pb))
|
||||
|
||||
b.Block.Slot = endSlot
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
request := &structs.GetIndividualVotesRequest{
|
||||
Indices: []string{"0", "1"},
|
||||
Epoch: "1",
|
||||
}
|
||||
errStr, resp := individualVotesHelper(t, request, s)
|
||||
require.Equal(t, "", errStr)
|
||||
want := &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: []*structs.IndividualVote{
|
||||
{
|
||||
Epoch: "1",
|
||||
ValidatorIndex: "0",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
IsCurrentEpochTargetAttester: true,
|
||||
IsCurrentEpochAttester: true,
|
||||
IsPreviousEpochAttester: true,
|
||||
IsPreviousEpochHeadAttester: true,
|
||||
IsPreviousEpochTargetAttester: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
{
|
||||
Epoch: "1",
|
||||
ValidatorIndex: "1",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
IsCurrentEpochTargetAttester: true,
|
||||
IsCurrentEpochAttester: true,
|
||||
IsPreviousEpochAttester: true,
|
||||
IsPreviousEpochHeadAttester: true,
|
||||
IsPreviousEpochTargetAttester: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
}
|
||||
|
||||
func TestServer_GetIndividualVotes_BellatrixEndOfEpoch(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.BeaconConfig())
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
validators := uint64(32)
|
||||
beaconState, _ := util.DeterministicGenesisStateBellatrix(t, validators)
|
||||
startSlot, err := slots.EpochStart(1)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetSlot(startSlot))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = startSlot
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
gen := stategen.New(beaconDB, doublylinkedtree.New())
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
// Save State at the end of the epoch:
|
||||
endSlot, err := slots.EpochEnd(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
beaconState, _ = util.DeterministicGenesisStateBellatrix(t, validators)
|
||||
require.NoError(t, beaconState.SetSlot(endSlot))
|
||||
|
||||
pb, err := beaconState.CurrentEpochParticipation()
|
||||
require.NoError(t, err)
|
||||
for i := range pb {
|
||||
pb[i] = 0xff
|
||||
}
|
||||
require.NoError(t, beaconState.SetCurrentParticipationBits(pb))
|
||||
require.NoError(t, beaconState.SetPreviousParticipationBits(pb))
|
||||
|
||||
b.Block.Slot = endSlot
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
request := &structs.GetIndividualVotesRequest{
|
||||
Indices: []string{"0", "1"},
|
||||
Epoch: "1",
|
||||
}
|
||||
errStr, resp := individualVotesHelper(t, request, s)
|
||||
require.Equal(t, "", errStr)
|
||||
want := &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: []*structs.IndividualVote{
|
||||
{
|
||||
Epoch: "1",
|
||||
ValidatorIndex: "0",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
IsCurrentEpochTargetAttester: true,
|
||||
IsCurrentEpochAttester: true,
|
||||
IsPreviousEpochAttester: true,
|
||||
IsPreviousEpochHeadAttester: true,
|
||||
IsPreviousEpochTargetAttester: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
{
|
||||
Epoch: "1",
|
||||
ValidatorIndex: "1",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
IsCurrentEpochTargetAttester: true,
|
||||
IsCurrentEpochAttester: true,
|
||||
IsPreviousEpochAttester: true,
|
||||
IsPreviousEpochHeadAttester: true,
|
||||
IsPreviousEpochTargetAttester: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
}
|
||||
|
||||
func TestServer_GetIndividualVotes_CapellaEndOfEpoch(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.BeaconConfig())
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
validators := uint64(32)
|
||||
beaconState, _ := util.DeterministicGenesisStateCapella(t, validators)
|
||||
startSlot, err := slots.EpochStart(1)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetSlot(startSlot))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = startSlot
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
gen := stategen.New(beaconDB, doublylinkedtree.New())
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
// Save State at the end of the epoch:
|
||||
endSlot, err := slots.EpochEnd(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
beaconState, _ = util.DeterministicGenesisStateCapella(t, validators)
|
||||
require.NoError(t, beaconState.SetSlot(endSlot))
|
||||
|
||||
pb, err := beaconState.CurrentEpochParticipation()
|
||||
require.NoError(t, err)
|
||||
for i := range pb {
|
||||
pb[i] = 0xff
|
||||
}
|
||||
require.NoError(t, beaconState.SetCurrentParticipationBits(pb))
|
||||
require.NoError(t, beaconState.SetPreviousParticipationBits(pb))
|
||||
|
||||
b.Block.Slot = endSlot
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
gRoot, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
request := &structs.GetIndividualVotesRequest{
|
||||
Indices: []string{"0", "1"},
|
||||
Epoch: "1",
|
||||
}
|
||||
errStr, resp := individualVotesHelper(t, request, s)
|
||||
require.Equal(t, "", errStr)
|
||||
want := &structs.GetIndividualVotesResponse{
|
||||
IndividualVotes: []*structs.IndividualVote{
|
||||
{
|
||||
Epoch: "1",
|
||||
ValidatorIndex: "0",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[0].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
IsCurrentEpochTargetAttester: true,
|
||||
IsCurrentEpochAttester: true,
|
||||
IsPreviousEpochAttester: true,
|
||||
IsPreviousEpochHeadAttester: true,
|
||||
IsPreviousEpochTargetAttester: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
{
|
||||
Epoch: "1",
|
||||
ValidatorIndex: "1",
|
||||
PublicKey: hexutil.Encode(beaconState.Validators()[1].PublicKey),
|
||||
IsActiveInCurrentEpoch: true,
|
||||
IsActiveInPreviousEpoch: true,
|
||||
IsCurrentEpochTargetAttester: true,
|
||||
IsCurrentEpochAttester: true,
|
||||
IsPreviousEpochAttester: true,
|
||||
IsPreviousEpochHeadAttester: true,
|
||||
IsPreviousEpochTargetAttester: true,
|
||||
CurrentEpochEffectiveBalanceGwei: fmt.Sprintf("%d", params.BeaconConfig().MaxEffectiveBalance),
|
||||
InclusionSlot: "0",
|
||||
InclusionDistance: "0",
|
||||
InactivityScore: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.DeepEqual(t, want, resp, "Unexpected response")
|
||||
}
|
||||
|
||||
// ensures that if any of the checkpoints are zero-valued, an error will be generated without genesis being present
|
||||
func TestServer_GetChainHead_NoGenesis(t *testing.T) {
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
s, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.SetSlot(1))
|
||||
|
||||
genBlock := util.NewBeaconBlock()
|
||||
genBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'G'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, genBlock)
|
||||
gRoot, err := genBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
cases := []struct {
|
||||
name string
|
||||
zeroSetter func(val *ethpb.Checkpoint) error
|
||||
}{
|
||||
{
|
||||
name: "zero-value prev justified",
|
||||
zeroSetter: s.SetPreviousJustifiedCheckpoint,
|
||||
},
|
||||
{
|
||||
name: "zero-value current justified",
|
||||
zeroSetter: s.SetCurrentJustifiedCheckpoint,
|
||||
},
|
||||
{
|
||||
name: "zero-value finalized",
|
||||
zeroSetter: s.SetFinalizedCheckpoint,
|
||||
},
|
||||
}
|
||||
finalized := ðpb.Checkpoint{Epoch: 1, Root: gRoot[:]}
|
||||
prevJustified := ðpb.Checkpoint{Epoch: 2, Root: gRoot[:]}
|
||||
justified := ðpb.Checkpoint{Epoch: 3, Root: gRoot[:]}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
require.NoError(t, s.SetPreviousJustifiedCheckpoint(prevJustified))
|
||||
require.NoError(t, s.SetCurrentJustifiedCheckpoint(justified))
|
||||
require.NoError(t, s.SetFinalizedCheckpoint(finalized))
|
||||
require.NoError(t, c.zeroSetter(ðpb.Checkpoint{Epoch: 0, Root: params.BeaconConfig().ZeroHash[:]}))
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(genBlock)
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint(),
|
||||
},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetChainHead(writer, request)
|
||||
require.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
require.StringContains(t, "could not get genesis block", writer.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead_NoFinalizedBlock(t *testing.T) {
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
bs, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, bs.SetSlot(1))
|
||||
require.NoError(t, bs.SetPreviousJustifiedCheckpoint(ðpb.Checkpoint{Epoch: 3, Root: bytesutil.PadTo([]byte{'A'}, fieldparams.RootLength)}))
|
||||
require.NoError(t, bs.SetCurrentJustifiedCheckpoint(ðpb.Checkpoint{Epoch: 2, Root: bytesutil.PadTo([]byte{'B'}, fieldparams.RootLength)}))
|
||||
require.NoError(t, bs.SetFinalizedCheckpoint(ðpb.Checkpoint{Epoch: 1, Root: bytesutil.PadTo([]byte{'C'}, fieldparams.RootLength)}))
|
||||
|
||||
genBlock := util.NewBeaconBlock()
|
||||
genBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'G'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, genBlock)
|
||||
gRoot, err := genBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(context.Background(), gRoot))
|
||||
|
||||
wsb, err := blocks.NewSignedBeaconBlock(genBlock)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: bs},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: bs.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: bs.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: bs.PreviousJustifiedCheckpoint()},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetChainHead(writer, request)
|
||||
require.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
require.StringContains(t, "ould not get finalized block", writer.Body.String())
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead_NoHeadBlock(t *testing.T) {
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: &chainMock.ChainService{Block: nil},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetChainHead(writer, request)
|
||||
require.Equal(t, http.StatusNotFound, writer.Code)
|
||||
require.StringContains(t, "head block of chain was nil", writer.Body.String())
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.MinimalSpecConfig())
|
||||
|
||||
db := dbTest.SetupDB(t)
|
||||
genBlock := util.NewBeaconBlock()
|
||||
genBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'G'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, genBlock)
|
||||
gRoot, err := genBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(context.Background(), gRoot))
|
||||
|
||||
finalizedBlock := util.NewBeaconBlock()
|
||||
finalizedBlock.Block.Slot = 1
|
||||
finalizedBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'A'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, finalizedBlock)
|
||||
fRoot, err := finalizedBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
justifiedBlock := util.NewBeaconBlock()
|
||||
justifiedBlock.Block.Slot = 2
|
||||
justifiedBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'B'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, justifiedBlock)
|
||||
jRoot, err := justifiedBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
prevJustifiedBlock := util.NewBeaconBlock()
|
||||
prevJustifiedBlock.Block.Slot = 3
|
||||
prevJustifiedBlock.Block.ParentRoot = bytesutil.PadTo([]byte{'C'}, fieldparams.RootLength)
|
||||
util.SaveBlock(t, context.Background(), db, prevJustifiedBlock)
|
||||
pjRoot, err := prevJustifiedBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
st, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{
|
||||
Slot: 1,
|
||||
PreviousJustifiedCheckpoint: ðpb.Checkpoint{Epoch: 3, Root: pjRoot[:]},
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Epoch: 2, Root: jRoot[:]},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Epoch: 1, Root: fRoot[:]},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot, err = slots.EpochStart(st.PreviousJustifiedCheckpoint().Epoch)
|
||||
require.NoError(t, err)
|
||||
b.Block.Slot++
|
||||
wsb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: st},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: st.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: st.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: st.PreviousJustifiedCheckpoint()},
|
||||
},
|
||||
}
|
||||
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetChainHead(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
var ch *structs.ChainHead
|
||||
err = json.NewDecoder(writer.Body).Decode(&ch)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "3", ch.PreviousJustifiedEpoch, "Unexpected PreviousJustifiedEpoch")
|
||||
assert.Equal(t, "2", ch.JustifiedEpoch, "Unexpected JustifiedEpoch")
|
||||
assert.Equal(t, "1", ch.FinalizedEpoch, "Unexpected FinalizedEpoch")
|
||||
assert.Equal(t, "24", ch.PreviousJustifiedSlot, "Unexpected PreviousJustifiedSlot")
|
||||
assert.Equal(t, "16", ch.JustifiedSlot, "Unexpected JustifiedSlot")
|
||||
assert.Equal(t, "8", ch.FinalizedSlot, "Unexpected FinalizedSlot")
|
||||
assert.DeepEqual(t, hexutil.Encode(pjRoot[:]), ch.PreviousJustifiedBlockRoot, "Unexpected PreviousJustifiedBlockRoot")
|
||||
assert.DeepEqual(t, hexutil.Encode(jRoot[:]), ch.JustifiedBlockRoot, "Unexpected JustifiedBlockRoot")
|
||||
assert.DeepEqual(t, hexutil.Encode(fRoot[:]), ch.FinalizedBlockRoot, "Unexpected FinalizedBlockRoot")
|
||||
assert.Equal(t, false, ch.OptimisticStatus)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package beacon
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
beacondb "github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync"
|
||||
@@ -18,4 +19,5 @@ type Server struct {
|
||||
Stater lookup.Stater
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
CoreService *core.Service
|
||||
}
|
||||
|
||||
@@ -19,15 +19,12 @@ go_library(
|
||||
"//api/pagination:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/feed/block:go_default_library",
|
||||
"//beacon-chain/core/feed/operation:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/core/validators:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/filters:go_default_library",
|
||||
"//beacon-chain/execution:go_default_library",
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const errEpoch = "Cannot retrieve information about an epoch in the future, current epoch %d, requesting %d"
|
||||
const errEpoch = "cannot retrieve information about an epoch in the future, current epoch %d, requesting %d"
|
||||
|
||||
// ListValidatorAssignments retrieves the validator assignments for a given epoch,
|
||||
// optional validator indices or public keys may be included to filter validator assignments.
|
||||
|
||||
@@ -7,13 +7,12 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/pagination"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filters"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -244,91 +243,9 @@ func (bs *Server) listBlocksForGenesis(ctx context.Context, _ *ethpb.ListBlocksR
|
||||
// the most recent finalized and justified slots.
|
||||
// DEPRECATED: This endpoint is superseded by the /eth/v1/beacon API endpoint
|
||||
func (bs *Server) GetChainHead(ctx context.Context, _ *emptypb.Empty) (*ethpb.ChainHead, error) {
|
||||
return bs.chainHeadRetrieval(ctx)
|
||||
}
|
||||
|
||||
// Retrieve chain head information from the DB and the current beacon state.
|
||||
func (bs *Server) chainHeadRetrieval(ctx context.Context) (*ethpb.ChainHead, error) {
|
||||
headBlock, err := bs.HeadFetcher.HeadBlock(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get head block")
|
||||
}
|
||||
optimisticStatus, err := bs.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Could not get optimistic status")
|
||||
}
|
||||
if err := consensusblocks.BeaconBlockIsNil(headBlock); err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "Head block of chain was nil: %v", err)
|
||||
}
|
||||
headBlockRoot, err := headBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get head block root: %v", err)
|
||||
}
|
||||
|
||||
validGenesis := false
|
||||
validateCP := func(cp *ethpb.Checkpoint, name string) error {
|
||||
if bytesutil.ToBytes32(cp.Root) == params.BeaconConfig().ZeroHash && cp.Epoch == 0 {
|
||||
if validGenesis {
|
||||
return nil
|
||||
}
|
||||
// Retrieve genesis block in the event we have genesis checkpoints.
|
||||
genBlock, err := bs.BeaconDB.GenesisBlock(ctx)
|
||||
if err != nil || consensusblocks.BeaconBlockIsNil(genBlock) != nil {
|
||||
return status.Error(codes.Internal, "Could not get genesis block")
|
||||
}
|
||||
validGenesis = true
|
||||
return nil
|
||||
}
|
||||
b, err := bs.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "Could not get %s block: %v", name, err)
|
||||
}
|
||||
if err := consensusblocks.BeaconBlockIsNil(b); err != nil {
|
||||
return status.Errorf(codes.Internal, "Could not get %s block: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
finalizedCheckpoint := bs.FinalizationFetcher.FinalizedCheckpt()
|
||||
if err := validateCP(finalizedCheckpoint, "finalized"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
justifiedCheckpoint := bs.FinalizationFetcher.CurrentJustifiedCheckpt()
|
||||
if err := validateCP(justifiedCheckpoint, "justified"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prevJustifiedCheckpoint := bs.FinalizationFetcher.PreviousJustifiedCheckpt()
|
||||
if err := validateCP(prevJustifiedCheckpoint, "prev justified"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fSlot, err := slots.EpochStart(finalizedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get epoch start slot from finalized checkpoint epoch")
|
||||
}
|
||||
jSlot, err := slots.EpochStart(justifiedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get epoch start slot from justified checkpoint epoch")
|
||||
}
|
||||
pjSlot, err := slots.EpochStart(prevJustifiedCheckpoint.Epoch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get epoch start slot from prev justified checkpoint epoch")
|
||||
}
|
||||
return ðpb.ChainHead{
|
||||
HeadSlot: headBlock.Block().Slot(),
|
||||
HeadEpoch: slots.ToEpoch(headBlock.Block().Slot()),
|
||||
HeadBlockRoot: headBlockRoot[:],
|
||||
FinalizedSlot: fSlot,
|
||||
FinalizedEpoch: finalizedCheckpoint.Epoch,
|
||||
FinalizedBlockRoot: finalizedCheckpoint.Root,
|
||||
JustifiedSlot: jSlot,
|
||||
JustifiedEpoch: justifiedCheckpoint.Epoch,
|
||||
JustifiedBlockRoot: justifiedCheckpoint.Root,
|
||||
PreviousJustifiedSlot: pjSlot,
|
||||
PreviousJustifiedEpoch: prevJustifiedCheckpoint.Epoch,
|
||||
PreviousJustifiedBlockRoot: prevJustifiedCheckpoint.Root,
|
||||
OptimisticStatus: optimisticStatus,
|
||||
}, nil
|
||||
ch, err := bs.CoreService.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(core.ErrorReasonToGRPC(err.Reason), "Could not retrieve chain head: %v", err.Err)
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
@@ -68,16 +69,19 @@ func TestServer_GetChainHead_NoGenesis(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(genBlock)
|
||||
require.NoError(t, err)
|
||||
bs := &Server{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizationFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint(),
|
||||
},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
_, err = bs.GetChainHead(context.Background(), nil)
|
||||
require.ErrorContains(t, "Could not get genesis block", err)
|
||||
require.ErrorContains(t, "could not get genesis block", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,26 +106,30 @@ func TestServer_GetChainHead_NoFinalizedBlock(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
bs := &Server{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizationFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = bs.GetChainHead(context.Background(), nil)
|
||||
require.ErrorContains(t, "Could not get finalized block", err)
|
||||
require.ErrorContains(t, "could not get finalized block", err)
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead_NoHeadBlock(t *testing.T) {
|
||||
bs := &Server{
|
||||
HeadFetcher: &chainMock.ChainService{Block: nil},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: &chainMock.ChainService{Block: nil},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
},
|
||||
}
|
||||
_, err := bs.GetChainHead(context.Background(), nil)
|
||||
assert.ErrorContains(t, "Head block of chain was nil", err)
|
||||
assert.ErrorContains(t, "head block of chain was nil", err)
|
||||
}
|
||||
|
||||
func TestServer_GetChainHead(t *testing.T) {
|
||||
@@ -172,13 +180,15 @@ func TestServer_GetChainHead(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
bs := &Server{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
FinalizationFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: db,
|
||||
HeadFetcher: &chainMock.ChainService{Block: wsb, State: s},
|
||||
OptimisticModeFetcher: &chainMock.ChainService{},
|
||||
FinalizedFetcher: &chainMock.ChainService{
|
||||
FinalizedCheckPoint: s.FinalizedCheckpoint(),
|
||||
CurrentJustifiedCheckPoint: s.CurrentJustifiedCheckpoint(),
|
||||
PreviousJustifiedCheckPoint: s.PreviousJustifiedCheckpoint()},
|
||||
},
|
||||
}
|
||||
|
||||
head, err := bs.GetChainHead(context.Background(), nil)
|
||||
|
||||
@@ -79,6 +79,9 @@ func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) {
|
||||
cc := &mockstategen.CanonicalChecker{Is: true, Err: nil}
|
||||
cs := &mockstategen.CurrentSlotter{Slot: math.MaxUint64 - 1}
|
||||
s.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs)
|
||||
if s.CoreService != nil {
|
||||
s.CoreService.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListBeaconCommittees_PreviousEpoch(t *testing.T) {
|
||||
|
||||
@@ -7,12 +7,9 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/api/pagination"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epoch/precompute"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
coreTime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd"
|
||||
@@ -392,7 +389,7 @@ func (bs *Server) GetValidator(
|
||||
func (bs *Server) GetValidatorActiveSetChanges(
|
||||
ctx context.Context, req *ethpb.GetValidatorActiveSetChangesRequest,
|
||||
) (*ethpb.ActiveSetChanges, error) {
|
||||
currentEpoch := slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot())
|
||||
currentEpoch := slots.ToEpoch(bs.CoreService.GenesisTimeFetcher.CurrentSlot())
|
||||
|
||||
var requestedEpoch primitives.Epoch
|
||||
switch q := req.QueryFilter.(type) {
|
||||
@@ -403,72 +400,12 @@ func (bs *Server) GetValidatorActiveSetChanges(
|
||||
default:
|
||||
requestedEpoch = currentEpoch
|
||||
}
|
||||
if requestedEpoch > currentEpoch {
|
||||
return nil, status.Errorf(
|
||||
codes.InvalidArgument,
|
||||
errEpoch,
|
||||
currentEpoch,
|
||||
requestedEpoch,
|
||||
)
|
||||
}
|
||||
|
||||
s, err := slots.EpochStart(requestedEpoch)
|
||||
as, err := bs.CoreService.ValidatorActiveSetChanges(ctx, requestedEpoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, status.Errorf(core.ErrorReasonToGRPC(err.Reason), "Could not retrieve validator active set changes: %v", err.Err)
|
||||
}
|
||||
requestedState, err := bs.ReplayerBuilder.ReplayerForSlot(s).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("error replaying blocks for state at slot %d: %v", s, err))
|
||||
}
|
||||
|
||||
activeValidatorCount, err := helpers.ActiveValidatorCount(ctx, requestedState, coreTime.CurrentEpoch(requestedState))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get active validator count: %v", err)
|
||||
}
|
||||
vs := requestedState.Validators()
|
||||
activatedIndices := validators.ActivatedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs)
|
||||
exitedIndices, err := validators.ExitedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs, activeValidatorCount)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not determine exited validator indices: %v", err)
|
||||
}
|
||||
slashedIndices := validators.SlashedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs)
|
||||
ejectedIndices, err := validators.EjectedValidatorIndices(coreTime.CurrentEpoch(requestedState), vs, activeValidatorCount)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not determine ejected validator indices: %v", err)
|
||||
}
|
||||
|
||||
// Retrieve public keys for the indices.
|
||||
activatedKeys := make([][]byte, len(activatedIndices))
|
||||
exitedKeys := make([][]byte, len(exitedIndices))
|
||||
slashedKeys := make([][]byte, len(slashedIndices))
|
||||
ejectedKeys := make([][]byte, len(ejectedIndices))
|
||||
for i, idx := range activatedIndices {
|
||||
pubkey := requestedState.PubkeyAtIndex(idx)
|
||||
activatedKeys[i] = pubkey[:]
|
||||
}
|
||||
for i, idx := range exitedIndices {
|
||||
pubkey := requestedState.PubkeyAtIndex(idx)
|
||||
exitedKeys[i] = pubkey[:]
|
||||
}
|
||||
for i, idx := range slashedIndices {
|
||||
pubkey := requestedState.PubkeyAtIndex(idx)
|
||||
slashedKeys[i] = pubkey[:]
|
||||
}
|
||||
for i, idx := range ejectedIndices {
|
||||
pubkey := requestedState.PubkeyAtIndex(idx)
|
||||
ejectedKeys[i] = pubkey[:]
|
||||
}
|
||||
return ðpb.ActiveSetChanges{
|
||||
Epoch: requestedEpoch,
|
||||
ActivatedPublicKeys: activatedKeys,
|
||||
ActivatedIndices: activatedIndices,
|
||||
ExitedPublicKeys: exitedKeys,
|
||||
ExitedIndices: exitedIndices,
|
||||
SlashedPublicKeys: slashedKeys,
|
||||
SlashedIndices: slashedIndices,
|
||||
EjectedPublicKeys: ejectedKeys,
|
||||
EjectedIndices: ejectedIndices,
|
||||
}, nil
|
||||
return as, nil
|
||||
}
|
||||
|
||||
// GetValidatorParticipation retrieves the validator participation information for a given epoch,
|
||||
@@ -477,7 +414,7 @@ func (bs *Server) GetValidatorActiveSetChanges(
|
||||
func (bs *Server) GetValidatorParticipation(
|
||||
ctx context.Context, req *ethpb.GetValidatorParticipationRequest,
|
||||
) (*ethpb.ValidatorParticipationResponse, error) {
|
||||
currentSlot := bs.GenesisTimeFetcher.CurrentSlot()
|
||||
currentSlot := bs.CoreService.GenesisTimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(currentSlot)
|
||||
|
||||
var requestedEpoch primitives.Epoch
|
||||
@@ -489,79 +426,11 @@ func (bs *Server) GetValidatorParticipation(
|
||||
default:
|
||||
requestedEpoch = currentEpoch
|
||||
}
|
||||
|
||||
if requestedEpoch > currentEpoch {
|
||||
return nil, status.Errorf(
|
||||
codes.InvalidArgument,
|
||||
"Cannot retrieve information about an epoch greater than current epoch, current epoch %d, requesting %d",
|
||||
currentEpoch,
|
||||
requestedEpoch,
|
||||
)
|
||||
}
|
||||
// Use the last slot of requested epoch to obtain current and previous epoch attestations.
|
||||
// This ensures that we don't miss previous attestations when input requested epochs.
|
||||
endSlot, err := slots.EpochEnd(requestedEpoch)
|
||||
vp, err := bs.CoreService.ValidatorParticipation(ctx, requestedEpoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, status.Errorf(core.ErrorReasonToGRPC(err.Reason), "Could not retrieve validator participation: %v", err.Err)
|
||||
}
|
||||
// Get as close as we can to the end of the current epoch without going past the current slot.
|
||||
// The above check ensures a future *epoch* isn't requested, but the end slot of the requested epoch could still
|
||||
// be past the current slot. In that case, use the current slot as the best approximation of the requested epoch.
|
||||
// Replayer will make sure the slot ultimately used is canonical.
|
||||
if endSlot > currentSlot {
|
||||
endSlot = currentSlot
|
||||
}
|
||||
|
||||
// ReplayerBuilder ensures that a canonical chain is followed to the slot
|
||||
beaconState, err := bs.ReplayerBuilder.ReplayerForSlot(endSlot).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("error replaying blocks for state at slot %d: %v", endSlot, err))
|
||||
}
|
||||
var v []*precompute.Validator
|
||||
var b *precompute.Balance
|
||||
|
||||
if beaconState.Version() == version.Phase0 {
|
||||
v, b, err = precompute.New(ctx, beaconState)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set up pre compute instance: %v", err)
|
||||
}
|
||||
_, b, err = precompute.ProcessAttestations(ctx, beaconState, v, b)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
||||
}
|
||||
} else if beaconState.Version() >= version.Altair {
|
||||
v, b, err = altair.InitializePrecomputeValidators(ctx, beaconState)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set up altair pre compute instance: %v", err)
|
||||
}
|
||||
_, b, err = altair.ProcessEpochParticipation(ctx, beaconState, b, v)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
||||
}
|
||||
} else {
|
||||
return nil, status.Errorf(codes.Internal, "Invalid state type retrieved with a version of %d", beaconState.Version())
|
||||
}
|
||||
|
||||
cp := bs.FinalizationFetcher.FinalizedCheckpt()
|
||||
p := ðpb.ValidatorParticipationResponse{
|
||||
Epoch: requestedEpoch,
|
||||
Finalized: requestedEpoch <= cp.Epoch,
|
||||
Participation: ðpb.ValidatorParticipation{
|
||||
// TODO(7130): Remove these three deprecated fields.
|
||||
GlobalParticipationRate: float32(b.PrevEpochTargetAttested) / float32(b.ActivePrevEpoch),
|
||||
VotedEther: b.PrevEpochTargetAttested,
|
||||
EligibleEther: b.ActivePrevEpoch,
|
||||
CurrentEpochActiveGwei: b.ActiveCurrentEpoch,
|
||||
CurrentEpochAttestingGwei: b.CurrentEpochAttested,
|
||||
CurrentEpochTargetAttestingGwei: b.CurrentEpochTargetAttested,
|
||||
PreviousEpochActiveGwei: b.ActivePrevEpoch,
|
||||
PreviousEpochAttestingGwei: b.PrevEpochAttested,
|
||||
PreviousEpochTargetAttestingGwei: b.PrevEpochTargetAttested,
|
||||
PreviousEpochHeadAttestingGwei: b.PrevEpochHeadAttested,
|
||||
},
|
||||
}
|
||||
|
||||
return p, nil
|
||||
return vp, nil
|
||||
}
|
||||
|
||||
// GetValidatorQueue retrieves the current validator queue information.
|
||||
@@ -672,105 +541,11 @@ func (bs *Server) GetIndividualVotes(
|
||||
ctx context.Context,
|
||||
req *ethpb.IndividualVotesRequest,
|
||||
) (*ethpb.IndividualVotesRespond, error) {
|
||||
currentEpoch := slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot())
|
||||
if req.Epoch > currentEpoch {
|
||||
return nil, status.Errorf(
|
||||
codes.InvalidArgument,
|
||||
errEpoch,
|
||||
currentEpoch,
|
||||
req.Epoch,
|
||||
)
|
||||
}
|
||||
|
||||
s, err := slots.EpochEnd(req.Epoch)
|
||||
response, err := bs.CoreService.IndividualVotes(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, status.Errorf(core.ErrorReasonToGRPC(err.Reason), "Could not retrieve individual votes: %v", err.Err)
|
||||
}
|
||||
st, err := bs.ReplayerBuilder.ReplayerForSlot(s).ReplayBlocks(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to replay blocks for state at epoch %d: %v", req.Epoch, err)
|
||||
}
|
||||
// Track filtered validators to prevent duplication in the response.
|
||||
filtered := map[primitives.ValidatorIndex]bool{}
|
||||
filteredIndices := make([]primitives.ValidatorIndex, 0)
|
||||
votes := make([]*ethpb.IndividualVotesRespond_IndividualVote, 0, len(req.Indices)+len(req.PublicKeys))
|
||||
// Filter out assignments by public keys.
|
||||
for _, pubKey := range req.PublicKeys {
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
||||
if !ok {
|
||||
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{PublicKey: pubKey, ValidatorIndex: primitives.ValidatorIndex(^uint64(0))})
|
||||
continue
|
||||
}
|
||||
filtered[index] = true
|
||||
filteredIndices = append(filteredIndices, index)
|
||||
}
|
||||
// Filter out assignments by validator indices.
|
||||
for _, index := range req.Indices {
|
||||
if !filtered[index] {
|
||||
filteredIndices = append(filteredIndices, index)
|
||||
}
|
||||
}
|
||||
sort.Slice(filteredIndices, func(i, j int) bool {
|
||||
return filteredIndices[i] < filteredIndices[j]
|
||||
})
|
||||
|
||||
var v []*precompute.Validator
|
||||
var bal *precompute.Balance
|
||||
if st.Version() == version.Phase0 {
|
||||
v, bal, err = precompute.New(ctx, st)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set up pre compute instance: %v", err)
|
||||
}
|
||||
v, _, err = precompute.ProcessAttestations(ctx, st, v, bal)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
||||
}
|
||||
} else if st.Version() >= version.Altair {
|
||||
v, bal, err = altair.InitializePrecomputeValidators(ctx, st)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set up altair pre compute instance: %v", err)
|
||||
}
|
||||
v, _, err = altair.ProcessEpochParticipation(ctx, st, bal, v)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not pre compute attestations: %v", err)
|
||||
}
|
||||
} else {
|
||||
return nil, status.Errorf(codes.Internal, "Invalid state type retrieved with a version of %d", st.Version())
|
||||
}
|
||||
|
||||
for _, index := range filteredIndices {
|
||||
if uint64(index) >= uint64(len(v)) {
|
||||
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{ValidatorIndex: index})
|
||||
continue
|
||||
}
|
||||
val, err := st.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not retrieve validator: %v", err)
|
||||
}
|
||||
pb := val.PublicKey()
|
||||
votes = append(votes, ðpb.IndividualVotesRespond_IndividualVote{
|
||||
Epoch: req.Epoch,
|
||||
PublicKey: pb[:],
|
||||
ValidatorIndex: index,
|
||||
IsSlashed: v[index].IsSlashed,
|
||||
IsWithdrawableInCurrentEpoch: v[index].IsWithdrawableCurrentEpoch,
|
||||
IsActiveInCurrentEpoch: v[index].IsActiveCurrentEpoch,
|
||||
IsActiveInPreviousEpoch: v[index].IsActivePrevEpoch,
|
||||
IsCurrentEpochAttester: v[index].IsCurrentEpochAttester,
|
||||
IsCurrentEpochTargetAttester: v[index].IsCurrentEpochTargetAttester,
|
||||
IsPreviousEpochAttester: v[index].IsPrevEpochAttester,
|
||||
IsPreviousEpochTargetAttester: v[index].IsPrevEpochTargetAttester,
|
||||
IsPreviousEpochHeadAttester: v[index].IsPrevEpochHeadAttester,
|
||||
CurrentEpochEffectiveBalanceGwei: v[index].CurrentEpochEffectiveBalance,
|
||||
InclusionSlot: v[index].InclusionSlot,
|
||||
InclusionDistance: v[index].InclusionDistance,
|
||||
InactivityScore: v[index].InactivityScore,
|
||||
})
|
||||
}
|
||||
|
||||
return ðpb.IndividualVotesRespond{
|
||||
IndividualVotes: votes,
|
||||
}, nil
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// Determines whether a validator has already exited.
|
||||
|
||||
@@ -44,7 +44,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
errNoEpochInfoError = "Cannot retrieve information about an epoch in the future"
|
||||
errNoEpochInfoError = "cannot retrieve information about an epoch in the future"
|
||||
)
|
||||
|
||||
func TestServer_GetValidatorActiveSetChanges_CannotRequestFutureEpoch(t *testing.T) {
|
||||
@@ -54,11 +54,13 @@ func TestServer_GetValidatorActiveSetChanges_CannotRequestFutureEpoch(t *testing
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetSlot(0))
|
||||
bs := &Server{
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: st,
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: beaconDB,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: st,
|
||||
},
|
||||
},
|
||||
BeaconDB: beaconDB,
|
||||
}
|
||||
|
||||
wanted := errNoEpochInfoError
|
||||
@@ -66,7 +68,7 @@ func TestServer_GetValidatorActiveSetChanges_CannotRequestFutureEpoch(t *testing
|
||||
ctx,
|
||||
ðpb.GetValidatorActiveSetChangesRequest{
|
||||
QueryFilter: ðpb.GetValidatorActiveSetChangesRequest_Epoch{
|
||||
Epoch: slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot()) + 1,
|
||||
Epoch: slots.ToEpoch(bs.CoreService.GenesisTimeFetcher.CurrentSlot()) + 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -1029,7 +1031,7 @@ func TestServer_ListValidators_FromOldEpoch(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
slot := primitives.Slot(0)
|
||||
epochs := 10
|
||||
epochs := primitives.Epoch(10)
|
||||
numVals := uint64(10)
|
||||
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
@@ -1065,7 +1067,7 @@ func TestServer_ListValidators_FromOldEpoch(t *testing.T) {
|
||||
}
|
||||
res, err := bs.ListValidators(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, epochs, len(res.ValidatorList))
|
||||
assert.Equal(t, int(numVals), len(res.ValidatorList))
|
||||
|
||||
vals := st.Validators()
|
||||
want := make([]*ethpb.Validators_ValidatorContainer, 0)
|
||||
@@ -1077,7 +1079,7 @@ func TestServer_ListValidators_FromOldEpoch(t *testing.T) {
|
||||
}
|
||||
req = ðpb.ListValidatorsRequest{
|
||||
QueryFilter: ðpb.ListValidatorsRequest_Epoch{
|
||||
Epoch: 10,
|
||||
Epoch: epochs,
|
||||
},
|
||||
}
|
||||
res, err = bs.ListValidators(context.Background(), req)
|
||||
@@ -1283,10 +1285,12 @@ func TestServer_GetValidatorActiveSetChanges(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, gRoot))
|
||||
|
||||
bs := &Server{
|
||||
FinalizationFetcher: &mock.ChainService{
|
||||
FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 0, Root: make([]byte, fieldparams.RootLength)},
|
||||
CoreService: &core.Service{
|
||||
FinalizedFetcher: &mock.ChainService{
|
||||
FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 0, Root: make([]byte, fieldparams.RootLength)},
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
res, err := bs.GetValidatorActiveSetChanges(ctx, ðpb.GetValidatorActiveSetChangesRequest{
|
||||
@@ -1476,27 +1480,25 @@ func TestServer_GetValidatorQueue_PendingExit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorParticipation_CannotRequestFutureEpoch(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
headState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headState.SetSlot(0))
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
}
|
||||
|
||||
wanted := "Cannot retrieve information about an epoch"
|
||||
wanted := "cannot retrieve information about an epoch"
|
||||
_, err = bs.GetValidatorParticipation(
|
||||
ctx,
|
||||
ðpb.GetValidatorParticipationRequest{
|
||||
QueryFilter: ðpb.GetValidatorParticipationRequest_Epoch{
|
||||
Epoch: slots.ToEpoch(bs.GenesisTimeFetcher.CurrentSlot()) + 1,
|
||||
Epoch: slots.ToEpoch(bs.CoreService.GenesisTimeFetcher.CurrentSlot()) + 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -1549,18 +1551,20 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
|
||||
m := &mock.ChainService{State: headState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
HeadFetcher: m,
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
BeaconDB: beaconDB,
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: m,
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
FinalizedFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
},
|
||||
CanonicalFetcher: &mock.ChainService{
|
||||
CanonicalRoots: map[[32]byte]bool{
|
||||
bRoot: true,
|
||||
},
|
||||
},
|
||||
FinalizationFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
@@ -1628,18 +1632,20 @@ func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {
|
||||
m := &mock.ChainService{State: headState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
HeadFetcher: m,
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
BeaconDB: beaconDB,
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: m,
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
FinalizedFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
},
|
||||
CanonicalFetcher: &mock.ChainService{
|
||||
CanonicalRoots: map[[32]byte]bool{
|
||||
bRoot: true,
|
||||
},
|
||||
},
|
||||
FinalizationFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
@@ -1744,13 +1750,15 @@ func runGetValidatorParticipationCurrentAndPrevEpoch(t *testing.T, genState stat
|
||||
m := &mock.ChainService{State: genState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
bs := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
BeaconDB: beaconDB,
|
||||
CoreService: &core.Service{
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
FinalizedFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
},
|
||||
HeadFetcher: m,
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
FinalizationFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
@@ -2258,12 +2266,17 @@ func setupValidators(t testing.TB, _ db.Database, count int) ([]*ethpb.Validator
|
||||
}
|
||||
|
||||
func TestServer_GetIndividualVotes_RequestFutureSlot(t *testing.T) {
|
||||
ds := &Server{GenesisTimeFetcher: &mock.ChainService{}}
|
||||
bs := &Server{
|
||||
CoreService: &core.Service{
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
|
||||
req := ðpb.IndividualVotesRequest{
|
||||
Epoch: slots.ToEpoch(ds.GenesisTimeFetcher.CurrentSlot()) + 1,
|
||||
Epoch: slots.ToEpoch(bs.CoreService.GenesisTimeFetcher.CurrentSlot()) + 1,
|
||||
}
|
||||
wanted := errNoEpochInfoError
|
||||
_, err := ds.GetIndividualVotes(context.Background(), req)
|
||||
_, err := bs.GetIndividualVotes(context.Background(), req)
|
||||
assert.ErrorContains(t, wanted, err)
|
||||
}
|
||||
|
||||
@@ -2292,8 +2305,10 @@ func TestServer_GetIndividualVotes_ValidatorsDontExist(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
bs := &Server{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
@@ -2388,8 +2403,10 @@ func TestServer_GetIndividualVotes_Working(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
bs := &Server{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
@@ -2451,8 +2468,10 @@ func TestServer_GetIndividualVotes_WorkingAltair(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
bs := &Server{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
@@ -2537,8 +2556,10 @@ func TestServer_GetIndividualVotes_AltairEndOfEpoch(t *testing.T) {
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
bs := &Server{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
@@ -2625,8 +2646,10 @@ func TestServer_GetIndividualVotes_BellatrixEndOfEpoch(t *testing.T) {
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
bs := &Server{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
@@ -2713,8 +2736,10 @@ func TestServer_GetIndividualVotes_CapellaEndOfEpoch(t *testing.T) {
|
||||
require.NoError(t, gen.SaveState(ctx, gRoot, beaconState))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, beaconState, gRoot))
|
||||
bs := &Server{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
CoreService: &core.Service{
|
||||
StateGen: gen,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(bs, beaconDB)
|
||||
|
||||
|
||||
@@ -128,13 +128,12 @@ func TestNodeServer_GetPeer(t *testing.T) {
|
||||
}
|
||||
ethpb.RegisterNodeServer(server, ns)
|
||||
reflection.Register(server)
|
||||
firstPeer := peersProvider.Peers().All()[0]
|
||||
|
||||
res, err := ns.GetPeer(context.Background(), ðpb.PeerRequest{PeerId: firstPeer.String()})
|
||||
res, err := ns.GetPeer(context.Background(), ðpb.PeerRequest{PeerId: mockP2p.MockRawPeerId0})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, firstPeer.String(), res.PeerId, "Unexpected peer ID")
|
||||
assert.Equal(t, "16Uiu2HAkyWZ4Ni1TpvDS8dPxsozmHY85KaiFjodQuV6Tz5tkHVeR" /* first peer's raw id */, res.PeerId, "Unexpected peer ID")
|
||||
assert.Equal(t, int(ethpb.PeerDirection_INBOUND), int(res.Direction), "Expected 1st peer to be an inbound connection")
|
||||
assert.Equal(t, ethpb.ConnectionState_CONNECTED, res.ConnectionState, "Expected peer to be connected")
|
||||
assert.Equal(t, int(ethpb.ConnectionState_CONNECTED), int(res.ConnectionState), "Expected peer to be connected")
|
||||
}
|
||||
|
||||
func TestNodeServer_ListPeers(t *testing.T) {
|
||||
@@ -149,8 +148,25 @@ func TestNodeServer_ListPeers(t *testing.T) {
|
||||
res, err := ns.ListPeers(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(res.Peers))
|
||||
assert.Equal(t, int(ethpb.PeerDirection_INBOUND), int(res.Peers[0].Direction))
|
||||
assert.Equal(t, ethpb.PeerDirection_OUTBOUND, res.Peers[1].Direction)
|
||||
|
||||
var (
|
||||
firstPeer *ethpb.Peer
|
||||
secondPeer *ethpb.Peer
|
||||
)
|
||||
|
||||
for _, p := range res.Peers {
|
||||
if p.PeerId == mockP2p.MockRawPeerId0 {
|
||||
firstPeer = p
|
||||
}
|
||||
if p.PeerId == mockP2p.MockRawPeerId1 {
|
||||
secondPeer = p
|
||||
}
|
||||
}
|
||||
|
||||
assert.NotNil(t, firstPeer)
|
||||
assert.NotNil(t, secondPeer)
|
||||
assert.Equal(t, int(ethpb.PeerDirection_INBOUND), int(firstPeer.Direction))
|
||||
assert.Equal(t, int(ethpb.PeerDirection_OUTBOUND), int(secondPeer.Direction))
|
||||
}
|
||||
|
||||
func TestNodeServer_GetETH1ConnectionStatus(t *testing.T) {
|
||||
|
||||
@@ -13,6 +13,7 @@ go_library(
|
||||
"proposer.go",
|
||||
"proposer_altair.go",
|
||||
"proposer_attestations.go",
|
||||
"proposer_attestations_electra.go",
|
||||
"proposer_bellatrix.go",
|
||||
"proposer_builder.go",
|
||||
"proposer_capella.go",
|
||||
@@ -147,6 +148,7 @@ common_deps = [
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//container/trie:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/bls/blst:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//encoding/ssz:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
@@ -186,6 +188,7 @@ go_test(
|
||||
"duties_test.go",
|
||||
"exit_test.go",
|
||||
"proposer_altair_test.go",
|
||||
"proposer_attestations_electra_test.go",
|
||||
"proposer_attestations_test.go",
|
||||
"proposer_bellatrix_test.go",
|
||||
"proposer_builder_test.go",
|
||||
|
||||
@@ -50,7 +50,7 @@ func (vs *Server) ProposeAttestation(ctx context.Context, att *ethpb.Attestation
|
||||
}
|
||||
|
||||
go func() {
|
||||
attCopy := ethpb.CopyAttestation(att)
|
||||
attCopy := att.Copy()
|
||||
if err := vs.AttPool.SaveUnaggregatedAttestation(attCopy); err != nil {
|
||||
log.WithError(err).Error("Could not save unaggregated attestation")
|
||||
return
|
||||
@@ -84,7 +84,7 @@ func (vs *Server) ProposeAttestationElectra(ctx context.Context, att *ethpb.Atte
|
||||
|
||||
go func() {
|
||||
ctx = trace.NewContext(context.Background(), trace.FromContext(ctx))
|
||||
attCopy := ethpb.CopyAttestationElectra(att)
|
||||
attCopy := att.Copy()
|
||||
if err := vs.AttPool.SaveUnaggregatedAttestation(attCopy); err != nil {
|
||||
log.WithError(err).Error("Could not save unaggregated attestation")
|
||||
return
|
||||
|
||||
@@ -475,6 +475,74 @@ func TestGetAttestationData_SucceedsInFirstEpoch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAttestationData_CommitteeIndexIsZeroPostElectra(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.ElectraForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
block := util.NewBeaconBlock()
|
||||
block.Block.Slot = 3*params.BeaconConfig().SlotsPerEpoch + 1
|
||||
targetBlock := util.NewBeaconBlock()
|
||||
targetBlock.Block.Slot = params.BeaconConfig().SlotsPerEpoch
|
||||
targetRoot, err := targetBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
justifiedBlock := util.NewBeaconBlock()
|
||||
justifiedBlock.Block.Slot = 2 * params.BeaconConfig().SlotsPerEpoch
|
||||
blockRoot, err := block.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
justifiedRoot, err := justifiedBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
slot := 3*params.BeaconConfig().SlotsPerEpoch + 1
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconState.SetSlot(slot))
|
||||
justifiedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: 2,
|
||||
Root: justifiedRoot[:],
|
||||
}
|
||||
require.NoError(t, beaconState.SetCurrentJustifiedCheckpoint(justifiedCheckpoint))
|
||||
offset := int64(slot.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
attesterServer := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: &mock.ChainService{Optimistic: false},
|
||||
TimeFetcher: &mock.ChainService{Genesis: time.Now().Add(time.Duration(-1*offset) * time.Second)},
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: &mock.ChainService{TargetRoot: targetRoot, Root: blockRoot[:], State: beaconState},
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: time.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
FinalizedFetcher: &mock.ChainService{CurrentJustifiedCheckPoint: justifiedCheckpoint},
|
||||
AttestationCache: cache.NewAttestationCache(),
|
||||
OptimisticModeFetcher: &mock.ChainService{Optimistic: false},
|
||||
},
|
||||
}
|
||||
|
||||
req := ðpb.AttestationDataRequest{
|
||||
CommitteeIndex: 123, // set non-zero committee index
|
||||
Slot: 3*params.BeaconConfig().SlotsPerEpoch + 1,
|
||||
}
|
||||
res, err := attesterServer.GetAttestationData(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := ðpb.AttestationData{
|
||||
Slot: 3*params.BeaconConfig().SlotsPerEpoch + 1,
|
||||
CommitteeIndex: 0,
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 2,
|
||||
Root: justifiedRoot[:],
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 3,
|
||||
Root: targetRoot[:],
|
||||
},
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, expected, res)
|
||||
}
|
||||
|
||||
func TestServer_SubscribeCommitteeSubnets_NoSlots(t *testing.T) {
|
||||
attesterServer := &Server{
|
||||
HeadFetcher: &mock.ChainService{},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -9,6 +11,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
@@ -17,6 +20,7 @@ import (
|
||||
attaggregation "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/attestation/aggregation/attestations"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
@@ -42,6 +46,8 @@ func (vs *Server) packAttestations(ctx context.Context, latestState state.Beacon
|
||||
}
|
||||
atts = append(atts, uAtts...)
|
||||
|
||||
// Checking the state's version here will give the wrong result if the last slot of Deneb is missed.
|
||||
// The head state will still be in Deneb while we are trying to build an Electra block.
|
||||
postElectra := slots.ToEpoch(blkSlot) >= params.BeaconConfig().ElectraForkEpoch
|
||||
|
||||
versionAtts := make([]ethpb.Att, 0, len(atts))
|
||||
@@ -66,33 +72,53 @@ func (vs *Server) packAttestations(ctx context.Context, latestState state.Beacon
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attsByDataRoot := make(map[attestation.Id][]ethpb.Att, len(versionAtts))
|
||||
attsById := make(map[attestation.Id][]ethpb.Att, len(versionAtts))
|
||||
for _, att := range versionAtts {
|
||||
id, err := attestation.NewId(att, attestation.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create attestation ID")
|
||||
}
|
||||
attsByDataRoot[id] = append(attsByDataRoot[id], att)
|
||||
attsById[id] = append(attsById[id], att)
|
||||
}
|
||||
|
||||
attsForInclusion := proposerAtts(make([]ethpb.Att, 0))
|
||||
for _, as := range attsByDataRoot {
|
||||
for id, as := range attsById {
|
||||
as, err := attaggregation.Aggregate(as)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attsForInclusion = append(attsForInclusion, as...)
|
||||
attsById[id] = as
|
||||
}
|
||||
|
||||
var attsForInclusion proposerAtts
|
||||
if postElectra {
|
||||
// TODO: hack for Electra devnet-1, take only one aggregate per ID
|
||||
// (which essentially means one aggregate for an attestation_data+committee combination
|
||||
topAggregates := make([]ethpb.Att, 0)
|
||||
for _, v := range attsById {
|
||||
topAggregates = append(topAggregates, v[0])
|
||||
}
|
||||
|
||||
attsForInclusion, err = computeOnChainAggregate(topAggregates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
attsForInclusion = make([]ethpb.Att, 0)
|
||||
for _, as := range attsById {
|
||||
attsForInclusion = append(attsForInclusion, as...)
|
||||
}
|
||||
}
|
||||
|
||||
deduped, err := attsForInclusion.dedup()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sorted, err := deduped.sortByProfitability()
|
||||
sorted, err := deduped.sort()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
atts = sorted.limitToMaxAttestations()
|
||||
return atts, nil
|
||||
return vs.filterAttestationBySignature(ctx, atts, latestState)
|
||||
}
|
||||
|
||||
// filter separates attestation list into two groups: valid and invalid attestations.
|
||||
@@ -112,14 +138,6 @@ func (a proposerAtts) filter(ctx context.Context, st state.BeaconState) (propose
|
||||
return validAtts, invalidAtts
|
||||
}
|
||||
|
||||
// sortByProfitability orders attestations by highest slot and by highest aggregation bit count.
|
||||
func (a proposerAtts) sortByProfitability() (proposerAtts, error) {
|
||||
if len(a) < 2 {
|
||||
return a, nil
|
||||
}
|
||||
return a.sortByProfitabilityUsingMaxCover()
|
||||
}
|
||||
|
||||
// sortByProfitabilityUsingMaxCover orders attestations by highest slot and by highest aggregation bit count.
|
||||
// Duplicate bits are counted only once, using max-cover algorithm.
|
||||
func (a proposerAtts) sortByProfitabilityUsingMaxCover() (proposerAtts, error) {
|
||||
@@ -187,6 +205,143 @@ func (a proposerAtts) sortByProfitabilityUsingMaxCover() (proposerAtts, error) {
|
||||
return sortedAtts, nil
|
||||
}
|
||||
|
||||
// sort attestations as follows:
|
||||
//
|
||||
// - all attestations selected by max-cover are taken, leftover attestations are discarded
|
||||
// (with current parameters all bits of a leftover attestation are already covered by selected attestations)
|
||||
// - selected attestations are ordered by slot, with higher slot coming first
|
||||
// - within a slot, all top attestations (one per committee) are ordered before any second-best attestations, second-best before third-best etc.
|
||||
// - within top/second-best/etc. attestations (one per committee), attestations are ordered by bit count, with higher bit count coming first
|
||||
func (a proposerAtts) sort() (proposerAtts, error) {
|
||||
if len(a) < 2 {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
if features.Get().EnableCommitteeAwarePacking {
|
||||
return a.sortBySlotAndCommittee()
|
||||
}
|
||||
return a.sortByProfitabilityUsingMaxCover()
|
||||
}
|
||||
|
||||
// Separate attestations by slot, as slot number takes higher precedence when sorting.
|
||||
// Also separate by committee index because maxcover will prefer attestations for the same
|
||||
// committee with disjoint bits over attestations for different committees with overlapping
|
||||
// bits, even though same bits for different committees are separate votes.
|
||||
func (a proposerAtts) sortBySlotAndCommittee() (proposerAtts, error) {
|
||||
type slotAtts struct {
|
||||
candidates map[primitives.CommitteeIndex]proposerAtts
|
||||
selected map[primitives.CommitteeIndex]proposerAtts
|
||||
leftover map[primitives.CommitteeIndex]proposerAtts
|
||||
}
|
||||
|
||||
var slots []primitives.Slot
|
||||
attsBySlot := map[primitives.Slot]*slotAtts{}
|
||||
for _, att := range a {
|
||||
slot := att.GetData().Slot
|
||||
ci := att.GetData().CommitteeIndex
|
||||
if _, ok := attsBySlot[slot]; !ok {
|
||||
attsBySlot[slot] = &slotAtts{}
|
||||
attsBySlot[slot].candidates = make(map[primitives.CommitteeIndex]proposerAtts)
|
||||
slots = append(slots, slot)
|
||||
}
|
||||
attsBySlot[slot].candidates[ci] = append(attsBySlot[slot].candidates[ci], att)
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, sa := range attsBySlot {
|
||||
sa.selected = make(map[primitives.CommitteeIndex]proposerAtts)
|
||||
sa.leftover = make(map[primitives.CommitteeIndex]proposerAtts)
|
||||
for ci, committeeAtts := range sa.candidates {
|
||||
sa.selected[ci], err = committeeAtts.sortByProfitabilityUsingMaxCover_committeeAwarePacking()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sortedAtts proposerAtts
|
||||
sort.Slice(slots, func(i, j int) bool {
|
||||
return slots[i] > slots[j]
|
||||
})
|
||||
for _, slot := range slots {
|
||||
sortedAtts = append(sortedAtts, sortSlotAttestations(attsBySlot[slot].selected)...)
|
||||
}
|
||||
for _, slot := range slots {
|
||||
sortedAtts = append(sortedAtts, sortSlotAttestations(attsBySlot[slot].leftover)...)
|
||||
}
|
||||
|
||||
return sortedAtts, nil
|
||||
}
|
||||
|
||||
// sortByProfitabilityUsingMaxCover orders attestations by highest aggregation bit count.
|
||||
// Duplicate bits are counted only once, using max-cover algorithm.
|
||||
func (a proposerAtts) sortByProfitabilityUsingMaxCover_committeeAwarePacking() (proposerAtts, error) {
|
||||
if len(a) < 2 {
|
||||
return a, nil
|
||||
}
|
||||
candidates := make([]*bitfield.Bitlist64, len(a))
|
||||
for i := 0; i < len(a); i++ {
|
||||
var err error
|
||||
candidates[i], err = a[i].GetAggregationBits().ToBitlist64()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Add selected candidates on top, those that are not selected - append at bottom.
|
||||
selectedKeys, _, err := aggregation.MaxCover(candidates, len(candidates), true /* allowOverlaps */)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("MaxCover aggregation failed")
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Pick selected attestations first, leftover attestations will be appended at the end.
|
||||
// Both lists will be sorted by number of bits set.
|
||||
selected := make(proposerAtts, selectedKeys.Count())
|
||||
for i, key := range selectedKeys.BitIndices() {
|
||||
selected[i] = a[key]
|
||||
}
|
||||
sort.Slice(selected, func(i, j int) bool {
|
||||
return selected[i].GetAggregationBits().Count() > selected[j].GetAggregationBits().Count()
|
||||
})
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// sortSlotAttestations assumes each proposerAtts value in the map is ordered by profitability.
|
||||
// The function takes the first attestation from each value, orders these attestations by bit count
|
||||
// and places them at the start of the resulting slice. It then takes the second attestation for each value,
|
||||
// orders these attestations by bit count and appends them to the end.
|
||||
// It continues this pattern until all attestations are processed.
|
||||
func sortSlotAttestations(slotAtts map[primitives.CommitteeIndex]proposerAtts) proposerAtts {
|
||||
attCount := 0
|
||||
for _, committeeAtts := range slotAtts {
|
||||
attCount += len(committeeAtts)
|
||||
}
|
||||
|
||||
sorted := make([]ethpb.Att, 0, attCount)
|
||||
|
||||
processedCount := 0
|
||||
index := 0
|
||||
for processedCount < attCount {
|
||||
var atts []ethpb.Att
|
||||
|
||||
for _, committeeAtts := range slotAtts {
|
||||
if len(committeeAtts) > index {
|
||||
atts = append(atts, committeeAtts[index])
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(atts, func(i, j int) bool {
|
||||
return atts[i].GetAggregationBits().Count() > atts[j].GetAggregationBits().Count()
|
||||
})
|
||||
sorted = append(sorted, atts...)
|
||||
|
||||
processedCount += len(atts)
|
||||
index++
|
||||
}
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
// limitToMaxAttestations limits attestations to maximum attestations per block.
|
||||
func (a proposerAtts) limitToMaxAttestations() proposerAtts {
|
||||
if len(a) == 0 {
|
||||
@@ -287,3 +442,193 @@ func (vs *Server) deleteAttsInPool(ctx context.Context, atts []ethpb.Att) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isAttestationFromCurrentEpoch returns true if the attestation is from the current epoch.
|
||||
func (vs *Server) isAttestationFromCurrentEpoch(att ethpb.Att, currentEpoch primitives.Epoch) bool {
|
||||
return att.GetData().Target.Epoch == currentEpoch
|
||||
}
|
||||
|
||||
// isAttestationFromPreviousEpoch returns true if the attestation is from the previous epoch.
|
||||
func (vs *Server) isAttestationFromPreviousEpoch(att ethpb.Att, currentEpoch primitives.Epoch) bool {
|
||||
return att.GetData().Target.Epoch+1 == currentEpoch
|
||||
}
|
||||
|
||||
// filterCurrentEpochAttestationByForkchoice filters attestations from the current epoch based on fork choice conditions.
|
||||
// Returns true if all of the following conditions are met:
|
||||
// 1. The attestation beacon block root is for a slot in the previous epoch (according to fork choice).
|
||||
// 2. The attestation target root is the same as the attestation beacon block root.
|
||||
// 3. The attestation beacon block root is canonical according to fork choice.
|
||||
func (vs *Server) filterCurrentEpochAttestationByForkchoice(ctx context.Context, att ethpb.Att, currentEpoch primitives.Epoch) (bool, error) {
|
||||
if !vs.isAttestationFromCurrentEpoch(att, currentEpoch) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
attTargetRoot := [32]byte(att.GetData().Target.Root)
|
||||
attBlockRoot := [32]byte(att.GetData().BeaconBlockRoot)
|
||||
if attBlockRoot != attTargetRoot {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
slot, err := vs.ForkchoiceFetcher.RecentBlockSlot(attBlockRoot)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
epoch := slots.ToEpoch(slot)
|
||||
if epoch+1 != currentEpoch {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return vs.ForkchoiceFetcher.IsCanonical(ctx, attBlockRoot)
|
||||
}
|
||||
|
||||
// filterCurrentEpochAttestationByTarget returns true if an attestation from the current epoch matches the fork choice target view.
|
||||
// The conditions checked are:
|
||||
// 1. The attestation's target epoch matches the forkchoice target epoch.
|
||||
// 2. The attestation's target root matches the forkchoice target root.
|
||||
func (vs *Server) filterCurrentEpochAttestationByTarget(att ethpb.Att, targetRoot [32]byte, targetEpoch, currentEpoch primitives.Epoch) (bool, error) {
|
||||
if !vs.isAttestationFromCurrentEpoch(att, currentEpoch) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
attTargetRoot := [32]byte(att.GetData().Target.Root)
|
||||
return att.GetData().Target.Epoch == targetEpoch && attTargetRoot == targetRoot, nil
|
||||
}
|
||||
|
||||
// filterPreviousEpochAttestationByTarget returns true if an attestation from the previous epoch matches the fork choice previous target view.
|
||||
// The conditions checked are:
|
||||
// 1. The attestation's target epoch matches the forkchoice previous target epoch.
|
||||
// 2. The attestation's target root matches the forkchoice previous target root.
|
||||
func (vs *Server) filterPreviousEpochAttestationByTarget(att ethpb.Att, cp *ethpb.Checkpoint, currentEpoch primitives.Epoch) (bool, error) {
|
||||
if !vs.isAttestationFromPreviousEpoch(att, currentEpoch) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return att.GetData().Target.Epoch == cp.Epoch && bytes.Equal(att.GetData().Target.Root, cp.Root), nil
|
||||
}
|
||||
|
||||
// filterAttestationBySignature filters attestations based on specific conditions and performs batch signature verification.
|
||||
// The conditions checked are:
|
||||
// 1. The attestation matches the current target view defined in `filterCurrentEpochAttestationByTarget`.
|
||||
// 2. The attestation matches the previous target view defined in `filterPreviousEpochAttestationByTarget`.
|
||||
// 3. The attestation matches certain fork choice conditions defined in `filterCurrentEpochAttestationByForkchoice`.
|
||||
// The remaining attestations are sent for batch signature verification. If the batch verification fails, each signature is verified individually.
|
||||
func (vs *Server) filterAttestationBySignature(ctx context.Context, atts proposerAtts, st state.BeaconState) (proposerAtts, error) {
|
||||
headSlot := vs.HeadFetcher.HeadSlot()
|
||||
targetEpoch := slots.ToEpoch(headSlot)
|
||||
r, err := vs.HeadFetcher.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headRoot := [32]byte(r)
|
||||
|
||||
targetRoot, err := vs.HeadFetcher.TargetRootForEpoch(headRoot, targetEpoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prevTargetEpoch := primitives.Epoch(0)
|
||||
if targetEpoch >= 1 {
|
||||
prevTargetEpoch = targetEpoch.Sub(1)
|
||||
}
|
||||
prevTargetRoot, err := vs.HeadFetcher.TargetRootForEpoch(headRoot, prevTargetEpoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentSlot := vs.TimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(currentSlot)
|
||||
|
||||
var verifiedAtts proposerAtts
|
||||
var unverifiedAtts proposerAtts
|
||||
for _, att := range atts {
|
||||
ok, err := vs.filterCurrentEpochAttestationByTarget(att, targetRoot, targetEpoch, currentEpoch)
|
||||
if err != nil {
|
||||
log.WithFields(attestationFields(att)).WithError(err).Error("Could not filter current epoch attestation by target")
|
||||
}
|
||||
if ok {
|
||||
verifiedAtts = append(verifiedAtts, att)
|
||||
continue
|
||||
}
|
||||
|
||||
ok, err = vs.filterPreviousEpochAttestationByTarget(att, ðpb.Checkpoint{Root: prevTargetRoot[:], Epoch: prevTargetEpoch}, currentEpoch)
|
||||
if err != nil {
|
||||
log.WithFields(attestationFields(att)).WithError(err).Error("Could not filter previous epoch attestation by target")
|
||||
}
|
||||
if ok {
|
||||
verifiedAtts = append(verifiedAtts, att)
|
||||
continue
|
||||
}
|
||||
|
||||
ok, err = vs.filterCurrentEpochAttestationByForkchoice(ctx, att, currentEpoch)
|
||||
if err != nil {
|
||||
log.WithFields(attestationFields(att)).WithError(err).Error("Could not filter current epoch attestation by fork choice")
|
||||
}
|
||||
if ok {
|
||||
verifiedAtts = append(verifiedAtts, att)
|
||||
continue
|
||||
}
|
||||
|
||||
unverifiedAtts = append(unverifiedAtts, att)
|
||||
}
|
||||
|
||||
if len(unverifiedAtts) == 0 {
|
||||
return verifiedAtts, nil
|
||||
}
|
||||
|
||||
unverifiedAtts = unverifiedAtts.filterBatchSignature(ctx, st)
|
||||
|
||||
return append(verifiedAtts, unverifiedAtts...), nil
|
||||
}
|
||||
|
||||
// filterBatchSignature verifies the signatures of the attestation set.
|
||||
// If batch verification fails, the attestation set is filtered by verifying each signature individually.
|
||||
func (a proposerAtts) filterBatchSignature(ctx context.Context, st state.BeaconState) proposerAtts {
|
||||
aSet, err := blocks.AttestationSignatureBatch(ctx, st, a)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not create attestation signature set")
|
||||
return a.filterIndividualSignature(ctx, st)
|
||||
}
|
||||
|
||||
if verified, err := aSet.Verify(); err != nil || !verified {
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Batch verification failed")
|
||||
} else {
|
||||
log.Error("Batch verification failed: signatures not verified")
|
||||
}
|
||||
return a.filterIndividualSignature(ctx, st)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// filterIndividualSignature filters the attestation set by verifying each signature individually.
|
||||
func (a proposerAtts) filterIndividualSignature(ctx context.Context, st state.BeaconState) proposerAtts {
|
||||
var validAtts proposerAtts
|
||||
for _, att := range a {
|
||||
aSet, err := blocks.AttestationSignatureBatch(ctx, st, []ethpb.Att{att})
|
||||
if err != nil {
|
||||
log.WithFields(attestationFields(att)).WithError(err).Error("Could not create individual attestation signature set")
|
||||
continue
|
||||
}
|
||||
if verified, err := aSet.Verify(); err != nil || !verified {
|
||||
logEntry := log.WithFields(attestationFields(att))
|
||||
if err != nil {
|
||||
logEntry.WithError(err).Error("Verification of individual attestation failed")
|
||||
} else {
|
||||
logEntry.Error("Verification of individual attestation failed: signature not verified")
|
||||
}
|
||||
continue
|
||||
}
|
||||
validAtts = append(validAtts, att)
|
||||
}
|
||||
return validAtts
|
||||
}
|
||||
|
||||
func attestationFields(att ethpb.Att) logrus.Fields {
|
||||
return logrus.Fields{
|
||||
"slot": att.GetData().Slot,
|
||||
"index": att.GetData().CommitteeIndex,
|
||||
"targetRoot": fmt.Sprintf("%x", att.GetData().Target.Root),
|
||||
"targetEpoch": att.GetData().Target.Epoch,
|
||||
"beaconBlockRoot": fmt.Sprintf("%x", att.GetData().BeaconBlockRoot),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// computeOnChainAggregate constructs a final aggregate form a list of network aggregates with equal attestation data.
|
||||
// It assumes that each network aggregate has exactly one committee bit set.
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Attestation:
|
||||
// aggregates = sorted(network_aggregates, key=lambda a: get_committee_indices(a.committee_bits)[0])
|
||||
//
|
||||
// data = aggregates[0].data
|
||||
// aggregation_bits = Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]()
|
||||
// for a in aggregates:
|
||||
// for b in a.aggregation_bits:
|
||||
// aggregation_bits.append(b)
|
||||
//
|
||||
// signature = bls.Aggregate([a.signature for a in aggregates])
|
||||
//
|
||||
// committee_indices = [get_committee_indices(a.committee_bits)[0] for a in aggregates]
|
||||
// committee_flags = [(index in committee_indices) for index in range(0, MAX_COMMITTEES_PER_SLOT)]
|
||||
// committee_bits = Bitvector[MAX_COMMITTEES_PER_SLOT](committee_flags)
|
||||
//
|
||||
// return Attestation(
|
||||
// aggregation_bits=aggregation_bits,
|
||||
// data=data,
|
||||
// committee_bits=committee_bits,
|
||||
// signature=signature,
|
||||
// )
|
||||
func computeOnChainAggregate(aggregates []ethpb.Att) ([]ethpb.Att, error) {
|
||||
aggsByDataRoot := make(map[[32]byte][]ethpb.Att)
|
||||
for _, agg := range aggregates {
|
||||
key, err := agg.GetData().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existing, ok := aggsByDataRoot[key]
|
||||
if ok {
|
||||
aggsByDataRoot[key] = append(existing, agg)
|
||||
} else {
|
||||
aggsByDataRoot[key] = []ethpb.Att{agg}
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]ethpb.Att, 0)
|
||||
|
||||
for _, aggs := range aggsByDataRoot {
|
||||
slices.SortFunc(aggs, func(a, b ethpb.Att) int {
|
||||
return a.CommitteeBitsVal().BitIndices()[0] - b.CommitteeBitsVal().BitIndices()[0]
|
||||
})
|
||||
|
||||
sigs := make([]bls.Signature, len(aggs))
|
||||
committeeIndices := make([]primitives.CommitteeIndex, len(aggs))
|
||||
aggBitsIndices := make([]uint64, 0)
|
||||
aggBitsOffset := uint64(0)
|
||||
var err error
|
||||
for i, a := range aggs {
|
||||
for _, bi := range a.GetAggregationBits().BitIndices() {
|
||||
aggBitsIndices = append(aggBitsIndices, uint64(bi)+aggBitsOffset)
|
||||
}
|
||||
sigs[i], err = bls.SignatureFromBytes(a.GetSignature())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
committeeIndices[i] = helpers.CommitteeIndices(a.CommitteeBitsVal())[0]
|
||||
|
||||
aggBitsOffset += a.GetAggregationBits().Len()
|
||||
}
|
||||
|
||||
aggregationBits := bitfield.NewBitlist(aggBitsOffset)
|
||||
for _, bi := range aggBitsIndices {
|
||||
aggregationBits.SetBitAt(bi, true)
|
||||
}
|
||||
|
||||
cb := primitives.NewAttestationCommitteeBits()
|
||||
att := ðpb.AttestationElectra{
|
||||
AggregationBits: aggregationBits,
|
||||
Data: aggs[0].GetData(),
|
||||
CommitteeBits: cb,
|
||||
Signature: bls.AggregateSignatures(sigs).Marshal(),
|
||||
}
|
||||
for _, ci := range committeeIndices {
|
||||
att.CommitteeBits.SetBitAt(uint64(ci), true)
|
||||
}
|
||||
result = append(result, att)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls/blst"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func Test_computeOnChainAggregate(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.MainnetConfig().Copy()
|
||||
cfg.MaxCommitteesPerSlot = 64
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
key, err := blst.RandKey()
|
||||
require.NoError(t, err)
|
||||
sig := key.Sign([]byte{'X'})
|
||||
|
||||
data1 := ðpb.AttestationData{
|
||||
Slot: 123,
|
||||
CommitteeIndex: 123,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root"), 32),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 123,
|
||||
Root: bytesutil.PadTo([]byte("root"), 32),
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 123,
|
||||
Root: bytesutil.PadTo([]byte("root"), 32),
|
||||
},
|
||||
}
|
||||
data2 := ðpb.AttestationData{
|
||||
Slot: 456,
|
||||
CommitteeIndex: 456,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root"), 32),
|
||||
Source: ðpb.Checkpoint{
|
||||
Epoch: 456,
|
||||
Root: bytesutil.PadTo([]byte("root"), 32),
|
||||
},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 456,
|
||||
Root: bytesutil.PadTo([]byte("root"), 32),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("single aggregate", func(t *testing.T) {
|
||||
cb := primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(0, true)
|
||||
att := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b00011111},
|
||||
Data: data1,
|
||||
CommitteeBits: cb,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
result, err := computeOnChainAggregate([]ethpb.Att{att})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(result))
|
||||
assert.DeepEqual(t, att.AggregationBits, result[0].GetAggregationBits())
|
||||
assert.DeepEqual(t, att.Data, result[0].GetData())
|
||||
assert.DeepEqual(t, att.CommitteeBits, result[0].CommitteeBitsVal())
|
||||
})
|
||||
t.Run("all aggregates for one root", func(t *testing.T) {
|
||||
cb := primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(0, true)
|
||||
att1 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1
|
||||
Data: data1,
|
||||
CommitteeBits: cb,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
cb = primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(1, true)
|
||||
att2 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1
|
||||
Data: data1,
|
||||
CommitteeBits: cb,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
result, err := computeOnChainAggregate([]ethpb.Att{att1, att2})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(result))
|
||||
assert.DeepEqual(t, bitfield.Bitlist{0b00110011, 0b00000001}, result[0].GetAggregationBits())
|
||||
assert.DeepEqual(t, data1, result[0].GetData())
|
||||
cb = primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(0, true)
|
||||
cb.SetBitAt(1, true)
|
||||
assert.DeepEqual(t, cb, result[0].CommitteeBitsVal())
|
||||
})
|
||||
t.Run("aggregates for multiple roots", func(t *testing.T) {
|
||||
cb := primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(0, true)
|
||||
att1 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1
|
||||
Data: data1,
|
||||
CommitteeBits: cb,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
cb = primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(1, true)
|
||||
att2 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b00010011}, // aggregation bits 0,1
|
||||
Data: data1,
|
||||
CommitteeBits: cb,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
cb = primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(0, true)
|
||||
att3 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b00011001}, // aggregation bits 0,3
|
||||
Data: data2,
|
||||
CommitteeBits: cb,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
cb = primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(1, true)
|
||||
att4 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b00010010}, // aggregation bits 1
|
||||
Data: data2,
|
||||
CommitteeBits: cb,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
result, err := computeOnChainAggregate([]ethpb.Att{att1, att2, att3, att4})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(result))
|
||||
cb = primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(0, true)
|
||||
cb.SetBitAt(1, true)
|
||||
|
||||
expectedAggBits := bitfield.Bitlist{0b00110011, 0b00000001}
|
||||
expectedData := data1
|
||||
found := false
|
||||
for _, a := range result {
|
||||
if reflect.DeepEqual(expectedAggBits, a.GetAggregationBits()) && reflect.DeepEqual(expectedData, a.GetData()) && reflect.DeepEqual(cb, a.CommitteeBitsVal()) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Expected aggregate not found")
|
||||
}
|
||||
|
||||
expectedAggBits = bitfield.Bitlist{0b00101001, 0b00000001}
|
||||
expectedData = data2
|
||||
found = false
|
||||
for _, a := range result {
|
||||
if reflect.DeepEqual(expectedAggBits, a.GetAggregationBits()) && reflect.DeepEqual(expectedData, a.GetData()) && reflect.DeepEqual(cb, a.CommitteeBitsVal()) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("Expected aggregate not found")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -7,40 +7,20 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls/blst"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
func TestProposer_ProposerAtts_sortByProfitability(t *testing.T) {
|
||||
atts := proposerAtts([]ethpb.Att{
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11000000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 2}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11110000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 3}, AggregationBits: bitfield.Bitlist{0b11000000}}),
|
||||
})
|
||||
want := proposerAtts([]ethpb.Att{
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11110000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 3}, AggregationBits: bitfield.Bitlist{0b11000000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 2}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11000000}}),
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
}
|
||||
|
||||
func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
func TestProposer_ProposerAtts_sort(t *testing.T) {
|
||||
type testData struct {
|
||||
slot primitives.Slot
|
||||
bits bitfield.Bitlist
|
||||
@@ -57,7 +37,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
t.Run("no atts", func(t *testing.T) {
|
||||
atts := getAtts([]testData{})
|
||||
want := getAtts([]testData{})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -71,7 +51,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -87,7 +67,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -105,7 +85,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -126,7 +106,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{1, bitfield.Bitlist{0b00001100, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11001000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -151,14 +131,14 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("selected and non selected atts sorted by bit count", func(t *testing.T) {
|
||||
t.Run("follows max-cover", func(t *testing.T) {
|
||||
// Items at slot 4, must be first split into two lists by max-cover, with
|
||||
// 0b10000011 scoring higher (as it provides more info in addition to already selected
|
||||
// attestations) than 0b11100001 (despite naive bit count suggesting otherwise). Then,
|
||||
@@ -183,7 +163,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -191,6 +171,241 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestProposer_ProposerAtts_committeeAwareSort(t *testing.T) {
|
||||
type testData struct {
|
||||
slot primitives.Slot
|
||||
bits bitfield.Bitlist
|
||||
}
|
||||
getAtts := func(data []testData) proposerAtts {
|
||||
var atts proposerAtts
|
||||
for _, att := range data {
|
||||
atts = append(atts, util.HydrateAttestation(ðpb.Attestation{
|
||||
Data: ðpb.AttestationData{Slot: att.slot}, AggregationBits: att.bits}))
|
||||
}
|
||||
return atts
|
||||
}
|
||||
|
||||
t.Run("no atts", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{})
|
||||
want := getAtts([]testData{})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("single att", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("single att per slot", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("two atts on one of the slots", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11110000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11110000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("compare to native sort", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
// The max-cover based approach will select 0b00001100 instead, despite lower bit count
|
||||
// (since it has two new/unknown bits).
|
||||
t.Run("max-cover", func(t *testing.T) {
|
||||
atts := getAtts([]testData{
|
||||
{1, bitfield.Bitlist{0b11000011, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11001000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b00001100, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{1, bitfield.Bitlist{0b11000011, 0b1}},
|
||||
{1, bitfield.Bitlist{0b00001100, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("multiple slots", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{
|
||||
{2, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11110000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{3, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11110000, 0b1}},
|
||||
{3, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{2, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("follows max-cover", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
// Items at slot 4 must be first split into two lists by max-cover, with
|
||||
// 0b10000011 being selected and 0b11100001 being leftover (despite naive bit count suggesting otherwise).
|
||||
atts := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b00000001, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11100001, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{2, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b10000011, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11111000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{3, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11111000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b10000011, 0b1}},
|
||||
{3, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{2, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProposer_sort_DifferentCommittees(t *testing.T) {
|
||||
t.Run("one att per committee", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
c1_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11111000, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 1}})
|
||||
c2_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11100000, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 2}})
|
||||
atts := proposerAtts{c1_a1, c2_a1}
|
||||
atts, err := atts.sort()
|
||||
require.NoError(t, err)
|
||||
want := proposerAtts{c1_a1, c2_a1}
|
||||
assert.DeepEqual(t, want, atts)
|
||||
})
|
||||
t.Run("multiple atts per committee", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
c1_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11111100, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 1}})
|
||||
c1_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b10000010, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 1}})
|
||||
c2_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11110000, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 2}})
|
||||
c2_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11100000, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 2}})
|
||||
atts := proposerAtts{c1_a1, c1_a2, c2_a1, c2_a2}
|
||||
atts, err := atts.sort()
|
||||
require.NoError(t, err)
|
||||
|
||||
want := proposerAtts{c1_a1, c2_a1, c1_a2}
|
||||
assert.DeepEqual(t, want, atts)
|
||||
})
|
||||
t.Run("multiple atts per committee, multiple slots", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
s2_c1_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11111100, 0b1}, Data: ðpb.AttestationData{Slot: 2, CommitteeIndex: 1}})
|
||||
s2_c1_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b10000010, 0b1}, Data: ðpb.AttestationData{Slot: 2, CommitteeIndex: 1}})
|
||||
s2_c2_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11110000, 0b1}, Data: ðpb.AttestationData{Slot: 2, CommitteeIndex: 2}})
|
||||
s2_c2_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11000000, 0b1}, Data: ðpb.AttestationData{Slot: 2, CommitteeIndex: 2}})
|
||||
s1_c1_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11111100, 0b1}, Data: ðpb.AttestationData{Slot: 1, CommitteeIndex: 1}})
|
||||
s1_c1_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b10000010, 0b1}, Data: ðpb.AttestationData{Slot: 1, CommitteeIndex: 1}})
|
||||
s1_c2_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11110000, 0b1}, Data: ðpb.AttestationData{Slot: 1, CommitteeIndex: 2}})
|
||||
s1_c2_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11000000, 0b1}, Data: ðpb.AttestationData{Slot: 1, CommitteeIndex: 2}})
|
||||
|
||||
// Arrange in some random order
|
||||
atts := proposerAtts{s1_c1_a1, s2_c1_a2, s1_c2_a2, s2_c2_a2, s1_c2_a1, s2_c2_a1, s1_c1_a2, s2_c1_a1}
|
||||
|
||||
atts, err := atts.sort()
|
||||
require.NoError(t, err)
|
||||
|
||||
want := proposerAtts{s2_c1_a1, s2_c2_a1, s2_c1_a2, s1_c1_a1, s1_c2_a1, s1_c1_a2}
|
||||
assert.DeepEqual(t, want, atts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProposer_ProposerAtts_dedup(t *testing.T) {
|
||||
data1 := util.HydrateAttestationData(ðpb.AttestationData{
|
||||
Slot: 4,
|
||||
@@ -446,6 +661,9 @@ func Test_packAttestations(t *testing.T) {
|
||||
}
|
||||
cb := primitives.NewAttestationCommitteeBits()
|
||||
cb.SetBitAt(0, true)
|
||||
key, err := blst.RandKey()
|
||||
require.NoError(t, err)
|
||||
sig := key.Sign([]byte{'X'})
|
||||
electraAtt := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b11111},
|
||||
CommitteeBits: cb,
|
||||
@@ -460,11 +678,12 @@ func Test_packAttestations(t *testing.T) {
|
||||
Root: make([]byte, 32),
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
pool := attestations.NewPool()
|
||||
require.NoError(t, pool.SaveAggregatedAttestations([]ethpb.Att{phase0Att, electraAtt}))
|
||||
s := &Server{AttPool: pool}
|
||||
slot := primitives.Slot(1)
|
||||
s := &Server{AttPool: pool, HeadFetcher: &chainMock.ChainService{}, TimeFetcher: &chainMock.ChainService{Slot: &slot}}
|
||||
|
||||
t.Run("Phase 0", func(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisState(t, 64)
|
||||
@@ -484,6 +703,20 @@ func Test_packAttestations(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, 64)
|
||||
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch+1))
|
||||
|
||||
atts, err := s.packAttestations(ctx, st, params.BeaconConfig().SlotsPerEpoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(atts))
|
||||
assert.DeepEqual(t, electraAtt, atts[0])
|
||||
})
|
||||
t.Run("Electra block with Deneb state", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.ElectraForkEpoch = 1
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
st, _ := util.DeterministicGenesisStateDeneb(t, 64)
|
||||
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch+1))
|
||||
|
||||
atts, err := s.packAttestations(ctx, st, params.BeaconConfig().SlotsPerEpoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(atts))
|
||||
@@ -529,3 +762,156 @@ func Test_limitToMaxAttestations(t *testing.T) {
|
||||
assert.Equal(t, len(pAtts)-1, len(pAtts.limitToMaxAttestations()))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_filterBatchSignature(t *testing.T) {
|
||||
st, k := util.DeterministicGenesisState(t, 64)
|
||||
// Generate 1 good signature
|
||||
aGood, err := util.GenerateAttestations(st, k, 1, 0, false)
|
||||
require.NoError(t, err)
|
||||
// Generate 1 bad signature
|
||||
aBad := util.NewAttestation()
|
||||
pa := proposerAtts(aGood)
|
||||
pa = append(pa, aBad)
|
||||
aFiltered := pa.filterBatchSignature(context.Background(), st)
|
||||
assert.Equal(t, 1, len(aFiltered))
|
||||
assert.DeepEqual(t, aGood[0], aFiltered[0])
|
||||
}
|
||||
|
||||
func Test_isAttestationFromCurrentEpoch(t *testing.T) {
|
||||
slot := primitives.Slot(1)
|
||||
epoch := slots.ToEpoch(slot)
|
||||
s := &Server{}
|
||||
a := ðpb.Attestation{
|
||||
Data: ðpb.AttestationData{Target: ðpb.Checkpoint{}},
|
||||
}
|
||||
require.Equal(t, true, s.isAttestationFromCurrentEpoch(a, epoch))
|
||||
|
||||
a.Data.Target.Epoch = 1
|
||||
require.Equal(t, false, s.isAttestationFromCurrentEpoch(a, epoch))
|
||||
}
|
||||
|
||||
func Test_isAttestationFromPreviousEpoch(t *testing.T) {
|
||||
slot := params.BeaconConfig().SlotsPerEpoch
|
||||
epoch := slots.ToEpoch(slot)
|
||||
s := &Server{}
|
||||
a := ðpb.Attestation{
|
||||
Data: ðpb.AttestationData{Target: ðpb.Checkpoint{}},
|
||||
}
|
||||
require.Equal(t, true, s.isAttestationFromPreviousEpoch(a, epoch))
|
||||
|
||||
a.Data.Target.Epoch = 1
|
||||
require.Equal(t, false, s.isAttestationFromPreviousEpoch(a, epoch))
|
||||
}
|
||||
|
||||
func Test_filterCurrentEpochAttestationByTarget(t *testing.T) {
|
||||
slot := params.BeaconConfig().SlotsPerEpoch
|
||||
epoch := slots.ToEpoch(slot)
|
||||
s := &Server{}
|
||||
targetRoot := [32]byte{'a'}
|
||||
a := ðpb.Attestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 1,
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: targetRoot[:],
|
||||
},
|
||||
},
|
||||
}
|
||||
got, err := s.filterCurrentEpochAttestationByTarget(a, targetRoot, 1, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, got)
|
||||
|
||||
got, err = s.filterCurrentEpochAttestationByTarget(a, [32]byte{}, 1, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
|
||||
got, err = s.filterCurrentEpochAttestationByTarget(a, targetRoot, 2, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
|
||||
a.Data.Target.Epoch = 2
|
||||
got, err = s.filterCurrentEpochAttestationByTarget(a, targetRoot, 1, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
}
|
||||
|
||||
func Test_filterPreviousEpochAttestationByTarget(t *testing.T) {
|
||||
slot := 2 * params.BeaconConfig().SlotsPerEpoch
|
||||
epoch := slots.ToEpoch(slot)
|
||||
s := &Server{}
|
||||
targetRoot := [32]byte{'a'}
|
||||
a := ðpb.Attestation{
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 1,
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: targetRoot[:],
|
||||
},
|
||||
},
|
||||
}
|
||||
got, err := s.filterPreviousEpochAttestationByTarget(a, ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: targetRoot[:],
|
||||
}, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, got)
|
||||
|
||||
got, err = s.filterPreviousEpochAttestationByTarget(a, ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
}, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
|
||||
got, err = s.filterPreviousEpochAttestationByTarget(a, ðpb.Checkpoint{
|
||||
Epoch: 2,
|
||||
Root: targetRoot[:],
|
||||
}, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
|
||||
got, err = s.filterPreviousEpochAttestationByTarget(a, ðpb.Checkpoint{
|
||||
Epoch: 3,
|
||||
Root: targetRoot[:],
|
||||
}, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
}
|
||||
|
||||
func Test_filterCurrentEpochAttestationByForkchoice(t *testing.T) {
|
||||
slot := params.BeaconConfig().SlotsPerEpoch
|
||||
epoch := slots.ToEpoch(slot)
|
||||
s := &Server{}
|
||||
targetRoot := [32]byte{'a'}
|
||||
a := ðpb.Attestation{
|
||||
Data: ðpb.AttestationData{
|
||||
BeaconBlockRoot: make([]byte, 32),
|
||||
Slot: params.BeaconConfig().SlotsPerEpoch,
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: targetRoot[:],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
got, err := s.filterCurrentEpochAttestationByForkchoice(ctx, a, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
|
||||
a.Data.BeaconBlockRoot = targetRoot[:]
|
||||
s.ForkchoiceFetcher = &chainMock.ChainService{BlockSlot: 1}
|
||||
got, err = s.filterCurrentEpochAttestationByForkchoice(ctx, a, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, got)
|
||||
|
||||
s.ForkchoiceFetcher = &chainMock.ChainService{BlockSlot: 100}
|
||||
got, err = s.filterCurrentEpochAttestationByForkchoice(ctx, a, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
|
||||
slot = params.BeaconConfig().SlotsPerEpoch * 2
|
||||
epoch = slots.ToEpoch(slot)
|
||||
got, err = s.filterCurrentEpochAttestationByForkchoice(ctx, a, epoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, got)
|
||||
}
|
||||
|
||||
@@ -96,6 +96,27 @@ func setExecutionData(ctx context.Context, blk interfaces.SignedBeaconBlock, loc
|
||||
// Compare payload values between local and builder. Default to the local value if it is higher.
|
||||
localValueGwei := primitives.WeiToGwei(local.Bid)
|
||||
builderValueGwei := primitives.WeiToGwei(bid.Value())
|
||||
minBid := primitives.Gwei(params.BeaconConfig().MinBuilderBid)
|
||||
// Use local block if min bid is not attained
|
||||
if builderValueGwei < minBid {
|
||||
log.WithFields(logrus.Fields{
|
||||
"minBuilderBid": minBid,
|
||||
"builderGweiValue": builderValueGwei,
|
||||
}).Warn("Proposer: using local execution payload because min bid not attained")
|
||||
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
|
||||
}
|
||||
|
||||
// Use local block if min difference is not attained
|
||||
minDiff := localValueGwei + primitives.Gwei(params.BeaconConfig().MinBuilderDiff)
|
||||
if builderValueGwei < minDiff {
|
||||
log.WithFields(logrus.Fields{
|
||||
"localGweiValue": localValueGwei,
|
||||
"minBidDiff": minDiff,
|
||||
"builderGweiValue": builderValueGwei,
|
||||
}).Warn("Proposer: using local execution payload because min difference with local value was not attained")
|
||||
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
|
||||
}
|
||||
|
||||
// Use builder payload if the following in true:
|
||||
// builder_bid_value * builderBoostFactor(default 100) > local_block_value * (local-block-value-boost + 100)
|
||||
boost := primitives.Gwei(params.BeaconConfig().LocalBlockValueBoost)
|
||||
|
||||
@@ -420,7 +420,41 @@ func TestServer_setExecutionData(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(3), e.BlockNumber()) // Local block
|
||||
|
||||
require.LogsContain(t, hook, "builderGweiValue=1 localBoostPercentage=0 localGweiValue=2")
|
||||
require.LogsContain(t, hook, "\"Proposer: using local execution payload because min difference with local value was not attained\" builderGweiValue=1 localGweiValue=2")
|
||||
})
|
||||
t.Run("Builder configured. Builder block does not achieve min bid", func(t *testing.T) {
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.MinBuilderBid = 5
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella())
|
||||
require.NoError(t, err)
|
||||
elBid := primitives.Uint64ToWei(2 * 1e9)
|
||||
ed, err := blocks.NewWrappedExecutionData(&v1.ExecutionPayloadCapella{BlockNumber: 3})
|
||||
require.NoError(t, err)
|
||||
vs.ExecutionEngineCaller = &powtesting.EngineClient{PayloadIDBytes: id, GetPayloadResponse: &blocks.GetPayloadResponse{ExecutionData: ed, Bid: elBid}}
|
||||
b := blk.Block()
|
||||
res, err := vs.getLocalPayload(ctx, b, capellaTransitionState)
|
||||
require.NoError(t, err)
|
||||
builderBid, err := vs.getBuilderPayloadAndBlobs(ctx, b.Slot(), b.ProposerIndex())
|
||||
require.NoError(t, err)
|
||||
_, err = builderBid.Header()
|
||||
require.NoError(t, err)
|
||||
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
|
||||
if builderBid.Version() >= version.Deneb {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.DeepEqual(t, [][]uint8{}, builderKzgCommitments)
|
||||
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, defaultBuilderBoostFactor)
|
||||
require.NoError(t, err)
|
||||
require.IsNil(t, bundle)
|
||||
e, err := blk.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(3), e.BlockNumber()) // Local block
|
||||
|
||||
require.LogsContain(t, hook, "\"Proposer: using local execution payload because min bid not attained\" builderGweiValue=1 minBuilderBid=5")
|
||||
cfg.MinBuilderBid = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
})
|
||||
t.Run("Builder configured. Local block and local boost has higher value", func(t *testing.T) {
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
|
||||
@@ -59,14 +59,26 @@ func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBe
|
||||
slot := blk.Slot()
|
||||
vIdx := blk.ProposerIndex()
|
||||
headRoot := blk.ParentRoot()
|
||||
logFields := logrus.Fields{
|
||||
"validatorIndex": vIdx,
|
||||
"slot": slot,
|
||||
"headRoot": fmt.Sprintf("%#x", headRoot),
|
||||
}
|
||||
payloadId, ok := vs.PayloadIDCache.PayloadID(slot, headRoot)
|
||||
|
||||
val, tracked := vs.TrackedValidatorsCache.Validator(vIdx)
|
||||
return vs.getLocalPayloadFromEngine(ctx, st, headRoot, slot, vIdx)
|
||||
}
|
||||
|
||||
// This returns the local execution payload of a slot, proposer ID, and parent root assuming payload Is cached.
|
||||
// If the payload ID is not cached, the function will prepare a new payload through local EL engine and return it by using the head state.
|
||||
func (vs *Server) getLocalPayloadFromEngine(
|
||||
ctx context.Context,
|
||||
st state.BeaconState,
|
||||
parentRoot [32]byte,
|
||||
slot primitives.Slot,
|
||||
proposerId primitives.ValidatorIndex) (*consensusblocks.GetPayloadResponse, error) {
|
||||
logFields := logrus.Fields{
|
||||
"validatorIndex": proposerId,
|
||||
"slot": slot,
|
||||
"headRoot": fmt.Sprintf("%#x", parentRoot),
|
||||
}
|
||||
payloadId, ok := vs.PayloadIDCache.PayloadID(slot, parentRoot)
|
||||
|
||||
val, tracked := vs.TrackedValidatorsCache.Validator(proposerId)
|
||||
if !tracked {
|
||||
logrus.WithFields(logFields).Warn("could not find tracked proposer index")
|
||||
}
|
||||
@@ -135,7 +147,7 @@ func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBe
|
||||
PrevRandao: random,
|
||||
SuggestedFeeRecipient: val.FeeRecipient[:],
|
||||
Withdrawals: withdrawals,
|
||||
ParentBeaconBlockRoot: headRoot[:],
|
||||
ParentBeaconBlockRoot: parentRoot[:],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -47,9 +47,9 @@ func BenchmarkProposerAtts_sortByProfitability(b *testing.B) {
|
||||
runner := func(atts []ethpb.Att) {
|
||||
attsCopy := make(proposerAtts, len(atts))
|
||||
for i, att := range atts {
|
||||
attsCopy[i] = ethpb.CopyAttestation(att.(*ethpb.Attestation))
|
||||
attsCopy[i] = att.(*ethpb.Attestation).Copy()
|
||||
}
|
||||
_, err := attsCopy.sortByProfitability()
|
||||
_, err := attsCopy.sort()
|
||||
require.NoError(b, err, "Could not sort attestations by profitability")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"handlers.go",
|
||||
"server.go",
|
||||
"validator_performance.go",
|
||||
],
|
||||
@@ -10,32 +11,60 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//beacon-chain/rpc/lookup:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//network/httputil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["validator_performance_test.go"],
|
||||
srcs = [
|
||||
"handlers_test.go",
|
||||
"validator_performance_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
"//beacon-chain/rpc/testutil:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/state/stategen/mock:go_default_library",
|
||||
"//beacon-chain/sync/initial-sync/testing:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/blocks/testing:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//time:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
117
beacon-chain/rpc/prysm/validator/handlers.go
Normal file
117
beacon-chain/rpc/prysm/validator/handlers.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/network/httputil"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// GetParticipation retrieves the validator participation information for a given epoch,
|
||||
// it returns the information about validator's participation rate in voting on the proof of stake
|
||||
// rules based on their balance compared to the total active validator balance.
|
||||
func (s *Server) GetParticipation(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetParticipation")
|
||||
defer span.End()
|
||||
|
||||
stateId := mux.Vars(r)["state_id"]
|
||||
if stateId == "" {
|
||||
httputil.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
st, err := s.Stater.State(ctx, []byte(stateId))
|
||||
if err != nil {
|
||||
shared.WriteStateFetchError(w, err)
|
||||
return
|
||||
}
|
||||
stEpoch := slots.ToEpoch(st.Slot())
|
||||
vp, rpcError := s.CoreService.ValidatorParticipation(ctx, stEpoch)
|
||||
if rpcError != nil {
|
||||
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
|
||||
return
|
||||
}
|
||||
|
||||
response := &structs.GetValidatorParticipationResponse{
|
||||
Epoch: fmt.Sprintf("%d", vp.Epoch),
|
||||
Finalized: vp.Finalized,
|
||||
Participation: &structs.ValidatorParticipation{
|
||||
GlobalParticipationRate: fmt.Sprintf("%f", vp.Participation.GlobalParticipationRate),
|
||||
VotedEther: fmt.Sprintf("%d", vp.Participation.VotedEther),
|
||||
EligibleEther: fmt.Sprintf("%d", vp.Participation.EligibleEther),
|
||||
CurrentEpochActiveGwei: fmt.Sprintf("%d", vp.Participation.CurrentEpochActiveGwei),
|
||||
CurrentEpochAttestingGwei: fmt.Sprintf("%d", vp.Participation.CurrentEpochAttestingGwei),
|
||||
CurrentEpochTargetAttestingGwei: fmt.Sprintf("%d", vp.Participation.CurrentEpochTargetAttestingGwei),
|
||||
PreviousEpochActiveGwei: fmt.Sprintf("%d", vp.Participation.PreviousEpochActiveGwei),
|
||||
PreviousEpochAttestingGwei: fmt.Sprintf("%d", vp.Participation.PreviousEpochAttestingGwei),
|
||||
PreviousEpochTargetAttestingGwei: fmt.Sprintf("%d", vp.Participation.PreviousEpochTargetAttestingGwei),
|
||||
PreviousEpochHeadAttestingGwei: fmt.Sprintf("%d", vp.Participation.PreviousEpochHeadAttestingGwei),
|
||||
},
|
||||
}
|
||||
httputil.WriteJson(w, response)
|
||||
}
|
||||
|
||||
// GetActiveSetChanges retrieves the active set changes for a given epoch.
|
||||
//
|
||||
// This data includes any activations, voluntary exits, and involuntary
|
||||
// ejections.
|
||||
func (s *Server) GetActiveSetChanges(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetActiveSetChanges")
|
||||
defer span.End()
|
||||
|
||||
stateId := mux.Vars(r)["state_id"]
|
||||
if stateId == "" {
|
||||
httputil.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
st, err := s.Stater.State(ctx, []byte(stateId))
|
||||
if err != nil {
|
||||
shared.WriteStateFetchError(w, err)
|
||||
return
|
||||
}
|
||||
stEpoch := slots.ToEpoch(st.Slot())
|
||||
|
||||
as, rpcError := s.CoreService.ValidatorActiveSetChanges(ctx, stEpoch)
|
||||
if rpcError != nil {
|
||||
httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
|
||||
return
|
||||
}
|
||||
|
||||
response := &structs.ActiveSetChanges{
|
||||
Epoch: fmt.Sprintf("%d", as.Epoch),
|
||||
ActivatedPublicKeys: byteSlice2dToStringSlice(as.ActivatedPublicKeys),
|
||||
ActivatedIndices: uint64SliceToStringSlice(as.ActivatedIndices),
|
||||
ExitedPublicKeys: byteSlice2dToStringSlice(as.ExitedPublicKeys),
|
||||
ExitedIndices: uint64SliceToStringSlice(as.ExitedIndices),
|
||||
SlashedPublicKeys: byteSlice2dToStringSlice(as.SlashedPublicKeys),
|
||||
SlashedIndices: uint64SliceToStringSlice(as.SlashedIndices),
|
||||
EjectedPublicKeys: byteSlice2dToStringSlice(as.EjectedPublicKeys),
|
||||
EjectedIndices: uint64SliceToStringSlice(as.EjectedIndices),
|
||||
}
|
||||
httputil.WriteJson(w, response)
|
||||
}
|
||||
|
||||
func byteSlice2dToStringSlice(byteArrays [][]byte) []string {
|
||||
s := make([]string, len(byteArrays))
|
||||
for i, b := range byteArrays {
|
||||
s[i] = hexutil.Encode(b)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func uint64SliceToStringSlice(indices []primitives.ValidatorIndex) []string {
|
||||
s := make([]string, len(indices))
|
||||
for i, u := range indices {
|
||||
s[i] = fmt.Sprintf("%d", u)
|
||||
}
|
||||
return s
|
||||
}
|
||||
558
beacon-chain/rpc/prysm/validator/handlers_test.go
Normal file
558
beacon-chain/rpc/prysm/validator/handlers_test.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
|
||||
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/testutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
mockstategen "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen/mock"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
blocktest "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/v5/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
func addDefaultReplayerBuilder(s *Server, h stategen.HistoryAccessor) {
|
||||
cc := &mockstategen.CanonicalChecker{Is: true, Err: nil}
|
||||
cs := &mockstategen.CurrentSlotter{Slot: math.MaxUint64 - 1}
|
||||
s.CoreService.ReplayerBuilder = stategen.NewCanonicalHistory(h, cc, cs)
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorParticipation_NoState(t *testing.T) {
|
||||
headState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headState.SetSlot(0))
|
||||
|
||||
var st state.BeaconState
|
||||
st, _ = util.DeterministicGenesisState(t, 4)
|
||||
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: headState,
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
|
||||
url := "http://example.com" + fmt.Sprintf("%d", slots.ToEpoch(s.CoreService.GenesisTimeFetcher.CurrentSlot())+1)
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetParticipation(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
require.StringContains(t, "state_id is required in URL params", writer.Body.String())
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
validatorCount := uint64(32)
|
||||
|
||||
validators := make([]*ethpb.Validator, validatorCount)
|
||||
balances := make([]uint64, validatorCount)
|
||||
for i := 0; i < len(validators); i++ {
|
||||
validators[i] = ðpb.Validator{
|
||||
PublicKey: bytesutil.ToBytes(uint64(i), 48),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
}
|
||||
balances[i] = params.BeaconConfig().MaxEffectiveBalance
|
||||
}
|
||||
|
||||
atts := []*ethpb.PendingAttestation{{
|
||||
Data: util.HydrateAttestationData(ðpb.AttestationData{}),
|
||||
InclusionDelay: 1,
|
||||
AggregationBits: bitfield.NewBitlist(validatorCount / uint64(params.BeaconConfig().SlotsPerEpoch)),
|
||||
}}
|
||||
headState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headState.SetSlot(8))
|
||||
require.NoError(t, headState.SetValidators(validators))
|
||||
require.NoError(t, headState.SetBalances(balances))
|
||||
require.NoError(t, headState.AppendCurrentEpochAttestations(atts[0]))
|
||||
require.NoError(t, headState.AppendPreviousEpochAttestations(atts[0]))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Slot = 8
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
bRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: bRoot[:]}))
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: params.BeaconConfig().ZeroHash[:]}))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bRoot))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, bRoot))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, params.BeaconConfig().ZeroHash))
|
||||
|
||||
m := &mock.ChainService{State: headState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
|
||||
var st state.BeaconState
|
||||
st, _ = util.DeterministicGenesisState(t, 4)
|
||||
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
BeaconDB: beaconDB,
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: m,
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
FinalizedFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
},
|
||||
CanonicalFetcher: &mock.ChainService{
|
||||
CanonicalRoots: map[[32]byte]bool{
|
||||
bRoot: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetParticipation(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
want := &structs.GetValidatorParticipationResponse{
|
||||
Participation: &structs.ValidatorParticipation{
|
||||
GlobalParticipationRate: fmt.Sprintf("%f", float32(params.BeaconConfig().EffectiveBalanceIncrement)/float32(validatorCount*params.BeaconConfig().MaxEffectiveBalance)),
|
||||
VotedEther: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
EligibleEther: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
CurrentEpochActiveGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
CurrentEpochAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
CurrentEpochTargetAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
PreviousEpochActiveGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
PreviousEpochAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
PreviousEpochTargetAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
PreviousEpochHeadAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
},
|
||||
}
|
||||
var vp *structs.GetValidatorParticipationResponse
|
||||
err = json.NewDecoder(writer.Body).Decode(&vp)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Compare the response with the expected values
|
||||
assert.Equal(t, true, vp.Finalized, "Incorrect validator participation response")
|
||||
assert.Equal(t, *want.Participation, *vp.Participation, "Incorrect validator participation response")
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.BeaconConfig())
|
||||
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
validatorCount := uint64(100)
|
||||
|
||||
validators := make([]*ethpb.Validator, validatorCount)
|
||||
balances := make([]uint64, validatorCount)
|
||||
for i := 0; i < len(validators); i++ {
|
||||
validators[i] = ðpb.Validator{
|
||||
PublicKey: bytesutil.ToBytes(uint64(i), 48),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
}
|
||||
balances[i] = params.BeaconConfig().MaxEffectiveBalance
|
||||
}
|
||||
|
||||
atts := []*ethpb.PendingAttestation{{
|
||||
Data: util.HydrateAttestationData(ðpb.AttestationData{}),
|
||||
InclusionDelay: 1,
|
||||
AggregationBits: bitfield.NewBitlist(validatorCount / uint64(params.BeaconConfig().SlotsPerEpoch)),
|
||||
}}
|
||||
headState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headState.SetSlot(0))
|
||||
require.NoError(t, headState.SetValidators(validators))
|
||||
require.NoError(t, headState.SetBalances(balances))
|
||||
require.NoError(t, headState.AppendCurrentEpochAttestations(atts[0]))
|
||||
require.NoError(t, headState.AppendPreviousEpochAttestations(atts[0]))
|
||||
|
||||
b := util.NewBeaconBlock()
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
bRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bRoot))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, bRoot))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, params.BeaconConfig().ZeroHash))
|
||||
|
||||
m := &mock.ChainService{State: headState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
|
||||
var st state.BeaconState
|
||||
st, _ = util.DeterministicGenesisState(t, 4)
|
||||
s := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: m,
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
FinalizedFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
},
|
||||
CanonicalFetcher: &mock.ChainService{
|
||||
CanonicalRoots: map[[32]byte]bool{
|
||||
bRoot: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetParticipation(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
want := &structs.GetValidatorParticipationResponse{
|
||||
Participation: &structs.ValidatorParticipation{
|
||||
GlobalParticipationRate: fmt.Sprintf("%f", float32(params.BeaconConfig().EffectiveBalanceIncrement)/float32(validatorCount*params.BeaconConfig().MaxEffectiveBalance)),
|
||||
VotedEther: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
EligibleEther: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
CurrentEpochActiveGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
CurrentEpochAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
CurrentEpochTargetAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
PreviousEpochActiveGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
PreviousEpochAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
PreviousEpochTargetAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
PreviousEpochHeadAttestingGwei: fmt.Sprintf("%d", params.BeaconConfig().EffectiveBalanceIncrement),
|
||||
},
|
||||
}
|
||||
var vp *structs.GetValidatorParticipationResponse
|
||||
err = json.NewDecoder(writer.Body).Decode(&vp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.DeepEqual(t, true, vp.Finalized, "Incorrect validator participation respond")
|
||||
assert.DeepEqual(t, want.Participation, vp.Participation, "Incorrect validator participation respond")
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorParticipation_CurrentAndPrevEpochWithBits(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
params.OverrideBeaconConfig(params.BeaconConfig())
|
||||
transition.SkipSlotCache.Disable()
|
||||
|
||||
t.Run("altair", func(t *testing.T) {
|
||||
validatorCount := uint64(32)
|
||||
genState, _ := util.DeterministicGenesisStateAltair(t, validatorCount)
|
||||
|
||||
c, err := altair.NextSyncCommittee(context.Background(), genState)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, genState.SetCurrentSyncCommittee(c))
|
||||
|
||||
bits := make([]byte, validatorCount)
|
||||
for i := range bits {
|
||||
bits[i] = 0xff
|
||||
}
|
||||
require.NoError(t, genState.SetCurrentParticipationBits(bits))
|
||||
require.NoError(t, genState.SetPreviousParticipationBits(bits))
|
||||
gb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockAltair())
|
||||
assert.NoError(t, err)
|
||||
runGetValidatorParticipationCurrentEpoch(t, genState, gb)
|
||||
})
|
||||
|
||||
t.Run("bellatrix", func(t *testing.T) {
|
||||
validatorCount := uint64(32)
|
||||
genState, _ := util.DeterministicGenesisStateBellatrix(t, validatorCount)
|
||||
c, err := altair.NextSyncCommittee(context.Background(), genState)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, genState.SetCurrentSyncCommittee(c))
|
||||
|
||||
bits := make([]byte, validatorCount)
|
||||
for i := range bits {
|
||||
bits[i] = 0xff
|
||||
}
|
||||
require.NoError(t, genState.SetCurrentParticipationBits(bits))
|
||||
require.NoError(t, genState.SetPreviousParticipationBits(bits))
|
||||
gb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockBellatrix())
|
||||
assert.NoError(t, err)
|
||||
runGetValidatorParticipationCurrentEpoch(t, genState, gb)
|
||||
})
|
||||
|
||||
t.Run("capella", func(t *testing.T) {
|
||||
validatorCount := uint64(32)
|
||||
genState, _ := util.DeterministicGenesisStateCapella(t, validatorCount)
|
||||
c, err := altair.NextSyncCommittee(context.Background(), genState)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, genState.SetCurrentSyncCommittee(c))
|
||||
|
||||
bits := make([]byte, validatorCount)
|
||||
for i := range bits {
|
||||
bits[i] = 0xff
|
||||
}
|
||||
require.NoError(t, genState.SetCurrentParticipationBits(bits))
|
||||
require.NoError(t, genState.SetPreviousParticipationBits(bits))
|
||||
gb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella())
|
||||
assert.NoError(t, err)
|
||||
runGetValidatorParticipationCurrentEpoch(t, genState, gb)
|
||||
})
|
||||
}
|
||||
|
||||
func runGetValidatorParticipationCurrentEpoch(t *testing.T, genState state.BeaconState, gb interfaces.SignedBeaconBlock) {
|
||||
helpers.ClearCache()
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
validatorCount := uint64(32)
|
||||
|
||||
gsr, err := genState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
gb, err = blocktest.SetBlockStateRoot(gb, gsr)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
gRoot, err := gb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, beaconDB.SaveState(ctx, genState, gRoot))
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, gb))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
|
||||
m := &mock.ChainService{State: genState}
|
||||
offset := int64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
|
||||
s := &Server{
|
||||
BeaconDB: beaconDB,
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: genState,
|
||||
},
|
||||
CoreService: &core.Service{
|
||||
HeadFetcher: m,
|
||||
StateGen: stategen.New(beaconDB, doublylinkedtree.New()),
|
||||
GenesisTimeFetcher: &mock.ChainService{
|
||||
Genesis: prysmTime.Now().Add(time.Duration(-1*offset) * time.Second),
|
||||
},
|
||||
FinalizedFetcher: &mock.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 100}},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetParticipation(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
want := &structs.GetValidatorParticipationResponse{
|
||||
Participation: &structs.ValidatorParticipation{
|
||||
GlobalParticipationRate: "1.000000",
|
||||
VotedEther: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
EligibleEther: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
CurrentEpochActiveGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
CurrentEpochAttestingGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
CurrentEpochTargetAttestingGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
PreviousEpochActiveGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
PreviousEpochAttestingGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
PreviousEpochTargetAttestingGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
PreviousEpochHeadAttestingGwei: fmt.Sprintf("%d", validatorCount*params.BeaconConfig().MaxEffectiveBalance),
|
||||
},
|
||||
}
|
||||
|
||||
var vp *structs.GetValidatorParticipationResponse
|
||||
err = json.NewDecoder(writer.Body).Decode(&vp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.DeepEqual(t, true, vp.Finalized, "Incorrect validator participation respond")
|
||||
assert.DeepEqual(t, *want.Participation, *vp.Participation, "Incorrect validator participation respond")
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorActiveSetChanges_NoState(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
var st state.BeaconState
|
||||
st, _ = util.DeterministicGenesisState(t, 4)
|
||||
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
CoreService: &core.Service{
|
||||
BeaconDB: beaconDB,
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
HeadFetcher: &mock.ChainService{
|
||||
State: st,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
url := "http://example.com" + fmt.Sprintf("%d", slots.ToEpoch(s.CoreService.GenesisTimeFetcher.CurrentSlot())+1)
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": ""})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetActiveSetChanges(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
require.StringContains(t, "state_id is required in URL params", writer.Body.String())
|
||||
}
|
||||
|
||||
func TestServer_GetValidatorActiveSetChanges(t *testing.T) {
|
||||
beaconDB := dbTest.SetupDB(t)
|
||||
|
||||
ctx := context.Background()
|
||||
validators := make([]*ethpb.Validator, 8)
|
||||
headState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headState.SetSlot(0))
|
||||
require.NoError(t, headState.SetValidators(validators))
|
||||
for i := 0; i < len(validators); i++ {
|
||||
activationEpoch := params.BeaconConfig().FarFutureEpoch
|
||||
withdrawableEpoch := params.BeaconConfig().FarFutureEpoch
|
||||
exitEpoch := params.BeaconConfig().FarFutureEpoch
|
||||
slashed := false
|
||||
balance := params.BeaconConfig().MaxEffectiveBalance
|
||||
// Mark indices divisible by two as activated.
|
||||
if i%2 == 0 {
|
||||
activationEpoch = 0
|
||||
} else if i%3 == 0 {
|
||||
// Mark indices divisible by 3 as slashed.
|
||||
withdrawableEpoch = params.BeaconConfig().EpochsPerSlashingsVector
|
||||
slashed = true
|
||||
} else if i%5 == 0 {
|
||||
// Mark indices divisible by 5 as exited.
|
||||
exitEpoch = 0
|
||||
withdrawableEpoch = params.BeaconConfig().MinValidatorWithdrawabilityDelay
|
||||
} else if i%7 == 0 {
|
||||
// Mark indices divisible by 7 as ejected.
|
||||
exitEpoch = 0
|
||||
withdrawableEpoch = params.BeaconConfig().MinValidatorWithdrawabilityDelay
|
||||
balance = params.BeaconConfig().EjectionBalance
|
||||
}
|
||||
err := headState.UpdateValidatorAtIndex(primitives.ValidatorIndex(i), ðpb.Validator{
|
||||
ActivationEpoch: activationEpoch,
|
||||
PublicKey: pubKey(uint64(i)),
|
||||
EffectiveBalance: balance,
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
WithdrawableEpoch: withdrawableEpoch,
|
||||
Slashed: slashed,
|
||||
ExitEpoch: exitEpoch,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
b := util.NewBeaconBlock()
|
||||
util.SaveBlock(t, ctx, beaconDB, b)
|
||||
|
||||
gRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, gRoot))
|
||||
require.NoError(t, beaconDB.SaveState(ctx, headState, gRoot))
|
||||
|
||||
var st state.BeaconState
|
||||
st, _ = util.DeterministicGenesisState(t, 4)
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
CoreService: &core.Service{
|
||||
FinalizedFetcher: &mock.ChainService{
|
||||
FinalizedCheckPoint: ðpb.Checkpoint{Epoch: 0, Root: make([]byte, fieldparams.RootLength)},
|
||||
},
|
||||
GenesisTimeFetcher: &mock.ChainService{},
|
||||
},
|
||||
}
|
||||
addDefaultReplayerBuilder(s, beaconDB)
|
||||
|
||||
url := "http://example.com"
|
||||
request := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "genesis"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetActiveSetChanges(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
wantedActive := []string{
|
||||
hexutil.Encode(pubKey(0)),
|
||||
hexutil.Encode(pubKey(2)),
|
||||
hexutil.Encode(pubKey(4)),
|
||||
hexutil.Encode(pubKey(6)),
|
||||
}
|
||||
wantedActiveIndices := []string{"0", "2", "4", "6"}
|
||||
wantedExited := []string{
|
||||
hexutil.Encode(pubKey(5)),
|
||||
}
|
||||
wantedExitedIndices := []string{"5"}
|
||||
wantedSlashed := []string{
|
||||
hexutil.Encode(pubKey(3)),
|
||||
}
|
||||
wantedSlashedIndices := []string{"3"}
|
||||
wantedEjected := []string{
|
||||
hexutil.Encode(pubKey(7)),
|
||||
}
|
||||
wantedEjectedIndices := []string{"7"}
|
||||
want := &structs.ActiveSetChanges{
|
||||
Epoch: "0",
|
||||
ActivatedPublicKeys: wantedActive,
|
||||
ActivatedIndices: wantedActiveIndices,
|
||||
ExitedPublicKeys: wantedExited,
|
||||
ExitedIndices: wantedExitedIndices,
|
||||
SlashedPublicKeys: wantedSlashed,
|
||||
SlashedIndices: wantedSlashedIndices,
|
||||
EjectedPublicKeys: wantedEjected,
|
||||
EjectedIndices: wantedEjectedIndices,
|
||||
}
|
||||
|
||||
var as *structs.ActiveSetChanges
|
||||
err = json.NewDecoder(writer.Body).Decode(&as)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, *want, *as)
|
||||
}
|
||||
|
||||
func pubKey(i uint64) []byte {
|
||||
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
|
||||
binary.LittleEndian.PutUint64(pubKey, i)
|
||||
return pubKey
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
CoreService *core.Service
|
||||
BeaconDB db.ReadOnlyDatabase
|
||||
Stater lookup.Stater
|
||||
CanonicalFetcher blockchain.CanonicalFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
CoreService *core.Service
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package validator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/network/httputil"
|
||||
@@ -11,27 +13,32 @@ import (
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// GetValidatorPerformance is an HTTP handler for GetValidatorPerformance.
|
||||
func (s *Server) GetValidatorPerformance(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetValidatorPerformance")
|
||||
// GetPerformance is an HTTP handler for GetPerformance.
|
||||
func (s *Server) GetPerformance(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetPerformance")
|
||||
defer span.End()
|
||||
|
||||
var req structs.GetValidatorPerformanceRequest
|
||||
if r.Body != http.NoBody {
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
handleHTTPError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
computed, err := s.CoreService.ComputeValidatorPerformance(
|
||||
|
||||
computed, rpcError := s.CoreService.ComputeValidatorPerformance(
|
||||
ctx,
|
||||
ðpb.ValidatorPerformanceRequest{
|
||||
PublicKeys: req.PublicKeys,
|
||||
Indices: req.Indices,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
handleHTTPError(w, "Could not compute validator performance: "+err.Err.Error(), core.ErrorReasonToHTTP(err.Reason))
|
||||
if rpcError != nil {
|
||||
handleHTTPError(w, "Could not compute validator performance: "+rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason))
|
||||
return
|
||||
}
|
||||
response := &structs.GetValidatorPerformanceResponse{
|
||||
|
||||
@@ -35,13 +35,13 @@ func TestServer_GetValidatorPerformance(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetValidatorPerformance))
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetPerformance))
|
||||
req := httptest.NewRequest("POST", "/foo", nil)
|
||||
|
||||
client := &http.Client{}
|
||||
rawResp, err := client.Post(srv.URL, "application/json", req.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusServiceUnavailable, rawResp.StatusCode)
|
||||
require.Equal(t, http.StatusBadRequest, rawResp.StatusCode)
|
||||
})
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
@@ -86,7 +86,7 @@ func TestServer_GetValidatorPerformance(t *testing.T) {
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetValidatorPerformance))
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetPerformance))
|
||||
req := httptest.NewRequest("POST", "/foo", &buf)
|
||||
client := &http.Client{}
|
||||
rawResp, err := client.Post(srv.URL, "application/json", req.Body)
|
||||
@@ -151,7 +151,7 @@ func TestServer_GetValidatorPerformance(t *testing.T) {
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetValidatorPerformance))
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetPerformance))
|
||||
req := httptest.NewRequest("POST", "/foo", &buf)
|
||||
client := &http.Client{}
|
||||
rawResp, err := client.Post(srv.URL, "application/json", req.Body)
|
||||
@@ -216,7 +216,7 @@ func TestServer_GetValidatorPerformance(t *testing.T) {
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetValidatorPerformance))
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetPerformance))
|
||||
req := httptest.NewRequest("POST", "/foo", &buf)
|
||||
client := &http.Client{}
|
||||
rawResp, err := client.Post(srv.URL, "application/json", req.Body)
|
||||
@@ -278,7 +278,7 @@ func TestServer_GetValidatorPerformance(t *testing.T) {
|
||||
err := json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetValidatorPerformance))
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetPerformance))
|
||||
req := httptest.NewRequest("POST", "/foo", &buf)
|
||||
client := &http.Client{}
|
||||
rawResp, err := client.Post(srv.URL, "application/json", req.Body)
|
||||
@@ -340,7 +340,7 @@ func TestServer_GetValidatorPerformance(t *testing.T) {
|
||||
err := json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetValidatorPerformance))
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetPerformance))
|
||||
req := httptest.NewRequest("POST", "/foo", &buf)
|
||||
client := &http.Client{}
|
||||
rawResp, err := client.Post(srv.URL, "application/json", req.Body)
|
||||
@@ -402,7 +402,7 @@ func TestServer_GetValidatorPerformance(t *testing.T) {
|
||||
err := json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetValidatorPerformance))
|
||||
srv := httptest.NewServer(http.HandlerFunc(vs.GetPerformance))
|
||||
req := httptest.NewRequest("POST", "/foo", &buf)
|
||||
client := &http.Client{}
|
||||
rawResp, err := client.Post(srv.URL, "application/json", req.Body)
|
||||
|
||||
@@ -215,6 +215,7 @@ func NewService(ctx context.Context, cfg *Config) *Service {
|
||||
}
|
||||
rewardFetcher := &rewards.BlockRewardService{Replayer: ch, DB: s.cfg.BeaconDB}
|
||||
coreService := &core.Service{
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
GenesisTimeFetcher: s.cfg.GenesisTimeFetcher,
|
||||
SyncChecker: s.cfg.SyncService,
|
||||
@@ -225,6 +226,7 @@ func NewService(ctx context.Context, cfg *Config) *Service {
|
||||
StateGen: s.cfg.StateGen,
|
||||
P2P: s.cfg.Broadcaster,
|
||||
FinalizedFetcher: s.cfg.FinalizationFetcher,
|
||||
ReplayerBuilder: ch,
|
||||
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
|
||||
}
|
||||
validatorServer := &validatorv1alpha1.Server{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user