mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-18 16:54:26 -05:00
Compare commits
5 Commits
gloas/fork
...
gloas_post
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15d9b95165 | ||
|
|
045a3cfe4e | ||
|
|
8ee28394ab | ||
|
|
b31e2ffe51 | ||
|
|
22e77add54 |
@@ -63,6 +63,19 @@ type PeerCount struct {
|
||||
Connected string `json:"connected"`
|
||||
Disconnecting string `json:"disconnecting"`
|
||||
}
|
||||
type GetVersionV2Response struct {
|
||||
Data *VersionV2 `json:"data"`
|
||||
}
|
||||
type VersionV2 struct {
|
||||
BeaconNode *ClientVersionV1 `json:"beacon_node"`
|
||||
ExecutionClient *ClientVersionV1 `json:"execution_client,omitempty"`
|
||||
}
|
||||
type ClientVersionV1 struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
}
|
||||
|
||||
type GetVersionResponse struct {
|
||||
Data *Version `json:"data"`
|
||||
|
||||
@@ -46,7 +46,6 @@ type ForkchoiceFetcher interface {
|
||||
HighestReceivedBlockSlot() primitives.Slot
|
||||
ReceivedBlocksLastEpoch() (uint64, error)
|
||||
InsertNode(context.Context, state.BeaconState, consensus_blocks.ROBlock) error
|
||||
InsertPayload(context.Context, interfaces.ROExecutionPayloadEnvelope) error
|
||||
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
|
||||
NewSlot(context.Context, primitives.Slot) error
|
||||
ProposerBoost() [32]byte
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/forkchoice"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
@@ -56,13 +55,6 @@ func (s *Service) InsertNode(ctx context.Context, st state.BeaconState, block co
|
||||
return s.cfg.ForkChoiceStore.InsertNode(ctx, st, block)
|
||||
}
|
||||
|
||||
// InsertPayload is a wrapper for payload insertion which is self locked
|
||||
func (s *Service) InsertPayload(ctx context.Context, pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
return s.cfg.ForkChoiceStore.InsertPayload(ctx, pe)
|
||||
}
|
||||
|
||||
// ForkChoiceDump returns the corresponding value from forkchoice
|
||||
func (s *Service) ForkChoiceDump(ctx context.Context) (*forkchoice.Dump, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
|
||||
@@ -429,9 +429,9 @@ func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) {
|
||||
|
||||
// Insert Attestations to D, F and G so that they have higher weight than D
|
||||
// Ensure G is head
|
||||
fcs.ProcessAttestation(ctx, []uint64{0}, brd, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.ProcessAttestation(ctx, []uint64{1}, brf, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.ProcessAttestation(ctx, []uint64{2}, brg, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.ProcessAttestation(ctx, []uint64{0}, brd, 1)
|
||||
fcs.ProcessAttestation(ctx, []uint64{1}, brf, 1)
|
||||
fcs.ProcessAttestation(ctx, []uint64{2}, brg, 1)
|
||||
fcs.SetBalancesByRooter(service.cfg.StateGen.ActiveNonSlashedBalancesByRoot)
|
||||
jc := &forkchoicetypes.Checkpoint{Epoch: 0, Root: bra}
|
||||
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, jc))
|
||||
|
||||
@@ -56,7 +56,7 @@ type fcuConfig struct {
|
||||
// sendFCU handles the logic to notify the engine of a forckhoice update
|
||||
// when processing an incoming block during regular sync. It
|
||||
// always updates the shuffling caches and handles epoch transitions .
|
||||
func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
func (s *Service) sendFCU(cfg *postBlockProcessConfig) {
|
||||
if cfg.postState.Version() < version.Fulu {
|
||||
// update the caches to compute the right proposer index
|
||||
// this function is called under a forkchoice lock which we need to release.
|
||||
@@ -64,7 +64,8 @@ func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
s.updateCachesPostBlockProcessing(cfg)
|
||||
s.ForkChoicer().Lock()
|
||||
}
|
||||
if err := s.getFCUArgs(cfg, fcuArgs); err != nil {
|
||||
fcuArgs, err := s.getFCUArgs(cfg)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get forkchoice update argument")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,11 +96,7 @@ func (s *Service) OnAttestation(ctx context.Context, a ethpb.Att, disparity time
|
||||
// We assume trusted attestation in this function has verified signature.
|
||||
|
||||
// Update forkchoice store with the new attestation for updating weight.
|
||||
attData := a.GetData()
|
||||
payloadStatus := true
|
||||
if attData.Target.Epoch >= params.BeaconConfig().GloasForkEpoch {
|
||||
payloadStatus = attData.CommitteeIndex == 1
|
||||
}
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(attData.BeaconBlockRoot), attData.Slot, payloadStatus)
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(a.GetData().BeaconBlockRoot), a.GetData().Target.Epoch)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -64,7 +64,6 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
return invalidBlock{error: err}
|
||||
}
|
||||
startTime := time.Now()
|
||||
fcuArgs := &fcuConfig{}
|
||||
|
||||
if features.Get().EnableLightClient && slots.ToEpoch(s.CurrentSlot()) >= params.BeaconConfig().AltairForkEpoch {
|
||||
defer s.processLightClientUpdates(cfg)
|
||||
@@ -102,7 +101,9 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
s.logNonCanonicalBlockReceived(cfg.roblock.Root(), cfg.headRoot)
|
||||
return nil
|
||||
}
|
||||
s.sendFCU(cfg, fcuArgs)
|
||||
if cfg.roblock.Version() <= version.Gloas {
|
||||
s.sendFCU(cfg)
|
||||
}
|
||||
|
||||
// Pre-Fulu the caches are updated when computing the payload attributes
|
||||
if cfg.postState.Version() >= version.Fulu {
|
||||
@@ -402,11 +403,7 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Re
|
||||
}
|
||||
r := bytesutil.ToBytes32(a.GetData().BeaconBlockRoot)
|
||||
if s.cfg.ForkChoiceStore.HasNode(r) {
|
||||
payloadStatus := true
|
||||
if a.GetData().Target.Epoch >= params.BeaconConfig().GloasForkEpoch {
|
||||
payloadStatus = a.GetData().CommitteeIndex == 1
|
||||
}
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Slot, payloadStatus)
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Target.Epoch)
|
||||
} else if features.Get().EnableExperimentalAttestationPool {
|
||||
if err = s.cfg.AttestationCache.Add(a); err != nil {
|
||||
return err
|
||||
|
||||
@@ -38,23 +38,26 @@ func (s *Service) CurrentSlot() primitives.Slot {
|
||||
}
|
||||
|
||||
// getFCUArgs returns the arguments to call forkchoice update
|
||||
func (s *Service) getFCUArgs(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
if err := s.getFCUArgsEarlyBlock(cfg, fcuArgs); err != nil {
|
||||
return err
|
||||
func (s *Service) getFCUArgs(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
|
||||
fcuArgs, err := s.getFCUArgsEarlyBlock(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fcuArgs.attributes = s.getPayloadAttribute(cfg.ctx, fcuArgs.headState, fcuArgs.proposingSlot, cfg.headRoot[:])
|
||||
return nil
|
||||
return fcuArgs, nil
|
||||
}
|
||||
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
if cfg.roblock.Root() == cfg.headRoot {
|
||||
fcuArgs.headState = cfg.postState
|
||||
fcuArgs.headBlock = cfg.roblock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return nil
|
||||
return &fcuConfig{
|
||||
headState: cfg.postState,
|
||||
headBlock: cfg.roblock,
|
||||
headRoot: cfg.headRoot,
|
||||
proposingSlot: s.CurrentSlot() + 1,
|
||||
}, nil
|
||||
}
|
||||
return s.fcuArgsNonCanonicalBlock(cfg, fcuArgs)
|
||||
return s.fcuArgsNonCanonicalBlock(cfg)
|
||||
}
|
||||
|
||||
// logNonCanonicalBlockReceived prints a message informing that the received
|
||||
@@ -79,16 +82,17 @@ func (s *Service) logNonCanonicalBlockReceived(blockRoot [32]byte, headRoot [32]
|
||||
|
||||
// fcuArgsNonCanonicalBlock returns the arguments to the FCU call when the
|
||||
// incoming block is non-canonical, that is, based on the head root.
|
||||
func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
headState, headBlock, err := s.getStateAndBlock(cfg.ctx, cfg.headRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
fcuArgs.headState = headState
|
||||
fcuArgs.headBlock = headBlock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return nil
|
||||
return &fcuConfig{
|
||||
headState: headState,
|
||||
headBlock: headBlock,
|
||||
headRoot: cfg.headRoot,
|
||||
proposingSlot: s.CurrentSlot() + 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sendStateFeedOnBlock sends an event that a new block has been synced
|
||||
|
||||
@@ -2417,14 +2417,12 @@ func Test_getFCUArgs(t *testing.T) {
|
||||
isValidPayload: true,
|
||||
}
|
||||
// error branch
|
||||
fcuArgs := &fcuConfig{}
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
_, err = s.getFCUArgs(cfg)
|
||||
require.ErrorContains(t, "block does not exist", err)
|
||||
|
||||
// canonical branch
|
||||
cfg.headRoot = cfg.roblock.Root()
|
||||
fcuArgs = &fcuConfig{}
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
fcuArgs, err := s.getFCUArgs(cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cfg.roblock.Root(), fcuArgs.headRoot)
|
||||
}
|
||||
|
||||
@@ -700,14 +700,6 @@ func (s *ChainService) InsertNode(ctx context.Context, st state.BeaconState, blo
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertPayload mocks the same method in the chain service
|
||||
func (s *ChainService) InsertPayload(ctx context.Context, pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.InsertPayload(ctx, pe)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForkChoiceDump mocks the same method in the chain service
|
||||
func (s *ChainService) ForkChoiceDump(ctx context.Context) (*forkchoice2.Dump, error) {
|
||||
if s.ForkChoiceStore != nil {
|
||||
|
||||
@@ -112,6 +112,34 @@ func ProcessExecutionPayload(
|
||||
return errors.Wrap(err, "signature verification failed")
|
||||
}
|
||||
|
||||
envelope, err := signedEnvelope.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope from signed envelope")
|
||||
}
|
||||
|
||||
if err := ApplyExecutionPayload(ctx, st, envelope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := st.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get hash tree root")
|
||||
}
|
||||
if r != envelope.StateRoot() {
|
||||
return fmt.Errorf("state root mismatch: expected %#x, got %#x", envelope.StateRoot(), r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyExecutionPayload applies the execution payload envelope to the state and performs the same
|
||||
// consistency checks as the full processing path. This keeps the post-payload state root computation
|
||||
// on a shared code path, even though some bid/payload checks are not strictly required for the root itself.
|
||||
func ApplyExecutionPayload(
|
||||
ctx context.Context,
|
||||
st state.BeaconState,
|
||||
envelope interfaces.ROExecutionPayloadEnvelope,
|
||||
) error {
|
||||
latestHeader := st.LatestBlockHeader()
|
||||
if len(latestHeader.StateRoot) == 0 || bytes.Equal(latestHeader.StateRoot, make([]byte, 32)) {
|
||||
previousStateRoot, err := st.HashTreeRoot(ctx)
|
||||
@@ -128,10 +156,6 @@ func ProcessExecutionPayload(
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute block header root")
|
||||
}
|
||||
envelope, err := signedEnvelope.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope from signed envelope")
|
||||
}
|
||||
|
||||
beaconBlockRoot := envelope.BeaconBlockRoot()
|
||||
if !bytes.Equal(beaconBlockRoot[:], blockHeaderRoot[:]) {
|
||||
@@ -217,14 +241,6 @@ func ProcessExecutionPayload(
|
||||
return errors.Wrap(err, "could not set latest block hash")
|
||||
}
|
||||
|
||||
r, err := st.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get hash tree root")
|
||||
}
|
||||
if r != envelope.StateRoot() {
|
||||
return fmt.Errorf("state root mismatch: expected %#x, got %#x", envelope.StateRoot(), r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -73,23 +73,22 @@ func PopulateFromSidecar(sidecar blocks.VerifiedRODataColumn) *SidecarReconstruc
|
||||
|
||||
// ValidatorsCustodyRequirement returns the number of custody groups regarding the validator indices attached to the beacon node.
|
||||
// https://github.com/ethereum/consensus-specs/blob/master/specs/fulu/validator.md#validator-custody
|
||||
func ValidatorsCustodyRequirement(state beaconState.ReadOnlyBeaconState, validatorsIndex map[primitives.ValidatorIndex]bool) (uint64, error) {
|
||||
totalNodeBalance := uint64(0)
|
||||
func ValidatorsCustodyRequirement(st beaconState.ReadOnlyBalances, validatorsIndex map[primitives.ValidatorIndex]bool) (uint64, error) {
|
||||
cfg := params.BeaconConfig()
|
||||
idxs := make([]primitives.ValidatorIndex, 0, len(validatorsIndex))
|
||||
for index := range validatorsIndex {
|
||||
validator, err := state.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "validator at index %v", index)
|
||||
}
|
||||
|
||||
totalNodeBalance += validator.EffectiveBalance()
|
||||
idxs = append(idxs, index)
|
||||
}
|
||||
totalBalance, err := st.EffectiveBalanceSum(idxs)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "effective balances")
|
||||
}
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
numberOfCustodyGroups := cfg.NumberOfCustodyGroups
|
||||
validatorCustodyRequirement := cfg.ValidatorCustodyRequirement
|
||||
balancePerAdditionalCustodyGroup := cfg.BalancePerAdditionalCustodyGroup
|
||||
|
||||
count := totalNodeBalance / balancePerAdditionalCustodyGroup
|
||||
count := totalBalance / balancePerAdditionalCustodyGroup
|
||||
return min(max(count, validatorCustodyRequirement), numberOfCustodyGroups), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ go_library(
|
||||
"//testing/spectest:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/blockchain/kzg:go_default_library",
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/cache/depositsnapshot:go_default_library",
|
||||
@@ -101,6 +102,7 @@ go_test(
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/blockchain/kzg:go_default_library",
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/execution/types"
|
||||
@@ -109,6 +110,8 @@ const (
|
||||
GetBlobsV1 = "engine_getBlobsV1"
|
||||
// GetBlobsV2 request string for JSON-RPC.
|
||||
GetBlobsV2 = "engine_getBlobsV2"
|
||||
// GetClientVersionV1 is the JSON-RPC method that identifies the execution client.
|
||||
GetClientVersionV1 = "engine_getClientVersionV1"
|
||||
// Defines the seconds before timing out engine endpoints with non-block execution semantics.
|
||||
defaultEngineTimeout = time.Second
|
||||
)
|
||||
@@ -145,6 +148,7 @@ type EngineCaller interface {
|
||||
GetPayload(ctx context.Context, payloadId [8]byte, slot primitives.Slot) (*blocks.GetPayloadResponse, error)
|
||||
ExecutionBlockByHash(ctx context.Context, hash common.Hash, withTxs bool) (*pb.ExecutionBlock, error)
|
||||
GetTerminalBlockHash(ctx context.Context, transitionTime uint64) ([]byte, bool, error)
|
||||
GetClientVersionV1(ctx context.Context) ([]*structs.ClientVersionV1, error)
|
||||
}
|
||||
|
||||
var ErrEmptyBlockHash = errors.New("Block hash is empty 0x0000...")
|
||||
@@ -581,6 +585,39 @@ func (s *Service) GetBlobsV2(ctx context.Context, versionedHashes []common.Hash)
|
||||
return result, handleRPCError(err)
|
||||
}
|
||||
|
||||
func (s *Service) GetClientVersionV1(ctx context.Context) ([]*structs.ClientVersionV1, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.GetClientVersionV1")
|
||||
defer span.End()
|
||||
|
||||
commit := version.GitCommit()
|
||||
if len(commit) >= 8 {
|
||||
commit = commit[:8]
|
||||
}
|
||||
|
||||
var result []*structs.ClientVersionV1
|
||||
err := s.rpcClient.CallContext(
|
||||
ctx,
|
||||
&result,
|
||||
GetClientVersionV1,
|
||||
structs.ClientVersionV1{
|
||||
Code: "PM",
|
||||
Name: "Prysm",
|
||||
Version: version.SemanticVersion(),
|
||||
Commit: commit,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, handleRPCError(err)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return nil, errors.New("execution client returned no result")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ReconstructFullBlock takes in a blinded beacon block and reconstructs
|
||||
// a beacon block with a full execution payload via the engine API.
|
||||
func (s *Service) ReconstructFullBlock(
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/db/filesystem"
|
||||
@@ -999,6 +1000,99 @@ func TestClient_HTTP(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, want, resp)
|
||||
})
|
||||
t.Run(GetClientVersionV1, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want any
|
||||
resp []*structs.ClientVersionV1
|
||||
hasError bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
want: []*structs.ClientVersionV1{{
|
||||
Code: "GE",
|
||||
Name: "go-ethereum",
|
||||
Version: "1.15.11-stable",
|
||||
Commit: "36b2371c",
|
||||
}},
|
||||
resp: []*structs.ClientVersionV1{{
|
||||
Code: "GE",
|
||||
Name: "go-ethereum",
|
||||
Version: "1.15.11-stable",
|
||||
Commit: "36b2371c",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "empty response",
|
||||
want: []*structs.ClientVersionV1{},
|
||||
hasError: true,
|
||||
errMsg: "execution client returned no result",
|
||||
},
|
||||
{
|
||||
name: "RPC error",
|
||||
want: "brokenMsg",
|
||||
hasError: true,
|
||||
errMsg: "unexpected error in JSON-RPC",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
defer func() {
|
||||
require.NoError(t, r.Body.Close())
|
||||
}()
|
||||
enc, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
jsonRequestString := string(enc)
|
||||
|
||||
// We expect the JSON string RPC request contains the right method name.
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, GetClientVersionV1,
|
||||
))
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, "\"code\":\"PM\"",
|
||||
))
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, "\"name\":\"Prysm\"",
|
||||
))
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, fmt.Sprintf("\"version\":\"%s\"", version.SemanticVersion()),
|
||||
))
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, fmt.Sprintf("\"commit\":\"%s\"", version.GitCommit()[:8]),
|
||||
))
|
||||
resp := map[string]any{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": tc.want,
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(resp)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
rpcClient, err := rpc.DialHTTP(srv.URL)
|
||||
require.NoError(t, err)
|
||||
defer rpcClient.Close()
|
||||
|
||||
service := &Service{}
|
||||
service.rpcClient = rpcClient
|
||||
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
resp, err := service.GetClientVersionV1(ctx)
|
||||
if tc.hasError {
|
||||
require.NotNil(t, err)
|
||||
require.ErrorContains(t, tc.errMsg, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.DeepEqual(t, tc.resp, resp)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestReconstructFullBellatrixBlock(t *testing.T) {
|
||||
|
||||
@@ -13,6 +13,7 @@ go_library(
|
||||
"//visibility:public",
|
||||
],
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//async/event:go_default_library",
|
||||
"//beacon-chain/core/peerdas:go_default_library",
|
||||
"//beacon-chain/execution/types:go_default_library",
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
@@ -42,6 +43,8 @@ type EngineClient struct {
|
||||
ErrorBlobSidecars error
|
||||
DataColumnSidecars []blocks.VerifiedRODataColumn
|
||||
ErrorDataColumnSidecars error
|
||||
ClientVersion []*structs.ClientVersionV1
|
||||
ErrorClientVersion error
|
||||
}
|
||||
|
||||
// NewPayload --
|
||||
@@ -173,3 +176,8 @@ func (e *EngineClient) GetTerminalBlockHash(ctx context.Context, transitionTime
|
||||
blk = parentBlk
|
||||
}
|
||||
}
|
||||
|
||||
// GetClientVersionV1 --
|
||||
func (e *EngineClient) GetClientVersionV1(context.Context) ([]*structs.ClientVersionV1, error) {
|
||||
return e.ClientVersion, e.ErrorClientVersion
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ go_library(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/forkchoice:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
|
||||
@@ -52,7 +52,6 @@ go_test(
|
||||
srcs = [
|
||||
"ffg_update_test.go",
|
||||
"forkchoice_test.go",
|
||||
"gloas_test.go",
|
||||
"no_vote_test.go",
|
||||
"node_test.go",
|
||||
"on_tick_test.go",
|
||||
@@ -72,7 +71,6 @@ go_test(
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/forkchoice:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
|
||||
@@ -161,7 +161,7 @@ func TestFFGUpdates_TwoBranches(t *testing.T) {
|
||||
// 7 8
|
||||
// | |
|
||||
// 9 10
|
||||
f.ProcessAttestation(t.Context(), []uint64{0}, indexToHash(1), 0, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{0}, indexToHash(1), 0)
|
||||
|
||||
// With the additional vote to the left branch, the head should be 9:
|
||||
// 0 <-- start
|
||||
@@ -191,7 +191,7 @@ func TestFFGUpdates_TwoBranches(t *testing.T) {
|
||||
// 7 8
|
||||
// | |
|
||||
// 9 10
|
||||
f.ProcessAttestation(t.Context(), []uint64{1}, indexToHash(2), 0, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{1}, indexToHash(2), 0)
|
||||
|
||||
// With the additional vote to the right branch, the head should be 10:
|
||||
// 0 <-- start
|
||||
|
||||
@@ -80,25 +80,24 @@ func (f *ForkChoice) Head(
|
||||
|
||||
// ProcessAttestation processes attestation for vote accounting, it iterates around validator indices
|
||||
// and update their votes accordingly.
|
||||
func (f *ForkChoice) ProcessAttestation(ctx context.Context, validatorIndices []uint64, blockRoot [32]byte, slot primitives.Slot, payloadStatus bool) {
|
||||
func (f *ForkChoice) ProcessAttestation(ctx context.Context, validatorIndices []uint64, blockRoot [32]byte, targetEpoch primitives.Epoch) {
|
||||
_, span := trace.StartSpan(ctx, "doublyLinkedForkchoice.ProcessAttestation")
|
||||
defer span.End()
|
||||
|
||||
for _, index := range validatorIndices {
|
||||
// Validator indices will grow the vote cache.
|
||||
newVote := false
|
||||
for index >= uint64(len(f.votes)) {
|
||||
f.votes = append(f.votes, Vote{currentRoot: params.BeaconConfig().ZeroHash, nextRoot: params.BeaconConfig().ZeroHash})
|
||||
newVote = true
|
||||
}
|
||||
|
||||
// Newly allocated vote if the root fields are untouched.
|
||||
newVote := f.votes[index].nextRoot == params.BeaconConfig().ZeroHash &&
|
||||
f.votes[index].currentRoot == params.BeaconConfig().ZeroHash
|
||||
|
||||
// Vote gets updated if it's newly allocated or high target epoch.
|
||||
targetEpoch := slots.ToEpoch(slot)
|
||||
nextEpoch := slots.ToEpoch(f.votes[index].nextSlot)
|
||||
if newVote || targetEpoch > nextEpoch {
|
||||
f.votes[index].nextSlot = slot
|
||||
if newVote || targetEpoch > f.votes[index].nextEpoch {
|
||||
f.votes[index].nextEpoch = targetEpoch
|
||||
f.votes[index].nextRoot = blockRoot
|
||||
f.votes[index].nextPayloadStatus = payloadStatus
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,56 +309,42 @@ func (f *ForkChoice) updateBalances() error {
|
||||
|
||||
// Update only if the validator's balance or vote has changed.
|
||||
if vote.currentRoot != vote.nextRoot || oldBalance != newBalance {
|
||||
// Add new balance to the next vote target if the root is known.
|
||||
pn, pending := f.store.resolveVoteNode(vote.nextRoot, vote.nextSlot, vote.nextPayloadStatus)
|
||||
if pn != nil && vote.nextRoot != zHash {
|
||||
if pending {
|
||||
pn.node.balance += newBalance
|
||||
} else {
|
||||
pn.balance += newBalance
|
||||
// Ignore the vote if the root is not in fork choice
|
||||
// store, that means we have not seen the block before.
|
||||
nextNode, ok := f.store.emptyNodeByRoot[vote.nextRoot]
|
||||
if ok && vote.nextRoot != zHash {
|
||||
// Protection against nil node
|
||||
if nextNode == nil {
|
||||
return errors.Wrap(ErrNilNode, "could not update balances")
|
||||
}
|
||||
nextNode.balance += newBalance
|
||||
}
|
||||
|
||||
// Subtract old balance from the current vote target if the root is known.
|
||||
pn, pending = f.store.resolveVoteNode(vote.currentRoot, vote.currentSlot, vote.currentPayloadStatus)
|
||||
if pn != nil && vote.currentRoot != zHash {
|
||||
if pending {
|
||||
if pn.node.balance < oldBalance {
|
||||
log.WithFields(logrus.Fields{
|
||||
"nodeRoot": fmt.Sprintf("%#x", bytesutil.Trunc(vote.currentRoot[:])),
|
||||
"oldBalance": oldBalance,
|
||||
"nodeBalance": pn.node.balance,
|
||||
"nodeWeight": pn.node.weight,
|
||||
"proposerBoostRoot": fmt.Sprintf("%#x", bytesutil.Trunc(f.store.proposerBoostRoot[:])),
|
||||
"previousProposerBoostRoot": fmt.Sprintf("%#x", bytesutil.Trunc(f.store.previousProposerBoostRoot[:])),
|
||||
"previousProposerBoostScore": f.store.previousProposerBoostScore,
|
||||
}).Warning("node with invalid balance, setting it to zero")
|
||||
pn.node.balance = 0
|
||||
} else {
|
||||
pn.node.balance -= oldBalance
|
||||
}
|
||||
currentNode, ok := f.store.emptyNodeByRoot[vote.currentRoot]
|
||||
if ok && vote.currentRoot != zHash {
|
||||
// Protection against nil node
|
||||
if currentNode == nil {
|
||||
return errors.Wrap(ErrNilNode, "could not update balances")
|
||||
}
|
||||
if currentNode.balance < oldBalance {
|
||||
log.WithFields(logrus.Fields{
|
||||
"nodeRoot": fmt.Sprintf("%#x", bytesutil.Trunc(vote.currentRoot[:])),
|
||||
"oldBalance": oldBalance,
|
||||
"nodeBalance": currentNode.balance,
|
||||
"nodeWeight": currentNode.weight,
|
||||
"proposerBoostRoot": fmt.Sprintf("%#x", bytesutil.Trunc(f.store.proposerBoostRoot[:])),
|
||||
"previousProposerBoostRoot": fmt.Sprintf("%#x", bytesutil.Trunc(f.store.previousProposerBoostRoot[:])),
|
||||
"previousProposerBoostScore": f.store.previousProposerBoostScore,
|
||||
}).Warning("node with invalid balance, setting it to zero")
|
||||
currentNode.balance = 0
|
||||
} else {
|
||||
if pn.balance < oldBalance {
|
||||
log.WithFields(logrus.Fields{
|
||||
"nodeRoot": fmt.Sprintf("%#x", bytesutil.Trunc(vote.currentRoot[:])),
|
||||
"oldBalance": oldBalance,
|
||||
"nodeBalance": pn.balance,
|
||||
"nodeWeight": pn.weight,
|
||||
"proposerBoostRoot": fmt.Sprintf("%#x", bytesutil.Trunc(f.store.proposerBoostRoot[:])),
|
||||
"previousProposerBoostRoot": fmt.Sprintf("%#x", bytesutil.Trunc(f.store.previousProposerBoostRoot[:])),
|
||||
"previousProposerBoostScore": f.store.previousProposerBoostScore,
|
||||
}).Warning("node with invalid balance, setting it to zero")
|
||||
pn.balance = 0
|
||||
} else {
|
||||
pn.balance -= oldBalance
|
||||
}
|
||||
currentNode.balance -= oldBalance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate the validator vote.
|
||||
f.votes[index].currentRoot = vote.nextRoot
|
||||
f.votes[index].currentSlot = vote.nextSlot
|
||||
f.votes[index].currentPayloadStatus = vote.nextPayloadStatus
|
||||
}
|
||||
f.balances = newBalances
|
||||
return nil
|
||||
@@ -425,23 +410,15 @@ func (f *ForkChoice) InsertSlashedIndex(_ context.Context, index primitives.Vali
|
||||
return
|
||||
}
|
||||
|
||||
v := f.votes[index]
|
||||
pn, pending := f.store.resolveVoteNode(v.currentRoot, v.currentSlot, v.currentPayloadStatus)
|
||||
if pn == nil {
|
||||
node, ok := f.store.emptyNodeByRoot[f.votes[index].currentRoot]
|
||||
if !ok || node == nil {
|
||||
return
|
||||
}
|
||||
if pending {
|
||||
if pn.node.balance < f.balances[index] {
|
||||
pn.node.balance = 0
|
||||
} else {
|
||||
pn.node.balance -= f.balances[index]
|
||||
}
|
||||
return
|
||||
}
|
||||
if pn.balance < f.balances[index] {
|
||||
pn.balance = 0
|
||||
|
||||
if node.balance < f.balances[index] {
|
||||
node.balance = 0
|
||||
} else {
|
||||
pn.balance -= f.balances[index]
|
||||
node.balance -= f.balances[index]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,9 +93,9 @@ func TestForkChoice_UpdateBalancesPositiveChange(t *testing.T) {
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
f.votes = []Vote{
|
||||
{indexToHash(1), indexToHash(1), 0, 0, true, true},
|
||||
{indexToHash(2), indexToHash(2), 0, 0, true, true},
|
||||
{indexToHash(3), indexToHash(3), 0, 0, true, true},
|
||||
{indexToHash(1), indexToHash(1), 0},
|
||||
{indexToHash(2), indexToHash(2), 0},
|
||||
{indexToHash(3), indexToHash(3), 0},
|
||||
}
|
||||
|
||||
// Each node gets one unique vote. The weight should look like 103 <- 102 <- 101 because
|
||||
@@ -103,9 +103,9 @@ func TestForkChoice_UpdateBalancesPositiveChange(t *testing.T) {
|
||||
f.justifiedBalances = []uint64{10, 20, 30}
|
||||
require.NoError(t, f.updateBalances())
|
||||
s := f.store
|
||||
assert.Equal(t, uint64(10), s.fullNodeByRoot[indexToHash(1)].balance)
|
||||
assert.Equal(t, uint64(20), s.fullNodeByRoot[indexToHash(2)].balance)
|
||||
assert.Equal(t, uint64(30), s.fullNodeByRoot[indexToHash(3)].balance)
|
||||
assert.Equal(t, uint64(10), s.emptyNodeByRoot[indexToHash(1)].balance)
|
||||
assert.Equal(t, uint64(20), s.emptyNodeByRoot[indexToHash(2)].balance)
|
||||
assert.Equal(t, uint64(30), s.emptyNodeByRoot[indexToHash(3)].balance)
|
||||
}
|
||||
|
||||
func TestForkChoice_UpdateBalancesNegativeChange(t *testing.T) {
|
||||
@@ -121,22 +121,22 @@ func TestForkChoice_UpdateBalancesNegativeChange(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
s := f.store
|
||||
s.fullNodeByRoot[indexToHash(1)].balance = 100
|
||||
s.fullNodeByRoot[indexToHash(2)].balance = 100
|
||||
s.fullNodeByRoot[indexToHash(3)].balance = 100
|
||||
s.emptyNodeByRoot[indexToHash(1)].balance = 100
|
||||
s.emptyNodeByRoot[indexToHash(2)].balance = 100
|
||||
s.emptyNodeByRoot[indexToHash(3)].balance = 100
|
||||
|
||||
f.balances = []uint64{100, 100, 100}
|
||||
f.votes = []Vote{
|
||||
{indexToHash(1), indexToHash(1), 0, 0, true, true},
|
||||
{indexToHash(2), indexToHash(2), 0, 0, true, true},
|
||||
{indexToHash(3), indexToHash(3), 0, 0, true, true},
|
||||
{indexToHash(1), indexToHash(1), 0},
|
||||
{indexToHash(2), indexToHash(2), 0},
|
||||
{indexToHash(3), indexToHash(3), 0},
|
||||
}
|
||||
|
||||
f.justifiedBalances = []uint64{10, 20, 30}
|
||||
require.NoError(t, f.updateBalances())
|
||||
assert.Equal(t, uint64(10), s.fullNodeByRoot[indexToHash(1)].balance)
|
||||
assert.Equal(t, uint64(20), s.fullNodeByRoot[indexToHash(2)].balance)
|
||||
assert.Equal(t, uint64(30), s.fullNodeByRoot[indexToHash(3)].balance)
|
||||
assert.Equal(t, uint64(10), s.emptyNodeByRoot[indexToHash(1)].balance)
|
||||
assert.Equal(t, uint64(20), s.emptyNodeByRoot[indexToHash(2)].balance)
|
||||
assert.Equal(t, uint64(30), s.emptyNodeByRoot[indexToHash(3)].balance)
|
||||
}
|
||||
|
||||
func TestForkChoice_UpdateBalancesUnderflow(t *testing.T) {
|
||||
@@ -152,22 +152,22 @@ func TestForkChoice_UpdateBalancesUnderflow(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
s := f.store
|
||||
s.fullNodeByRoot[indexToHash(1)].balance = 100
|
||||
s.fullNodeByRoot[indexToHash(2)].balance = 100
|
||||
s.fullNodeByRoot[indexToHash(3)].balance = 100
|
||||
s.emptyNodeByRoot[indexToHash(1)].balance = 100
|
||||
s.emptyNodeByRoot[indexToHash(2)].balance = 100
|
||||
s.emptyNodeByRoot[indexToHash(3)].balance = 100
|
||||
|
||||
f.balances = []uint64{125, 125, 125}
|
||||
f.votes = []Vote{
|
||||
{indexToHash(1), indexToHash(1), 0, 0, true, true},
|
||||
{indexToHash(2), indexToHash(2), 0, 0, true, true},
|
||||
{indexToHash(3), indexToHash(3), 0, 0, true, true},
|
||||
{indexToHash(1), indexToHash(1), 0},
|
||||
{indexToHash(2), indexToHash(2), 0},
|
||||
{indexToHash(3), indexToHash(3), 0},
|
||||
}
|
||||
|
||||
f.justifiedBalances = []uint64{10, 20, 30}
|
||||
require.NoError(t, f.updateBalances())
|
||||
assert.Equal(t, uint64(0), s.fullNodeByRoot[indexToHash(1)].balance)
|
||||
assert.Equal(t, uint64(0), s.fullNodeByRoot[indexToHash(2)].balance)
|
||||
assert.Equal(t, uint64(5), s.fullNodeByRoot[indexToHash(3)].balance)
|
||||
assert.Equal(t, uint64(0), s.emptyNodeByRoot[indexToHash(1)].balance)
|
||||
assert.Equal(t, uint64(0), s.emptyNodeByRoot[indexToHash(2)].balance)
|
||||
assert.Equal(t, uint64(5), s.emptyNodeByRoot[indexToHash(3)].balance)
|
||||
}
|
||||
|
||||
func TestForkChoice_IsCanonical(t *testing.T) {
|
||||
@@ -332,8 +332,8 @@ func TestForkChoice_RemoveEquivocating(t *testing.T) {
|
||||
require.Equal(t, [32]byte{'c'}, head)
|
||||
|
||||
// Insert two attestations for block b, one for c it becomes head
|
||||
f.ProcessAttestation(ctx, []uint64{1, 2}, [32]byte{'b'}, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(ctx, []uint64{3}, [32]byte{'c'}, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(ctx, []uint64{1, 2}, [32]byte{'b'}, 1)
|
||||
f.ProcessAttestation(ctx, []uint64{3}, [32]byte{'c'}, 1)
|
||||
f.justifiedBalances = []uint64{100, 200, 200, 300}
|
||||
head, err = f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -341,21 +341,21 @@ func TestForkChoice_RemoveEquivocating(t *testing.T) {
|
||||
|
||||
// Process b's slashing, c is now head
|
||||
f.InsertSlashedIndex(ctx, 1)
|
||||
require.Equal(t, uint64(200), f.store.fullNodeByRoot[[32]byte{'b'}].balance)
|
||||
require.Equal(t, uint64(200), f.store.emptyNodeByRoot[[32]byte{'b'}].balance)
|
||||
f.justifiedBalances = []uint64{100, 200, 200, 300}
|
||||
head, err = f.Head(ctx)
|
||||
require.Equal(t, uint64(200), f.store.fullNodeByRoot[[32]byte{'b'}].weight)
|
||||
require.Equal(t, uint64(300), f.store.fullNodeByRoot[[32]byte{'c'}].weight)
|
||||
require.Equal(t, uint64(200), f.store.emptyNodeByRoot[[32]byte{'b'}].weight)
|
||||
require.Equal(t, uint64(300), f.store.emptyNodeByRoot[[32]byte{'c'}].weight)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [32]byte{'c'}, head)
|
||||
|
||||
// Process b's slashing again, should be a noop
|
||||
f.InsertSlashedIndex(ctx, 1)
|
||||
require.Equal(t, uint64(200), f.store.fullNodeByRoot[[32]byte{'b'}].balance)
|
||||
require.Equal(t, uint64(200), f.store.emptyNodeByRoot[[32]byte{'b'}].balance)
|
||||
f.justifiedBalances = []uint64{100, 200, 200, 300}
|
||||
head, err = f.Head(ctx)
|
||||
require.Equal(t, uint64(200), f.store.fullNodeByRoot[[32]byte{'b'}].weight)
|
||||
require.Equal(t, uint64(300), f.store.fullNodeByRoot[[32]byte{'c'}].weight)
|
||||
require.Equal(t, uint64(200), f.store.emptyNodeByRoot[[32]byte{'b'}].weight)
|
||||
require.Equal(t, uint64(300), f.store.emptyNodeByRoot[[32]byte{'c'}].weight)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [32]byte{'c'}, head)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
@@ -288,50 +287,3 @@ func (s *Store) nodeTreeDump(ctx context.Context, n *Node, nodes []*forkchoice2.
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// InsertPayload inserts a full node into forkchoice after the Gloas fork.
|
||||
func (f *ForkChoice) InsertPayload(ctx context.Context, pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
s := f.store
|
||||
root := pe.BeaconBlockRoot()
|
||||
en := s.emptyNodeByRoot[root]
|
||||
if en == nil {
|
||||
return errors.Wrap(ErrNilNode, "cannot insert full node without an empty one")
|
||||
}
|
||||
if _, ok := s.fullNodeByRoot[root]; ok {
|
||||
// We don't import two payloads for the same root
|
||||
return nil
|
||||
}
|
||||
fn := &PayloadNode{
|
||||
node: en.node,
|
||||
optimistic: true,
|
||||
timestamp: time.Now(),
|
||||
full: true,
|
||||
children: make([]*Node, 0),
|
||||
}
|
||||
s.fullNodeByRoot[root] = fn
|
||||
f.updateNewFullNodeWeight(fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ForkChoice) updateNewFullNodeWeight(fn *PayloadNode) {
|
||||
for index, vote := range f.votes {
|
||||
if vote.currentRoot == fn.node.root && vote.nextPayloadStatus && index < len(f.balances) {
|
||||
fn.balance += f.balances[index]
|
||||
}
|
||||
}
|
||||
fn.weight = fn.balance
|
||||
}
|
||||
|
||||
func (s *Store) resolveVoteNode(r [32]byte, slot primitives.Slot, payloadStatus bool) (*PayloadNode, bool) {
|
||||
en := s.emptyNodeByRoot[r]
|
||||
if en == nil {
|
||||
return nil, true
|
||||
}
|
||||
if payloadStatus {
|
||||
return s.fullNodeByRoot[r], false
|
||||
} else if slot == en.node.slot {
|
||||
return en, true
|
||||
} else {
|
||||
return en, false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
package doublylinkedtree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
)
|
||||
|
||||
func prepareGloasForkchoiceState(
|
||||
_ context.Context,
|
||||
slot primitives.Slot,
|
||||
blockRoot [32]byte,
|
||||
parentRoot [32]byte,
|
||||
blockHash [32]byte,
|
||||
parentBlockHash [32]byte,
|
||||
justifiedEpoch primitives.Epoch,
|
||||
finalizedEpoch primitives.Epoch,
|
||||
) (state.BeaconState, blocks.ROBlock, error) {
|
||||
blockHeader := ðpb.BeaconBlockHeader{
|
||||
ParentRoot: parentRoot[:],
|
||||
}
|
||||
|
||||
justifiedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: justifiedEpoch,
|
||||
}
|
||||
|
||||
finalizedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: finalizedEpoch,
|
||||
}
|
||||
|
||||
builderPendingPayments := make([]*ethpb.BuilderPendingPayment, 64)
|
||||
for i := range builderPendingPayments {
|
||||
builderPendingPayments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
base := ðpb.BeaconStateGloas{
|
||||
Slot: slot,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
CurrentJustifiedCheckpoint: justifiedCheckpoint,
|
||||
FinalizedCheckpoint: finalizedCheckpoint,
|
||||
LatestBlockHeader: blockHeader,
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: [][]byte{make([]byte, 48)},
|
||||
},
|
||||
Builders: make([]*ethpb.Builder, 0),
|
||||
BuilderPendingPayments: builderPendingPayments,
|
||||
ExecutionPayloadAvailability: make([]byte, 1024),
|
||||
LatestBlockHash: make([]byte, 32),
|
||||
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
ProposerLookahead: make([]uint64, 64),
|
||||
}
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
roblock, err := blocks.NewROBlockWithRoot(signed, blockRoot)
|
||||
return st, roblock, err
|
||||
}
|
||||
|
||||
func prepareGloasForkchoicePayload(
|
||||
blockRoot [32]byte,
|
||||
) (interfaces.ROExecutionPayloadEnvelope, error) {
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{},
|
||||
}
|
||||
return blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
}
|
||||
|
||||
func TestInsertGloasBlock_EmptyNodeOnly(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
root := indexToHash(1)
|
||||
blockHash := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, root, params.BeaconConfig().ZeroHash, blockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
// Empty node should exist.
|
||||
en := f.store.emptyNodeByRoot[root]
|
||||
require.NotNil(t, en)
|
||||
|
||||
// Full node should NOT exist.
|
||||
_, hasFull := f.store.fullNodeByRoot[root]
|
||||
assert.Equal(t, false, hasFull)
|
||||
|
||||
// Parent should be the genesis full node.
|
||||
genesisRoot := params.BeaconConfig().ZeroHash
|
||||
genesisFull := f.store.fullNodeByRoot[genesisRoot]
|
||||
require.NotNil(t, genesisFull)
|
||||
assert.Equal(t, genesisFull, en.node.parent)
|
||||
}
|
||||
|
||||
func TestInsertPayload_CreatesFullNode(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
root := indexToHash(1)
|
||||
blockHash := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, root, params.BeaconConfig().ZeroHash, blockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
require.Equal(t, 2, len(f.store.emptyNodeByRoot))
|
||||
require.Equal(t, 1, len(f.store.fullNodeByRoot))
|
||||
|
||||
pe, err := prepareGloasForkchoicePayload(root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
require.Equal(t, 2, len(f.store.fullNodeByRoot))
|
||||
|
||||
fn := f.store.fullNodeByRoot[root]
|
||||
require.NotNil(t, fn)
|
||||
|
||||
en := f.store.emptyNodeByRoot[root]
|
||||
require.NotNil(t, en)
|
||||
|
||||
// Empty and full share the same *Node.
|
||||
assert.Equal(t, en.node, fn.node)
|
||||
assert.Equal(t, true, fn.optimistic)
|
||||
assert.Equal(t, true, fn.full)
|
||||
}
|
||||
|
||||
func TestInsertPayload_DuplicateIsNoop(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
root := indexToHash(1)
|
||||
blockHash := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, root, params.BeaconConfig().ZeroHash, blockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
pe, err := prepareGloasForkchoicePayload(root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
require.Equal(t, 2, len(f.store.fullNodeByRoot))
|
||||
|
||||
fn := f.store.fullNodeByRoot[root]
|
||||
require.NotNil(t, fn)
|
||||
|
||||
// Insert again — should be a no-op.
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
assert.Equal(t, fn, f.store.fullNodeByRoot[root])
|
||||
require.Equal(t, 2, len(f.store.fullNodeByRoot))
|
||||
}
|
||||
|
||||
func TestInsertPayload_WithoutEmptyNode_Errors(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
root := indexToHash(99)
|
||||
pe, err := prepareGloasForkchoicePayload(root)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = f.InsertPayload(ctx, pe)
|
||||
require.ErrorContains(t, ErrNilNode.Error(), err)
|
||||
}
|
||||
|
||||
func TestGloasBlock_ChildBuildsOnEmpty(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
// Insert Gloas block A (empty only).
|
||||
rootA := indexToHash(1)
|
||||
blockHashA := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, rootA, params.BeaconConfig().ZeroHash, blockHashA, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
// Insert Gloas block B as child of (A, empty)
|
||||
rootB := indexToHash(2)
|
||||
blockHashB := indexToHash(200)
|
||||
nonMatchingParentHash := indexToHash(999)
|
||||
st, roblock, err = prepareGloasForkchoiceState(ctx, 2, rootB, rootA, blockHashB, nonMatchingParentHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
emptyA := f.store.emptyNodeByRoot[rootA]
|
||||
require.NotNil(t, emptyA)
|
||||
nodeB := f.store.emptyNodeByRoot[rootB]
|
||||
require.NotNil(t, nodeB)
|
||||
require.Equal(t, emptyA, nodeB.node.parent)
|
||||
}
|
||||
|
||||
func TestGloasBlock_ChildrenOfEmptyAndFull(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
// Insert Gloas block A (empty only).
|
||||
rootA := indexToHash(1)
|
||||
blockHashA := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, rootA, params.BeaconConfig().ZeroHash, blockHashA, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
// Insert payload for A
|
||||
pe, err := prepareGloasForkchoicePayload(rootA)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
|
||||
// Insert Gloas block B as child of (A, empty)
|
||||
rootB := indexToHash(2)
|
||||
blockHashB := indexToHash(200)
|
||||
nonMatchingParentHash := indexToHash(999)
|
||||
st, roblock, err = prepareGloasForkchoiceState(ctx, 2, rootB, rootA, blockHashB, nonMatchingParentHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
// Insert Gloas block C as child of (A, full)
|
||||
rootC := indexToHash(3)
|
||||
blockHashC := indexToHash(201)
|
||||
st, roblock, err = prepareGloasForkchoiceState(ctx, 3, rootC, rootA, blockHashC, blockHashA, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
emptyA := f.store.emptyNodeByRoot[rootA]
|
||||
require.NotNil(t, emptyA)
|
||||
nodeB := f.store.emptyNodeByRoot[rootB]
|
||||
require.NotNil(t, nodeB)
|
||||
require.Equal(t, emptyA, nodeB.node.parent)
|
||||
nodeC := f.store.emptyNodeByRoot[rootC]
|
||||
require.NotNil(t, nodeC)
|
||||
fullA := f.store.fullNodeByRoot[rootA]
|
||||
require.NotNil(t, fullA)
|
||||
require.Equal(t, fullA, nodeC.node.parent)
|
||||
}
|
||||
|
||||
func TestGloasBlock_ChildBuildsOnFull(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
// Insert Gloas block A (empty only).
|
||||
rootA := indexToHash(1)
|
||||
blockHashA := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, rootA, params.BeaconConfig().ZeroHash, blockHashA, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
// Insert payload for A → creates the full node.
|
||||
pe, err := prepareGloasForkchoicePayload(rootA)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
|
||||
fullA := f.store.fullNodeByRoot[rootA]
|
||||
require.NotNil(t, fullA)
|
||||
|
||||
// Child for (A, full)
|
||||
rootB := indexToHash(2)
|
||||
blockHashB := indexToHash(200)
|
||||
st, roblock, err = prepareGloasForkchoiceState(ctx, 2, rootB, rootA, blockHashB, blockHashA, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
nodeB := f.store.emptyNodeByRoot[rootB]
|
||||
require.NotNil(t, nodeB)
|
||||
assert.Equal(t, fullA, nodeB.node.parent)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
f.ProcessAttestation(ctx, []uint64{0}, newRoot, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, []uint64{0}, newRoot, fEpoch)
|
||||
headRoot, err = f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 1")
|
||||
@@ -90,7 +90,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
f.ProcessAttestation(ctx, []uint64{1}, newRoot, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, []uint64{1}, newRoot, fEpoch)
|
||||
headRoot, err = f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 2")
|
||||
@@ -118,7 +118,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
f.ProcessAttestation(ctx, []uint64{2}, newRoot, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, []uint64{2}, newRoot, fEpoch)
|
||||
headRoot, err = f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3")
|
||||
@@ -147,7 +147,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
f.ProcessAttestation(ctx, []uint64{3}, newRoot, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, []uint64{3}, newRoot, fEpoch)
|
||||
headRoot, err = f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3")
|
||||
@@ -177,7 +177,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
|
||||
// Regression: process attestations for C, check that it
|
||||
// becomes head, we need two attestations to have C.weight = 30 > 24 = D.weight
|
||||
f.ProcessAttestation(ctx, []uint64{4, 5}, indexToHash(3), primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, []uint64{4, 5}, indexToHash(3), fEpoch)
|
||||
headRoot, err = f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, indexToHash(3), headRoot, "Incorrect head for justified epoch at slot 4")
|
||||
@@ -238,10 +238,10 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
|
||||
// The maliciously withheld block has one vote.
|
||||
votes := []uint64{1}
|
||||
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fEpoch)
|
||||
// The honest block has one vote.
|
||||
votes = []uint64{2}
|
||||
f.ProcessAttestation(ctx, votes, honestBlock, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, votes, honestBlock, fEpoch)
|
||||
|
||||
// Ensure the head is STILL C, the honest block, as the honest block had proposer boost.
|
||||
r, err = f.Head(ctx)
|
||||
@@ -307,7 +307,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
// An attestation is received for B that has more voting power than C with the proposer boost,
|
||||
// allowing B to then become the head if their attestation has enough adversarial votes.
|
||||
votes := []uint64{1, 2}
|
||||
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fEpoch)
|
||||
|
||||
// Expect the head to have switched to B.
|
||||
r, err = f.Head(ctx)
|
||||
@@ -382,7 +382,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
|
||||
// An attestation for C is received at slot N+3.
|
||||
votes := []uint64{1}
|
||||
f.ProcessAttestation(ctx, votes, c, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, votes, c, fEpoch)
|
||||
|
||||
// A block D, building on B, is received at slot N+3. It should not be able to win without boosting.
|
||||
dSlot := primitives.Slot(3)
|
||||
@@ -422,7 +422,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
votes = []uint64{2}
|
||||
f.ProcessAttestation(ctx, votes, d2, primitives.Slot(fEpoch), true)
|
||||
f.ProcessAttestation(ctx, votes, d2, fEpoch)
|
||||
// Ensure D becomes the head thanks to boosting.
|
||||
r, err = f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestForkChoice_ShouldOverrideFCU(t *testing.T) {
|
||||
for i := range attesters {
|
||||
attesters[i] = uint64(i + 64)
|
||||
}
|
||||
f.ProcessAttestation(ctx, attesters, blk.Root(), 0, true)
|
||||
f.ProcessAttestation(ctx, attesters, blk.Root(), 0)
|
||||
|
||||
orphanLateBlockFirstThreshold := time.Duration(params.BeaconConfig().SecondsPerSlot/params.BeaconConfig().IntervalsPerSlot) * time.Second
|
||||
driftGenesisTime(f, 2, orphanLateBlockFirstThreshold+time.Second)
|
||||
@@ -124,7 +124,7 @@ func TestForkChoice_GetProposerHead(t *testing.T) {
|
||||
for i := range attesters {
|
||||
attesters[i] = uint64(i + 64)
|
||||
}
|
||||
f.ProcessAttestation(ctx, attesters, blk.Root(), 0, true)
|
||||
f.ProcessAttestation(ctx, attesters, blk.Root(), 0)
|
||||
|
||||
driftGenesisTime(f, 3, 1*time.Second)
|
||||
childRoot := [32]byte{'b'}
|
||||
|
||||
@@ -136,7 +136,6 @@ func (s *Store) insert(ctx context.Context,
|
||||
node: n,
|
||||
optimistic: optimistic,
|
||||
timestamp: time.Now(),
|
||||
children: make([]*Node, 0),
|
||||
}
|
||||
s.emptyNodeByRoot[root] = pn
|
||||
ret = pn
|
||||
|
||||
@@ -78,10 +78,7 @@ type PayloadNode struct {
|
||||
|
||||
// Vote defines an individual validator's vote.
|
||||
type Vote struct {
|
||||
currentRoot [fieldparams.RootLength]byte // current voting root.
|
||||
nextRoot [fieldparams.RootLength]byte // next voting root.
|
||||
nextSlot primitives.Slot // slot of the next voting period.
|
||||
currentSlot primitives.Slot // slot of the current voting period.
|
||||
nextPayloadStatus bool // whether the next vote is for a full or empty payload
|
||||
currentPayloadStatus bool // whether the current vote is for a full or empty payload
|
||||
currentRoot [fieldparams.RootLength]byte // current voting root.
|
||||
nextRoot [fieldparams.RootLength]byte // next voting root.
|
||||
nextEpoch primitives.Epoch // epoch of next voting period.
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestStore_LongFork(t *testing.T) {
|
||||
require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'c'}, 2))
|
||||
|
||||
// Add an attestation to c, it is head
|
||||
f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'c'}, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'c'}, 1)
|
||||
f.justifiedBalances = []uint64{100}
|
||||
c := f.store.emptyNodeByRoot[[32]byte{'c'}]
|
||||
require.Equal(t, primitives.Epoch(2), slots.ToEpoch(c.node.slot))
|
||||
@@ -98,8 +98,8 @@ func TestStore_LongFork(t *testing.T) {
|
||||
headRoot, err = f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [32]byte{'c'}, headRoot)
|
||||
require.Equal(t, uint64(0), f.store.emptyNodeByRoot[[32]byte{'d'}].node.weight)
|
||||
require.Equal(t, uint64(100), f.store.emptyNodeByRoot[[32]byte{'c'}].node.weight)
|
||||
require.Equal(t, uint64(0), f.store.emptyNodeByRoot[[32]byte{'d'}].weight)
|
||||
require.Equal(t, uint64(100), f.store.emptyNodeByRoot[[32]byte{'c'}].weight)
|
||||
}
|
||||
|
||||
// Epoch 1 Epoch 2 Epoch 3
|
||||
@@ -153,7 +153,7 @@ func TestStore_NoDeadLock(t *testing.T) {
|
||||
require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'h'}, 2))
|
||||
require.NoError(t, f.store.setUnrealizedFinalizedEpoch([32]byte{'h'}, 1))
|
||||
// Add an attestation for h
|
||||
f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'h'}, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'h'}, 1)
|
||||
|
||||
// Epoch 3
|
||||
// Current Head is H
|
||||
@@ -225,7 +225,7 @@ func TestStore_ForkNextEpoch(t *testing.T) {
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
// Insert an attestation to H, H is head
|
||||
f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'h'}, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'h'}, 1)
|
||||
f.justifiedBalances = []uint64{100}
|
||||
headRoot, err := f.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -243,8 +243,8 @@ func TestStore_ForkNextEpoch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [32]byte{'d'}, headRoot)
|
||||
require.Equal(t, primitives.Epoch(2), f.JustifiedCheckpoint().Epoch)
|
||||
require.Equal(t, uint64(0), f.store.emptyNodeByRoot[[32]byte{'d'}].node.weight)
|
||||
require.Equal(t, uint64(100), f.store.emptyNodeByRoot[[32]byte{'h'}].node.weight)
|
||||
require.Equal(t, uint64(0), f.store.emptyNodeByRoot[[32]byte{'d'}].weight)
|
||||
require.Equal(t, uint64(100), f.store.emptyNodeByRoot[[32]byte{'h'}].weight)
|
||||
// Set current epoch to 3, and H's unrealized checkpoint. Check it's head
|
||||
driftGenesisTime(f, 99, 0)
|
||||
require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'h'}, 2))
|
||||
@@ -252,8 +252,8 @@ func TestStore_ForkNextEpoch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [32]byte{'h'}, headRoot)
|
||||
require.Equal(t, primitives.Epoch(2), f.JustifiedCheckpoint().Epoch)
|
||||
require.Equal(t, uint64(0), f.store.emptyNodeByRoot[[32]byte{'d'}].node.weight)
|
||||
require.Equal(t, uint64(100), f.store.emptyNodeByRoot[[32]byte{'h'}].node.weight)
|
||||
require.Equal(t, uint64(0), f.store.emptyNodeByRoot[[32]byte{'d'}].weight)
|
||||
require.Equal(t, uint64(100), f.store.emptyNodeByRoot[[32]byte{'h'}].weight)
|
||||
}
|
||||
|
||||
func TestStore_PullTips_Heuristics(t *testing.T) {
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestVotes_CanFindHead(t *testing.T) {
|
||||
// 0
|
||||
// / \
|
||||
// 2 1 <- +vote, new head
|
||||
f.ProcessAttestation(t.Context(), []uint64{0}, indexToHash(1), 2*params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{0}, indexToHash(1), 2)
|
||||
r, err = f.Head(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, indexToHash(1), r, "Incorrect head for with justified epoch at 1")
|
||||
@@ -55,7 +55,7 @@ func TestVotes_CanFindHead(t *testing.T) {
|
||||
// 0
|
||||
// / \
|
||||
// vote, new head -> 2 1
|
||||
f.ProcessAttestation(t.Context(), []uint64{1}, indexToHash(2), 2*params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{1}, indexToHash(2), 2)
|
||||
r, err = f.Head(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1")
|
||||
@@ -80,7 +80,7 @@ func TestVotes_CanFindHead(t *testing.T) {
|
||||
// head -> 2 1 <- old vote
|
||||
// |
|
||||
// 3 <- new vote
|
||||
f.ProcessAttestation(t.Context(), []uint64{0}, indexToHash(3), 3*params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{0}, indexToHash(3), 3)
|
||||
r, err = f.Head(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1")
|
||||
@@ -91,7 +91,7 @@ func TestVotes_CanFindHead(t *testing.T) {
|
||||
// old vote -> 2 1 <- new vote
|
||||
// |
|
||||
// 3 <- head
|
||||
f.ProcessAttestation(t.Context(), []uint64{1}, indexToHash(1), 3*params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{1}, indexToHash(1), 3)
|
||||
r, err = f.Head(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, indexToHash(3), r, "Incorrect head for with justified epoch at 1")
|
||||
@@ -150,7 +150,7 @@ func TestVotes_CanFindHead(t *testing.T) {
|
||||
assert.Equal(t, indexToHash(6), r, "Incorrect head for with justified epoch at 3")
|
||||
|
||||
// Moved 2 votes to block 5:
|
||||
f.ProcessAttestation(t.Context(), []uint64{0, 1}, indexToHash(5), 4*params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{0, 1}, indexToHash(5), 4)
|
||||
|
||||
// Inset blocks 7 and 8
|
||||
// 6 should still be the head, even though 5 has all the votes.
|
||||
@@ -227,7 +227,7 @@ func TestVotes_CanFindHead(t *testing.T) {
|
||||
|
||||
// Move two votes for 10, verify it's head
|
||||
|
||||
f.ProcessAttestation(t.Context(), []uint64{0, 1}, indexToHash(10), 5*params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{0, 1}, indexToHash(10), 5)
|
||||
r, err = f.Head(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, indexToHash(10), r, "Incorrect head for with justified epoch at 3")
|
||||
@@ -235,7 +235,7 @@ func TestVotes_CanFindHead(t *testing.T) {
|
||||
// Add 3 more validators to the system.
|
||||
f.justifiedBalances = []uint64{1, 1, 1, 1, 1}
|
||||
// The new validators voted for 9
|
||||
f.ProcessAttestation(t.Context(), []uint64{2, 3, 4}, indexToHash(9), 5*params.BeaconConfig().SlotsPerEpoch, true)
|
||||
f.ProcessAttestation(t.Context(), []uint64{2, 3, 4}, indexToHash(9), 5)
|
||||
// The new head should be 9.
|
||||
r, err = f.Head(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
forkchoice2 "github.com/OffchainLabs/prysm/v7/consensus-types/forkchoice"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
)
|
||||
|
||||
@@ -24,7 +23,6 @@ type ForkChoicer interface {
|
||||
Unlock()
|
||||
HeadRetriever // to compute head.
|
||||
BlockProcessor // to track new block for fork choice.
|
||||
PayloadProcessor // to track new payloads for fork choice.
|
||||
AttestationProcessor // to track new attestation for fork choice.
|
||||
Getter // to retrieve fork choice information.
|
||||
Setter // to set fork choice information.
|
||||
@@ -49,14 +47,9 @@ type BlockProcessor interface {
|
||||
InsertChain(context.Context, []*forkchoicetypes.BlockAndCheckpoints) error
|
||||
}
|
||||
|
||||
// PayloadProcessor processes a payload envelope
|
||||
type PayloadProcessor interface {
|
||||
InsertPayload(context.Context, interfaces.ROExecutionPayloadEnvelope) error
|
||||
}
|
||||
|
||||
// AttestationProcessor processes the attestation that's used for accounting fork choice.
|
||||
type AttestationProcessor interface {
|
||||
ProcessAttestation(context.Context, []uint64, [32]byte, primitives.Slot, bool)
|
||||
ProcessAttestation(context.Context, []uint64, [32]byte, primitives.Epoch)
|
||||
}
|
||||
|
||||
// Getter returns fork choice related information.
|
||||
|
||||
@@ -405,6 +405,7 @@ func (s *Service) nodeEndpoints() []endpoint {
|
||||
MetadataProvider: s.cfg.MetadataProvider,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
ExecutionChainInfoFetcher: s.cfg.ExecutionChainInfoFetcher,
|
||||
ExecutionEngineCaller: s.cfg.ExecutionEngineCaller,
|
||||
}
|
||||
|
||||
const namespace = "node"
|
||||
@@ -469,6 +470,16 @@ func (s *Service) nodeEndpoints() []endpoint {
|
||||
handler: server.GetVersion,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/eth/v2/node/version",
|
||||
name: namespace + ".GetVersionV2",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptEncodingHeaderHandler(),
|
||||
},
|
||||
handler: server.GetVersionV2,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/node/health",
|
||||
name: namespace + ".GetHealth",
|
||||
|
||||
@@ -86,6 +86,7 @@ func Test_endpoints(t *testing.T) {
|
||||
"/eth/v1/node/peers/{peer_id}": {http.MethodGet},
|
||||
"/eth/v1/node/peer_count": {http.MethodGet},
|
||||
"/eth/v1/node/version": {http.MethodGet},
|
||||
"/eth/v2/node/version": {http.MethodGet},
|
||||
"/eth/v1/node/syncing": {http.MethodGet},
|
||||
"/eth/v1/node/health": {http.MethodGet},
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
srcs = [
|
||||
"handlers.go",
|
||||
"handlers_peers.go",
|
||||
"log.go",
|
||||
"server.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/eth/node",
|
||||
@@ -30,6 +31,7 @@ go_library(
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -44,6 +46,7 @@ go_test(
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
"//beacon-chain/execution/testing:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/p2p/peers:go_default_library",
|
||||
"//beacon-chain/p2p/testing:go_default_library",
|
||||
|
||||
@@ -103,6 +103,8 @@ func (s *Server) GetIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// GetVersion requests that the beacon node identify information about its implementation in a
|
||||
// format similar to a HTTP User-Agent field.
|
||||
//
|
||||
// Deprecated: in favour of GetVersionV2.
|
||||
func (*Server) GetVersion(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "node.GetVersion")
|
||||
defer span.End()
|
||||
@@ -116,6 +118,38 @@ func (*Server) GetVersion(w http.ResponseWriter, r *http.Request) {
|
||||
httputil.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetVersionV2 Retrieves structured information about the version of the beacon node and its attached
|
||||
// execution client in the same format as used on the Engine API
|
||||
func (s *Server) GetVersionV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "node.GetVersionV2")
|
||||
defer span.End()
|
||||
|
||||
var elData *structs.ClientVersionV1
|
||||
elDataList, err := s.ExecutionEngineCaller.GetClientVersionV1(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("endpoint", "GetVersionV2").Debug("Could not get execution client version")
|
||||
} else if len(elDataList) > 0 {
|
||||
elData = elDataList[0]
|
||||
}
|
||||
|
||||
commit := version.GitCommit()
|
||||
if len(commit) >= 8 {
|
||||
commit = commit[:8]
|
||||
}
|
||||
resp := &structs.GetVersionV2Response{
|
||||
Data: &structs.VersionV2{
|
||||
BeaconNode: &structs.ClientVersionV1{
|
||||
Code: "PM",
|
||||
Name: "Prysm",
|
||||
Version: version.SemanticVersion(),
|
||||
Commit: commit,
|
||||
},
|
||||
ExecutionClient: elData,
|
||||
},
|
||||
}
|
||||
httputil.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetHealth returns node health status in http status codes. Useful for load balancers.
|
||||
func (s *Server) GetHealth(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "node.GetHealth")
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/OffchainLabs/go-bitfield"
|
||||
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
||||
mock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
|
||||
mockengine "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
|
||||
mockp2p "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/testutil"
|
||||
@@ -90,6 +91,75 @@ func TestGetVersion(t *testing.T) {
|
||||
assert.StringContains(t, arch, resp.Data.Version)
|
||||
}
|
||||
|
||||
func TestGetVersionV2(t *testing.T) {
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/node/version", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s := &Server{
|
||||
ExecutionEngineCaller: &mockengine.EngineClient{
|
||||
ClientVersion: []*structs.ClientVersionV1{{
|
||||
Code: "EL",
|
||||
Name: "ExecutionClient",
|
||||
Version: "v1.0.0",
|
||||
Commit: "abcdef12",
|
||||
}},
|
||||
},
|
||||
}
|
||||
s.GetVersionV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
resp := &structs.GetVersionV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
require.NotNil(t, resp.Data.BeaconNode)
|
||||
require.NotNil(t, resp.Data.ExecutionClient)
|
||||
require.Equal(t, "EL", resp.Data.ExecutionClient.Code)
|
||||
require.Equal(t, "ExecutionClient", resp.Data.ExecutionClient.Name)
|
||||
require.Equal(t, "v1.0.0", resp.Data.ExecutionClient.Version)
|
||||
require.Equal(t, "abcdef12", resp.Data.ExecutionClient.Commit)
|
||||
require.Equal(t, "PM", resp.Data.BeaconNode.Code)
|
||||
require.Equal(t, "Prysm", resp.Data.BeaconNode.Name)
|
||||
require.Equal(t, version.SemanticVersion(), resp.Data.BeaconNode.Version)
|
||||
require.Equal(t, true, len(resp.Data.BeaconNode.Commit) <= 8)
|
||||
})
|
||||
|
||||
t.Run("unhappy path", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/node/version", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s := &Server{
|
||||
ExecutionEngineCaller: &mockengine.EngineClient{
|
||||
ClientVersion: nil,
|
||||
ErrorClientVersion: fmt.Errorf("error"),
|
||||
},
|
||||
}
|
||||
s.GetVersionV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
resp := &structs.GetVersionV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
require.NotNil(t, resp.Data.BeaconNode)
|
||||
require.Equal(t, true, resp.Data.ExecutionClient == nil)
|
||||
|
||||
// make sure there is no 'execution_client' field
|
||||
var payload map[string]any
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &payload))
|
||||
data, ok := payload["data"].(map[string]any)
|
||||
require.Equal(t, true, ok)
|
||||
_, found := data["beacon_node"]
|
||||
require.Equal(t, true, found)
|
||||
_, found = data["execution_client"]
|
||||
require.Equal(t, false, found)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestGetHealth(t *testing.T) {
|
||||
checker := &syncmock.Sync{}
|
||||
optimisticFetcher := &mock.ChainService{Optimistic: false}
|
||||
|
||||
9
beacon-chain/rpc/eth/node/log.go
Normal file
9
beacon-chain/rpc/eth/node/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Code generated by hack/gen-logs.sh; DO NOT EDIT.
|
||||
// This file is created and regenerated automatically. Anything added here might get removed.
|
||||
package node
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
// The prefix for logs from this package will be the text after the last slash in the package path.
|
||||
// If you wish to change this, you should add your desired name in the runtime/logging/logrus-prefixed-formatter/prefix-replacement.go file.
|
||||
var log = logrus.WithField("package", "beacon-chain/rpc/eth/node")
|
||||
@@ -26,4 +26,5 @@ type Server struct {
|
||||
GenesisTimeFetcher blockchain.TimeFetcher
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
ExecutionChainInfoFetcher execution.ChainInfoFetcher
|
||||
ExecutionEngineCaller execution.EngineCaller
|
||||
}
|
||||
|
||||
@@ -152,6 +152,7 @@ type ReadOnlyBalances interface {
|
||||
Balances() []uint64
|
||||
BalanceAtIndex(idx primitives.ValidatorIndex) (uint64, error)
|
||||
BalancesLength() int
|
||||
EffectiveBalanceSum([]primitives.ValidatorIndex) (uint64, error)
|
||||
}
|
||||
|
||||
// ReadOnlyCheckpoint defines a struct which only has read access to checkpoint methods.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Validators participating in consensus on the beacon chain.
|
||||
@@ -80,6 +81,25 @@ func (b *BeaconState) ValidatorAtIndex(idx primitives.ValidatorIndex) (*ethpb.Va
|
||||
return b.validatorAtIndex(idx)
|
||||
}
|
||||
|
||||
// EffectiveBalances returns the sum of the effective balances of the given list of validator indices, the eb of each given validator, or an
|
||||
// error if one of the indices is out of bounds, or the state wasn't correctly initialized.
|
||||
func (b *BeaconState) EffectiveBalanceSum(idxs []primitives.ValidatorIndex) (uint64, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
var sum uint64
|
||||
for i := range idxs {
|
||||
if b.validatorsMultiValue == nil {
|
||||
return 0, errors.Wrap(state.ErrNilValidatorsInState, "nil validators multi-value slice")
|
||||
}
|
||||
v, err := b.validatorsMultiValue.At(b, uint64(idxs[i]))
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "validators multi value at index")
|
||||
}
|
||||
sum += v.EffectiveBalance
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func (b *BeaconState) validatorAtIndex(idx primitives.ValidatorIndex) (*ethpb.Validator, error) {
|
||||
if b.validatorsMultiValue == nil {
|
||||
return ðpb.Validator{}, nil
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/stategen"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
)
|
||||
|
||||
@@ -14,6 +15,8 @@ type StateManager struct {
|
||||
StatesBySlot map[primitives.Slot]state.BeaconState
|
||||
}
|
||||
|
||||
var _ stategen.StateManager = (*StateManager)(nil)
|
||||
|
||||
// NewService --
|
||||
func NewService() *StateManager {
|
||||
return &StateManager{
|
||||
@@ -101,3 +104,8 @@ func (m *StateManager) AddStateForSlot(state state.BeaconState, slot primitives.
|
||||
func (m *StateManager) DeleteStateFromCaches(context.Context, [32]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FinalizedReadOnlyBalances --
|
||||
func (m *StateManager) FinalizedReadOnlyBalances() stategen.NilCheckableReadOnlyBalances {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ var defaultHotStateDBInterval primitives.Slot = 128
|
||||
|
||||
var populatePubkeyCacheOnce sync.Once
|
||||
|
||||
// NilCheckableReadOnlyBalances adds the IsNil method to ReadOnlyBalances
|
||||
// to allow checking if the underlying state value is nil.
|
||||
type NilCheckableReadOnlyBalances interface {
|
||||
state.ReadOnlyBalances
|
||||
IsNil() bool
|
||||
}
|
||||
|
||||
// StateManager represents a management object that handles the internal
|
||||
// logic of maintaining both hot and cold states in DB.
|
||||
type StateManager interface {
|
||||
@@ -43,6 +50,7 @@ type StateManager interface {
|
||||
ActiveNonSlashedBalancesByRoot(context.Context, [32]byte) ([]uint64, error)
|
||||
StateByRootIfCachedNoCopy(blockRoot [32]byte) state.BeaconState
|
||||
StateByRootInitialSync(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error)
|
||||
FinalizedReadOnlyBalances() NilCheckableReadOnlyBalances
|
||||
}
|
||||
|
||||
// State is a concrete implementation of StateManager.
|
||||
@@ -201,3 +209,8 @@ func (s *State) FinalizedState() state.BeaconState {
|
||||
defer s.finalizedInfo.lock.RUnlock()
|
||||
return s.finalizedInfo.state.Copy()
|
||||
}
|
||||
|
||||
// Returns the finalized state as a ReadOnlyBalances so that it can be used read-only without copying.
|
||||
func (s *State) FinalizedReadOnlyBalances() NilCheckableReadOnlyBalances {
|
||||
return s.finalizedInfo.state
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ func (s *Service) validatorsCustodyRequirement() (uint64, error) {
|
||||
}
|
||||
|
||||
// Retrieve the finalized state.
|
||||
finalizedState := s.cfg.stateGen.FinalizedState()
|
||||
finalizedState := s.cfg.stateGen.FinalizedReadOnlyBalances()
|
||||
if finalizedState == nil || finalizedState.IsNil() {
|
||||
return 0, nilFinalizedStateError
|
||||
}
|
||||
|
||||
@@ -268,10 +268,23 @@ func (s *Service) validateCommitteeIndexAndCount(
|
||||
a eth.Att,
|
||||
bs state.ReadOnlyBeaconState,
|
||||
) (primitives.CommitteeIndex, uint64, pubsub.ValidationResult, error) {
|
||||
// - [REJECT] attestation.data.index == 0
|
||||
if a.Version() >= version.Electra && a.GetData().CommitteeIndex != 0 {
|
||||
return 0, 0, pubsub.ValidationReject, errors.New("attestation data's committee index must be 0")
|
||||
// Validate committee index based on fork.
|
||||
if a.Version() >= version.Electra {
|
||||
data := a.GetData()
|
||||
attEpoch := slots.ToEpoch(data.Slot)
|
||||
postGloas := attEpoch >= params.BeaconConfig().GloasForkEpoch
|
||||
if postGloas {
|
||||
if result, err := s.validateGloasCommitteeIndex(data); result != pubsub.ValidationAccept {
|
||||
return 0, 0, result, err
|
||||
}
|
||||
} else {
|
||||
// [REJECT] attestation.data.index == 0 (New in Electra, removed in Gloas)
|
||||
if data.CommitteeIndex != 0 {
|
||||
return 0, 0, pubsub.ValidationReject, errors.New("attestation data's committee index must be 0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
valCount, err := helpers.ActiveValidatorCount(ctx, bs, slots.ToEpoch(a.GetData().Slot))
|
||||
if err != nil {
|
||||
return 0, 0, pubsub.ValidationIgnore, err
|
||||
@@ -356,6 +369,29 @@ func validateAttestingIndex(
|
||||
return pubsub.ValidationAccept, nil
|
||||
}
|
||||
|
||||
// validateGloasCommitteeIndex validates committee index rules for Gloas fork.
|
||||
// [REJECT] attestation.data.index < 2. (New in Gloas)
|
||||
// [REJECT] attestation.data.index == 0 if block.slot == attestation.data.slot. (New in Gloas)
|
||||
func (s *Service) validateGloasCommitteeIndex(data *eth.AttestationData) (pubsub.ValidationResult, error) {
|
||||
if data.CommitteeIndex >= 2 {
|
||||
return pubsub.ValidationReject, errors.New("attestation data's committee index must be < 2")
|
||||
}
|
||||
|
||||
// Same-slot attestations must use committee index 0
|
||||
if data.CommitteeIndex != 0 {
|
||||
blockRoot := bytesutil.ToBytes32(data.BeaconBlockRoot)
|
||||
slot, err := s.cfg.chain.RecentBlockSlot(blockRoot)
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
if slot == data.Slot {
|
||||
return pubsub.ValidationReject, errors.New("same slot attestations must use committee index 0")
|
||||
}
|
||||
}
|
||||
|
||||
return pubsub.ValidationAccept, nil
|
||||
}
|
||||
|
||||
// generateUnaggregatedAttCacheKey generates the cache key for unaggregated attestation tracking.
|
||||
func generateUnaggregatedAttCacheKey(att eth.Att) (string, error) {
|
||||
var attester uint64
|
||||
|
||||
@@ -684,3 +684,75 @@ func Test_validateCommitteeIndexAndCount_Boundary(t *testing.T) {
|
||||
require.ErrorContains(t, "committee index", err)
|
||||
require.Equal(t, pubsub.ValidationReject, res)
|
||||
}
|
||||
|
||||
func Test_validateGloasCommitteeIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
committeeIndex primitives.CommitteeIndex
|
||||
attestationSlot primitives.Slot
|
||||
blockSlot primitives.Slot
|
||||
wantResult pubsub.ValidationResult
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "committee index >= 2 should reject",
|
||||
committeeIndex: 2,
|
||||
attestationSlot: 10,
|
||||
blockSlot: 10,
|
||||
wantResult: pubsub.ValidationReject,
|
||||
wantErr: "committee index must be < 2",
|
||||
},
|
||||
{
|
||||
name: "committee index 0 should accept",
|
||||
committeeIndex: 0,
|
||||
attestationSlot: 10,
|
||||
blockSlot: 10,
|
||||
wantResult: pubsub.ValidationAccept,
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "committee index 1 different-slot should accept",
|
||||
committeeIndex: 1,
|
||||
attestationSlot: 10,
|
||||
blockSlot: 9,
|
||||
wantResult: pubsub.ValidationAccept,
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "committee index 1 same-slot should reject",
|
||||
committeeIndex: 1,
|
||||
attestationSlot: 10,
|
||||
blockSlot: 10,
|
||||
wantResult: pubsub.ValidationReject,
|
||||
wantErr: "same slot attestations must use committee index 0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockChain := &mockChain.ChainService{
|
||||
BlockSlot: tt.blockSlot,
|
||||
}
|
||||
s := &Service{
|
||||
cfg: &config{
|
||||
chain: mockChain,
|
||||
},
|
||||
}
|
||||
|
||||
data := ðpb.AttestationData{
|
||||
Slot: tt.attestationSlot,
|
||||
CommitteeIndex: tt.committeeIndex,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot"), 32),
|
||||
}
|
||||
|
||||
result, err := s.validateGloasCommitteeIndex(data)
|
||||
|
||||
require.Equal(t, tt.wantResult, result)
|
||||
if tt.wantErr != "" {
|
||||
require.ErrorContains(t, tt.wantErr, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
3
changelog/bastin_get-version-v2.md
Normal file
3
changelog/bastin_get-version-v2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- New beacon API endpoint `eth/v2/node/version`.
|
||||
2
changelog/kasey_copy-free-balance-lookup.md
Normal file
2
changelog/kasey_copy-free-balance-lookup.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Fixed
|
||||
- Avoid copying the full finalized state every time we compute cgc.
|
||||
@@ -1,2 +0,0 @@
|
||||
### Added
|
||||
- Process Gloas attestations in forkchoice.
|
||||
@@ -1,2 +0,0 @@
|
||||
### Added
|
||||
- Added an InsertPayload method to allow full node insertion after gloas.
|
||||
2
changelog/potuz_gloas_post_block_process.md
Normal file
2
changelog/potuz_gloas_post_block_process.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Changed
|
||||
- Changed fcuArgs parameters passing and only call FCU pre-Gloas
|
||||
3
changelog/terence_gloas-attestation-validation.md
Normal file
3
changelog/terence_gloas-attestation-validation.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add gossip beacon attestation validation conditions for Gloas fork
|
||||
2
changelog/tt_apply_execution_payload.md
Normal file
2
changelog/tt_apply_execution_payload.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Ignored
|
||||
- Refactor ProcessExecutionPayload to ApplyExecutionPayload
|
||||
@@ -34,8 +34,17 @@ func SemanticVersion() string {
|
||||
return gitTag
|
||||
}
|
||||
|
||||
// GitCommit returns the current build commit hash.
|
||||
func GitCommit() string {
|
||||
return resolvedGitCommit()
|
||||
}
|
||||
|
||||
// BuildData returns the git tag and commit of the current build.
|
||||
func BuildData() string {
|
||||
return fmt.Sprintf("Prysm/%s/%s", gitTag, resolvedGitCommit())
|
||||
}
|
||||
|
||||
func resolvedGitCommit() string {
|
||||
// if doing a local build, these values are not interpolated
|
||||
if gitCommit == "{STABLE_GIT_COMMIT}" {
|
||||
commit, err := exec.Command("git", "rev-parse", "HEAD").Output()
|
||||
@@ -45,7 +54,7 @@ func BuildData() string {
|
||||
gitCommit = strings.TrimRight(string(commit), "\r\n")
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Prysm/%s/%s", gitTag, gitCommit)
|
||||
return gitCommit
|
||||
}
|
||||
|
||||
// GetCommitPrefix returns the first 4 characters of the git commit.
|
||||
|
||||
@@ -12,6 +12,7 @@ go_library(
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/testing/spectest/shared/common/forkchoice",
|
||||
visibility = ["//testing/spectest:__subpackages__"],
|
||||
deps = [
|
||||
"//api/server/structs:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/blockchain/kzg:go_default_library",
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package forkchoice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -141,8 +140,7 @@ func (bb *Builder) Check(t testing.TB, c *Check) {
|
||||
if c.Head != nil {
|
||||
r, err := bb.service.HeadRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
wantedRoot := common.FromHex(c.Head.Root)
|
||||
require.Equal(t, true, bytes.Equal(wantedRoot, r), fmt.Sprintf("Roots differ. wanted %#x, got %#x", wantedRoot, r))
|
||||
require.DeepEqual(t, common.FromHex(c.Head.Root), r)
|
||||
require.Equal(t, primitives.Slot(c.Head.Slot), bb.service.HeadSlot())
|
||||
}
|
||||
if c.JustifiedCheckPoint != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
|
||||
mock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
|
||||
@@ -141,3 +142,7 @@ func (m *engineMock) ExecutionBlockByHash(_ context.Context, hash common.Hash, _
|
||||
func (m *engineMock) GetTerminalBlockHash(context.Context, uint64) ([]byte, bool, error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (m *engineMock) GetClientVersionV1(context.Context) ([]*structs.ClientVersionV1, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user