mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-04 18:15:12 -05:00
Compare commits
3 Commits
gloas-ptc-
...
ptc-duty-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f73073cfdf | ||
|
|
0076fc78bd | ||
|
|
af2a6c29a6 |
@@ -540,12 +540,6 @@ type PayloadAttestation struct {
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type PayloadAttestationMessage struct {
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
Data *PayloadAttestationData `json:"data"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type BeaconBlockBodyGloas struct {
|
||||
RandaoReveal string `json:"randao_reveal"`
|
||||
Eth1Data *Eth1Data `json:"eth1_data"`
|
||||
|
||||
@@ -3263,34 +3263,3 @@ func (d *PayloadAttestationData) ToConsensus() (*eth.PayloadAttestationData, err
|
||||
BlobDataAvailable: d.BlobDataAvailable,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func PayloadAttestationMessageFromConsensus(msg *eth.PayloadAttestationMessage) *PayloadAttestationMessage {
|
||||
return &PayloadAttestationMessage{
|
||||
ValidatorIndex: fmt.Sprintf("%d", msg.ValidatorIndex),
|
||||
Data: PayloadAttestationDataFromConsensus(msg.Data),
|
||||
Signature: hexutil.Encode(msg.Signature),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PayloadAttestationMessage) ToConsensus() (*eth.PayloadAttestationMessage, error) {
|
||||
if p == nil {
|
||||
return nil, errNilValue
|
||||
}
|
||||
validatorIndex, err := strconv.ParseUint(p.ValidatorIndex, 10, 64)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "ValidatorIndex")
|
||||
}
|
||||
data, err := p.Data.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Data")
|
||||
}
|
||||
sig, err := bytesutil.DecodeHexWithLength(p.Signature, fieldparams.BLSSignatureLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Signature")
|
||||
}
|
||||
return ð.PayloadAttestationMessage{
|
||||
ValidatorIndex: primitives.ValidatorIndex(validatorIndex),
|
||||
Data: data,
|
||||
Signature: sig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -188,11 +188,6 @@ type BLSToExecutionChangesPoolResponse struct {
|
||||
Data []*SignedBLSToExecutionChange `json:"data"`
|
||||
}
|
||||
|
||||
type GetPoolPayloadAttestationsResponse struct {
|
||||
Version string `json:"version"`
|
||||
Data []*PayloadAttestation `json:"data"`
|
||||
}
|
||||
|
||||
type GetAttesterSlashingsResponse struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
Data json.RawMessage `json:"data"` // Accepts both `[]*AttesterSlashing` and `[]*AttesterSlashingElectra` types
|
||||
|
||||
@@ -31,11 +31,6 @@ type GetAttestationDataResponse struct {
|
||||
Data *AttestationData `json:"data"`
|
||||
}
|
||||
|
||||
type GetPayloadAttestationDataResponse struct {
|
||||
Version string `json:"version"`
|
||||
Data *PayloadAttestationData `json:"data"`
|
||||
}
|
||||
|
||||
type ProduceSyncCommitteeContributionResponse struct {
|
||||
Data *SyncCommitteeContribution `json:"data"`
|
||||
}
|
||||
@@ -79,6 +74,18 @@ type SyncCommitteeDuty struct {
|
||||
ValidatorSyncCommitteeIndices []string `json:"validator_sync_committee_indices"`
|
||||
}
|
||||
|
||||
type GetPTCDutiesResponse struct {
|
||||
DependentRoot string `json:"dependent_root"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Data []*PTCDuty `json:"data"`
|
||||
}
|
||||
|
||||
type PTCDuty struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
Slot string `json:"slot"`
|
||||
}
|
||||
|
||||
// ProduceBlockV3Response is a wrapper json object for the returned block from the ProduceBlockV3 endpoint
|
||||
type ProduceBlockV3Response struct {
|
||||
Version string `json:"version"`
|
||||
|
||||
@@ -70,7 +70,7 @@ func ProcessPayloadAttestations(ctx context.Context, st state.BeaconState, body
|
||||
|
||||
// indexedPayloadAttestation converts a payload attestation into its indexed form.
|
||||
func indexedPayloadAttestation(ctx context.Context, st state.ReadOnlyBeaconState, att *eth.PayloadAttestation) (*consensus_types.IndexedPayloadAttestation, error) {
|
||||
committee, err := payloadCommittee(ctx, st, att.Data.Slot)
|
||||
committee, err := PayloadCommittee(ctx, st, att.Data.Slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func indexedPayloadAttestation(ctx context.Context, st state.ReadOnlyBeaconState
|
||||
}, nil
|
||||
}
|
||||
|
||||
// payloadCommittee returns the payload timeliness committee for a given slot for the state.
|
||||
// PayloadCommittee returns the payload timeliness committee for a given slot for the state.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:
|
||||
//
|
||||
@@ -101,7 +101,7 @@ func indexedPayloadAttestation(ctx context.Context, st state.ReadOnlyBeaconState
|
||||
// committee = get_beacon_committee(state, slot, CommitteeIndex(i))
|
||||
// indices.extend(committee)
|
||||
// return compute_balance_weighted_selection(state, indices, seed, size=PTC_SIZE, shuffle_indices=False)
|
||||
func payloadCommittee(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot) ([]primitives.ValidatorIndex, error) {
|
||||
func PayloadCommittee(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot) ([]primitives.ValidatorIndex, error) {
|
||||
epoch := slots.ToEpoch(slot)
|
||||
seed, err := ptcSeed(st, epoch, slot)
|
||||
if err != nil {
|
||||
|
||||
@@ -37,7 +37,6 @@ go_library(
|
||||
"//beacon-chain/node/registration:go_default_library",
|
||||
"//beacon-chain/operations/attestations:go_default_library",
|
||||
"//beacon-chain/operations/blstoexec:go_default_library",
|
||||
"//beacon-chain/operations/payloadattestation:go_default_library",
|
||||
"//beacon-chain/operations/slashings:go_default_library",
|
||||
"//beacon-chain/operations/synccommittee:go_default_library",
|
||||
"//beacon-chain/operations/voluntaryexits:go_default_library",
|
||||
|
||||
@@ -40,7 +40,6 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/node/registration"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/attestations"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/blstoexec"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/payloadattestation"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/slashings"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/synccommittee"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/voluntaryexits"
|
||||
@@ -102,7 +101,6 @@ type BeaconNode struct {
|
||||
slashingsPool slashings.PoolManager
|
||||
syncCommitteePool synccommittee.Pool
|
||||
blsToExecPool blstoexec.PoolManager
|
||||
payloadAttestationPool payloadattestation.PoolManager
|
||||
depositCache cache.DepositCache
|
||||
trackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
payloadIDCache *cache.PayloadIDCache
|
||||
@@ -143,21 +141,20 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco
|
||||
ctx := cliCtx.Context
|
||||
|
||||
beacon := &BeaconNode{
|
||||
cliCtx: cliCtx,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
services: runtime.NewServiceRegistry(),
|
||||
stop: make(chan struct{}),
|
||||
stateFeed: new(event.Feed),
|
||||
blockFeed: new(event.Feed),
|
||||
opFeed: new(event.Feed),
|
||||
attestationCache: cache.NewAttestationCache(),
|
||||
attestationPool: attestations.NewPool(),
|
||||
exitPool: voluntaryexits.NewPool(),
|
||||
slashingsPool: slashings.NewPool(),
|
||||
syncCommitteePool: synccommittee.NewPool(),
|
||||
blsToExecPool: blstoexec.NewPool(),
|
||||
// TODO payloadAttestationPool: set once a PoolManager implementation exists.
|
||||
cliCtx: cliCtx,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
services: runtime.NewServiceRegistry(),
|
||||
stop: make(chan struct{}),
|
||||
stateFeed: new(event.Feed),
|
||||
blockFeed: new(event.Feed),
|
||||
opFeed: new(event.Feed),
|
||||
attestationCache: cache.NewAttestationCache(),
|
||||
attestationPool: attestations.NewPool(),
|
||||
exitPool: voluntaryexits.NewPool(),
|
||||
slashingsPool: slashings.NewPool(),
|
||||
syncCommitteePool: synccommittee.NewPool(),
|
||||
blsToExecPool: blstoexec.NewPool(),
|
||||
trackedValidatorsCache: cache.NewTrackedValidatorsCache(),
|
||||
payloadIDCache: cache.NewPayloadIDCache(),
|
||||
slasherBlockHeadersFeed: new(event.Feed),
|
||||
@@ -975,7 +972,6 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error {
|
||||
SlashingsPool: b.slashingsPool,
|
||||
BLSChangesPool: b.blsToExecPool,
|
||||
SyncCommitteeObjectPool: b.syncCommitteePool,
|
||||
PayloadAttestationPool: b.payloadAttestationPool,
|
||||
ExecutionChainService: web3Service,
|
||||
ExecutionChainInfoFetcher: web3Service,
|
||||
ChainStartFetcher: chainStartFetcher,
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["pool.go"],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/operations/payloadattestation",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["mock.go"],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/operations/payloadattestation/mock",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//proto/prysm/v1alpha1:go_default_library"],
|
||||
)
|
||||
@@ -1,28 +0,0 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// PoolMock is a fake implementation of PoolManager.
|
||||
type PoolMock struct {
|
||||
Attestations []*ethpb.PayloadAttestation
|
||||
}
|
||||
|
||||
// PendingPayloadAttestations --
|
||||
func (m *PoolMock) PendingPayloadAttestations() []*ethpb.PayloadAttestation {
|
||||
return m.Attestations
|
||||
}
|
||||
|
||||
// InsertPayloadAttestation --
|
||||
func (m *PoolMock) InsertPayloadAttestation(msg *ethpb.PayloadAttestationMessage) error {
|
||||
m.Attestations = append(m.Attestations, ðpb.PayloadAttestation{
|
||||
Data: msg.Data,
|
||||
Signature: msg.Signature,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkIncluded --
|
||||
func (*PoolMock) MarkIncluded(_ *ethpb.PayloadAttestation) {
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package payloadattestation
|
||||
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// PoolManager maintains pending payload attestations.
|
||||
// This pool is used by proposers to insert payload attestations into new blocks.
|
||||
type PoolManager interface {
|
||||
PendingPayloadAttestations() []*ethpb.PayloadAttestation
|
||||
InsertPayloadAttestation(msg *ethpb.PayloadAttestationMessage) error
|
||||
MarkIncluded(att *ethpb.PayloadAttestation)
|
||||
}
|
||||
|
||||
// PayloadStatusFetcher determines the payload presence and blob data availability
|
||||
// for a given slot. This is used by PTC validators to produce PayloadAttestationData.
|
||||
type PayloadStatusFetcher interface {
|
||||
PayloadStatus(slot primitives.Slot) (payloadPresent bool, blobDataAvailable bool, err error)
|
||||
}
|
||||
@@ -26,7 +26,6 @@ go_library(
|
||||
"//beacon-chain/light-client:go_default_library",
|
||||
"//beacon-chain/operations/attestations:go_default_library",
|
||||
"//beacon-chain/operations/blstoexec:go_default_library",
|
||||
"//beacon-chain/operations/payloadattestation:go_default_library",
|
||||
"//beacon-chain/operations/slashings:go_default_library",
|
||||
"//beacon-chain/operations/synccommittee:go_default_library",
|
||||
"//beacon-chain/operations/voluntaryexits:go_default_library",
|
||||
|
||||
@@ -217,7 +217,6 @@ func (s *Service) validatorEndpoints(
|
||||
OperationNotifier: s.cfg.OperationNotifier,
|
||||
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
|
||||
PayloadIDCache: s.cfg.PayloadIDCache,
|
||||
PayloadAttestationPool: s.cfg.PayloadAttestationPool,
|
||||
CoreService: coreService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
@@ -341,6 +340,17 @@ func (s *Service) validatorEndpoints(
|
||||
handler: server.GetSyncCommitteeDuties,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/validator/duties/ptc/{epoch}",
|
||||
name: namespace + ".GetPTCDuties",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptEncodingHeaderHandler(),
|
||||
},
|
||||
handler: server.GetPTCDuties,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/validator/prepare_beacon_proposer",
|
||||
name: namespace + ".PrepareBeaconProposer",
|
||||
@@ -391,16 +401,6 @@ func (s *Service) validatorEndpoints(
|
||||
handler: server.SyncCommitteeSelections,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/validator/payload_attestation_data/{slot}",
|
||||
name: namespace + ".GetPayloadAttestationData",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
|
||||
middleware.AcceptEncodingHeaderHandler(),
|
||||
},
|
||||
handler: server.GetPayloadAttestationData,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,7 +523,6 @@ func (s *Service) beaconEndpoints(
|
||||
SyncChecker: s.cfg.SyncService,
|
||||
ExecutionReconstructor: s.cfg.ExecutionReconstructor,
|
||||
BLSChangesPool: s.cfg.BLSChangesPool,
|
||||
PayloadAttestationPool: s.cfg.PayloadAttestationPool,
|
||||
FinalizationFetcher: s.cfg.FinalizationFetcher,
|
||||
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
|
||||
CoreService: coreService,
|
||||
@@ -881,27 +880,6 @@ func (s *Service) beaconEndpoints(
|
||||
handler: server.GetProposerLookahead,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/beacon/pool/payload_attestations",
|
||||
name: namespace + ".ListPayloadAttestations",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptEncodingHeaderHandler(),
|
||||
},
|
||||
handler: server.ListPayloadAttestations,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/beacon/pool/payload_attestations",
|
||||
name: namespace + ".SubmitPayloadAttestations",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptEncodingHeaderHandler(),
|
||||
},
|
||||
handler: server.SubmitPayloadAttestations,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ 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},
|
||||
"/eth/v1/beacon/pool/payload_attestations": {http.MethodGet, http.MethodPost},
|
||||
"/prysm/v1/beacon/individual_votes": {http.MethodPost},
|
||||
}
|
||||
|
||||
@@ -92,23 +91,22 @@ func Test_endpoints(t *testing.T) {
|
||||
}
|
||||
|
||||
validatorRoutes := map[string][]string{
|
||||
"/eth/v1/validator/duties/attester/{epoch}": {http.MethodPost},
|
||||
"/eth/v1/validator/duties/proposer/{epoch}": {http.MethodGet},
|
||||
"/eth/v1/validator/duties/sync/{epoch}": {http.MethodPost},
|
||||
"/eth/v3/validator/blocks/{slot}": {http.MethodGet},
|
||||
"/eth/v1/validator/attestation_data": {http.MethodGet},
|
||||
"/eth/v2/validator/aggregate_attestation": {http.MethodGet},
|
||||
"/eth/v2/validator/aggregate_and_proofs": {http.MethodPost},
|
||||
"/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost},
|
||||
"/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost},
|
||||
"/eth/v1/validator/beacon_committee_selections": {http.MethodPost},
|
||||
"/eth/v1/validator/sync_committee_selections": {http.MethodPost},
|
||||
"/eth/v1/validator/sync_committee_contribution": {http.MethodGet},
|
||||
"/eth/v1/validator/contribution_and_proofs": {http.MethodPost},
|
||||
"/eth/v1/validator/prepare_beacon_proposer": {http.MethodPost},
|
||||
"/eth/v1/validator/register_validator": {http.MethodPost},
|
||||
"/eth/v1/validator/liveness/{epoch}": {http.MethodPost},
|
||||
"/eth/v1/validator/payload_attestation_data/{slot}": {http.MethodGet},
|
||||
"/eth/v1/validator/duties/attester/{epoch}": {http.MethodPost},
|
||||
"/eth/v1/validator/duties/proposer/{epoch}": {http.MethodGet},
|
||||
"/eth/v1/validator/duties/sync/{epoch}": {http.MethodPost},
|
||||
"/eth/v3/validator/blocks/{slot}": {http.MethodGet},
|
||||
"/eth/v1/validator/attestation_data": {http.MethodGet},
|
||||
"/eth/v2/validator/aggregate_attestation": {http.MethodGet},
|
||||
"/eth/v2/validator/aggregate_and_proofs": {http.MethodPost},
|
||||
"/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost},
|
||||
"/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost},
|
||||
"/eth/v1/validator/beacon_committee_selections": {http.MethodPost},
|
||||
"/eth/v1/validator/sync_committee_selections": {http.MethodPost},
|
||||
"/eth/v1/validator/sync_committee_contribution": {http.MethodGet},
|
||||
"/eth/v1/validator/contribution_and_proofs": {http.MethodPost},
|
||||
"/eth/v1/validator/prepare_beacon_proposer": {http.MethodPost},
|
||||
"/eth/v1/validator/register_validator": {http.MethodPost},
|
||||
"/eth/v1/validator/liveness/{epoch}": {http.MethodPost},
|
||||
}
|
||||
|
||||
prysmBeaconRoutes := map[string][]string{
|
||||
|
||||
@@ -32,7 +32,6 @@ go_library(
|
||||
"//beacon-chain/execution:go_default_library",
|
||||
"//beacon-chain/operations/attestations:go_default_library",
|
||||
"//beacon-chain/operations/blstoexec:go_default_library",
|
||||
"//beacon-chain/operations/payloadattestation:go_default_library",
|
||||
"//beacon-chain/operations/slashings:go_default_library",
|
||||
"//beacon-chain/operations/voluntaryexits:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
@@ -95,7 +94,6 @@ go_test(
|
||||
"//beacon-chain/operations/attestations:go_default_library",
|
||||
"//beacon-chain/operations/blstoexec:go_default_library",
|
||||
"//beacon-chain/operations/blstoexec/mock:go_default_library",
|
||||
"//beacon-chain/operations/payloadattestation/mock:go_default_library",
|
||||
"//beacon-chain/operations/slashings/mock:go_default_library",
|
||||
"//beacon-chain/operations/synccommittee:go_default_library",
|
||||
"//beacon-chain/operations/voluntaryexits/mock:go_default_library",
|
||||
|
||||
@@ -893,99 +893,3 @@ func (s *Server) SubmitProposerSlashing(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitPayloadAttestations submits payload attestation messages to the node's pool.
|
||||
func (s *Server) SubmitPayloadAttestations(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitPayloadAttestations")
|
||||
defer span.End()
|
||||
|
||||
currentEpoch := slots.ToEpoch(s.TimeFetcher.CurrentSlot())
|
||||
if currentEpoch < params.BeaconConfig().GloasForkEpoch {
|
||||
httputil.HandleError(w, fmt.Sprintf("payload attestations require the Gloas fork, current epoch %d, Gloas epoch %d", currentEpoch, params.BeaconConfig().GloasForkEpoch), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if shared.IsSyncing(ctx, w, s.SyncChecker, s.HeadFetcher, s.TimeFetcher, s.OptimisticModeFetcher) {
|
||||
return
|
||||
}
|
||||
|
||||
versionHeader := r.Header.Get(api.VersionHeader)
|
||||
if versionHeader == "" {
|
||||
httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var msgs []*structs.PayloadAttestationMessage
|
||||
if err := json.NewDecoder(r.Body).Decode(&msgs); err != nil {
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var failures []*server.IndexedError
|
||||
for i, msg := range msgs {
|
||||
consensusMsg, err := msg.ToConsensus()
|
||||
if err != nil {
|
||||
failures = append(failures, &server.IndexedError{
|
||||
Index: i,
|
||||
Message: "Could not convert message: " + err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: Add full gossip validation (BLS signatures, PTC membership).
|
||||
if err := s.PayloadAttestationPool.InsertPayloadAttestation(consensusMsg); err != nil {
|
||||
failures = append(failures, &server.IndexedError{
|
||||
Index: i,
|
||||
Message: "Could not insert payload attestation: " + err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if err := s.Broadcaster.Broadcast(ctx, consensusMsg); err != nil {
|
||||
log.WithError(err).Error("Could not broadcast payload attestation message")
|
||||
}
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
failuresErr := &server.IndexedErrorContainer{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: server.ErrIndexedValidationFail,
|
||||
Failures: failures,
|
||||
}
|
||||
httputil.WriteError(w, failuresErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ListPayloadAttestations retrieves payload attestations from the pool.
|
||||
func (s *Server) ListPayloadAttestations(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "beacon.ListPayloadAttestations")
|
||||
defer span.End()
|
||||
|
||||
currentEpoch := slots.ToEpoch(s.TimeFetcher.CurrentSlot())
|
||||
if currentEpoch < params.BeaconConfig().GloasForkEpoch {
|
||||
httputil.HandleError(w, fmt.Sprintf("payload attestations require the Gloas fork, current epoch %d, Gloas epoch %d", currentEpoch, params.BeaconConfig().GloasForkEpoch), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rawSlot, slot, ok := shared.UintFromQuery(w, r, "slot", false)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
allAtts := s.PayloadAttestationPool.PendingPayloadAttestations()
|
||||
|
||||
var data []*structs.PayloadAttestation
|
||||
for _, att := range allAtts {
|
||||
if rawSlot != "" && att.Data.Slot != primitives.Slot(slot) {
|
||||
continue
|
||||
}
|
||||
data = append(data, structs.PayloadAttestationFromConsensus(att))
|
||||
}
|
||||
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Gloas))
|
||||
httputil.WriteJson(w, &structs.GetPoolPayloadAttestationsResponse{
|
||||
Version: version.String(version.Gloas),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/attestations"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/blstoexec"
|
||||
blstoexecmock "github.com/OffchainLabs/prysm/v7/beacon-chain/operations/blstoexec/mock"
|
||||
payloadattestationmock "github.com/OffchainLabs/prysm/v7/beacon-chain/operations/payloadattestation/mock"
|
||||
slashingsmock "github.com/OffchainLabs/prysm/v7/beacon-chain/operations/slashings/mock"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/synccommittee"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/voluntaryexits/mock"
|
||||
@@ -2548,253 +2547,3 @@ var (
|
||||
}
|
||||
}`
|
||||
)
|
||||
|
||||
func TestSubmitPayloadAttestations(t *testing.T) {
|
||||
t.Run("pre-gloas fork returns error", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 100
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
slot := primitives.Slot(0)
|
||||
chainService := &blockchainmock.ChainService{Slot: &slot}
|
||||
s := &Server{
|
||||
TimeFetcher: chainService,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitPayloadAttestations(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Gloas fork", writer.Body.String())
|
||||
})
|
||||
t.Run("no version header", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
slot := primitives.Slot(0)
|
||||
chainService := &blockchainmock.ChainService{Slot: &slot}
|
||||
s := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
HeadFetcher: chainService,
|
||||
TimeFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitPayloadAttestations(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Eth-Consensus-Version", writer.Body.String())
|
||||
})
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
slot := primitives.Slot(1)
|
||||
chainService := &blockchainmock.ChainService{Slot: &slot}
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
pool := &payloadattestationmock.PoolMock{}
|
||||
s := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
HeadFetcher: chainService,
|
||||
TimeFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
Broadcaster: broadcaster,
|
||||
PayloadAttestationPool: pool,
|
||||
}
|
||||
|
||||
body := `[{
|
||||
"validator_index": "1",
|
||||
"data": {
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"slot": "1",
|
||||
"payload_present": true,
|
||||
"blob_data_available": true
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}]`
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com", strings.NewReader(body))
|
||||
request.Header.Set(api.VersionHeader, "gloas")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitPayloadAttestations(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
assert.Equal(t, 1, len(pool.Attestations))
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
})
|
||||
t.Run("invalid body", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
slot := primitives.Slot(1)
|
||||
chainService := &blockchainmock.ChainService{Slot: &slot}
|
||||
s := &Server{
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
HeadFetcher: chainService,
|
||||
TimeFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com", strings.NewReader("invalid"))
|
||||
request.Header.Set(api.VersionHeader, "gloas")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitPayloadAttestations(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListPayloadAttestations(t *testing.T) {
|
||||
t.Run("pre-gloas fork returns error", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 100
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
slot := primitives.Slot(0)
|
||||
chainService := &blockchainmock.ChainService{Slot: &slot}
|
||||
s := &Server{
|
||||
TimeFetcher: chainService,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.ListPayloadAttestations(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Gloas fork", writer.Body.String())
|
||||
})
|
||||
t.Run("empty pool", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
slot := primitives.Slot(0)
|
||||
chainService := &blockchainmock.ChainService{Slot: &slot}
|
||||
pool := &payloadattestationmock.PoolMock{}
|
||||
s := &Server{
|
||||
TimeFetcher: chainService,
|
||||
PayloadAttestationPool: pool,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.ListPayloadAttestations(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
resp := &structs.GetPoolPayloadAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, 0, len(resp.Data))
|
||||
})
|
||||
t.Run("returns attestations", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
slot := primitives.Slot(0)
|
||||
chainService := &blockchainmock.ChainService{Slot: &slot}
|
||||
pool := &payloadattestationmock.PoolMock{
|
||||
Attestations: []*ethpbv1alpha1.PayloadAttestation{
|
||||
{
|
||||
Data: ðpbv1alpha1.PayloadAttestationData{
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig1"), 96),
|
||||
},
|
||||
{
|
||||
Data: ðpbv1alpha1.PayloadAttestationData{
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
|
||||
Slot: 2,
|
||||
PayloadPresent: false,
|
||||
BlobDataAvailable: false,
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig2"), 96),
|
||||
},
|
||||
},
|
||||
}
|
||||
s := &Server{
|
||||
TimeFetcher: chainService,
|
||||
PayloadAttestationPool: pool,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.ListPayloadAttestations(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
resp := &structs.GetPoolPayloadAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, 2, len(resp.Data))
|
||||
assert.Equal(t, "gloas", resp.Version)
|
||||
})
|
||||
t.Run("filter by slot", func(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
slot := primitives.Slot(0)
|
||||
chainService := &blockchainmock.ChainService{Slot: &slot}
|
||||
pool := &payloadattestationmock.PoolMock{
|
||||
Attestations: []*ethpbv1alpha1.PayloadAttestation{
|
||||
{
|
||||
Data: ðpbv1alpha1.PayloadAttestationData{
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig1"), 96),
|
||||
},
|
||||
{
|
||||
Data: ðpbv1alpha1.PayloadAttestationData{
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
|
||||
Slot: 2,
|
||||
PayloadPresent: false,
|
||||
BlobDataAvailable: false,
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig2"), 96),
|
||||
},
|
||||
},
|
||||
}
|
||||
s := &Server{
|
||||
TimeFetcher: chainService,
|
||||
PayloadAttestationPool: pool,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com?slot=1", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.ListPayloadAttestations(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
resp := &structs.GetPoolPayloadAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
assert.Equal(t, "1", resp.Data[0].Data.Slot)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/execution"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/attestations"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/blstoexec"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/payloadattestation"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/slashings"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/voluntaryexits"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
|
||||
@@ -49,7 +48,6 @@ type Server struct {
|
||||
ExecutionReconstructor execution.Reconstructor
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
BLSChangesPool blstoexec.PoolManager
|
||||
PayloadAttestationPool payloadattestation.PoolManager
|
||||
ForkchoiceFetcher blockchain.ForkchoiceFetcher
|
||||
CoreService *core.Service
|
||||
AttestationStateFetcher blockchain.AttestationStateFetcher
|
||||
|
||||
@@ -21,7 +21,6 @@ go_library(
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/operations/attestations:go_default_library",
|
||||
"//beacon-chain/operations/payloadattestation:go_default_library",
|
||||
"//beacon-chain/operations/synccommittee:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/api/server/structs"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/builder"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/core"
|
||||
rpchelpers "github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/eth/helpers"
|
||||
@@ -1212,6 +1213,175 @@ func (s *Server) GetSyncCommitteeDuties(w http.ResponseWriter, r *http.Request)
|
||||
httputil.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetPTCDuties retrieves the payload timeliness committee (PTC) duties for the requested epoch.
|
||||
// The PTC is responsible for attesting to payload timeliness in ePBS (Gloas fork and later).
|
||||
func (s *Server) GetPTCDuties(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetPTCDuties")
|
||||
defer span.End()
|
||||
|
||||
if shared.IsSyncing(ctx, w, s.SyncChecker, s.HeadFetcher, s.TimeFetcher, s.OptimisticModeFetcher) {
|
||||
return
|
||||
}
|
||||
|
||||
_, requestedEpochUint, ok := shared.UintFromRoute(w, r, "epoch")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
requestedEpoch := primitives.Epoch(requestedEpochUint)
|
||||
|
||||
// PTC duties are only available from Gloas fork onwards.
|
||||
if requestedEpoch < params.BeaconConfig().GloasForkEpoch {
|
||||
httputil.HandleError(w, "PTC duties are not available before Gloas fork", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var indices []string
|
||||
err := json.NewDecoder(r.Body).Decode(&indices)
|
||||
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
|
||||
}
|
||||
if len(indices) == 0 {
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
requestedValIndices := make([]primitives.ValidatorIndex, len(indices))
|
||||
for i, ix := range indices {
|
||||
valIx, valid := shared.ValidateUint(w, fmt.Sprintf("ValidatorIndices[%d]", i), ix)
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
requestedValIndices[i] = primitives.ValidatorIndex(valIx)
|
||||
}
|
||||
|
||||
// Limit how far in the future we can query (current + 1 epoch).
|
||||
cs := s.TimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(cs)
|
||||
nextEpoch := currentEpoch + 1
|
||||
if requestedEpoch > nextEpoch {
|
||||
httputil.HandleError(w,
|
||||
fmt.Sprintf("Request epoch %d can not be greater than next epoch %d", requestedEpoch, nextEpoch),
|
||||
http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// For next epoch requests, we use the current epoch's state since PTC
|
||||
// assignments for next epoch can be computed from current epoch's state.
|
||||
// This mirrors the spec's get_ptc_assignment which asserts epoch <= next_epoch
|
||||
// and uses the current state to compute assignments.
|
||||
epochForState := requestedEpoch
|
||||
if requestedEpoch == nextEpoch {
|
||||
epochForState = currentEpoch
|
||||
}
|
||||
st, err := s.Stater.StateByEpoch(ctx, epochForState)
|
||||
if err != nil {
|
||||
shared.WriteStateFetchError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Build a set of requested validators for O(1) lookup.
|
||||
requestedSet := make(map[primitives.ValidatorIndex]bool, len(requestedValIndices))
|
||||
for _, idx := range requestedValIndices {
|
||||
requestedSet[idx] = true
|
||||
}
|
||||
|
||||
// Compute PTC duties for each slot in the epoch.
|
||||
startSlot, err := slots.EpochStart(requestedEpoch)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get epoch start slot: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
endSlot := startSlot + params.BeaconConfig().SlotsPerEpoch
|
||||
|
||||
duties := make([]*structs.PTCDuty, 0)
|
||||
for slot := startSlot; slot < endSlot; slot++ {
|
||||
ptc, err := gloas.PayloadCommittee(ctx, st, slot)
|
||||
if err != nil {
|
||||
httputil.HandleError(w,
|
||||
fmt.Sprintf("Could not get PTC for slot %d: %s", slot, err.Error()),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Check which requested validators are in this slot's PTC.
|
||||
for _, valIdx := range ptc {
|
||||
if !requestedSet[valIdx] {
|
||||
continue
|
||||
}
|
||||
// Validate the validator index with explicit bounds check.
|
||||
if uint64(valIdx) >= uint64(st.NumValidators()) {
|
||||
httputil.HandleError(w, fmt.Sprintf("Invalid validator index %d", valIdx), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
pubkey := st.PubkeyAtIndex(valIdx)
|
||||
// Defensive check: ensure pubkey is not zero.
|
||||
var zeroPubkey [fieldparams.BLSPubkeyLength]byte
|
||||
if bytes.Equal(pubkey[:], zeroPubkey[:]) {
|
||||
httputil.HandleError(w, fmt.Sprintf("Invalid validator index %d", valIdx), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
duties = append(duties, &structs.PTCDuty{
|
||||
Pubkey: hexutil.Encode(pubkey[:]),
|
||||
ValidatorIndex: strconv.FormatUint(uint64(valIdx), 10),
|
||||
Slot: strconv.FormatUint(uint64(slot), 10),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get dependent root. The dependent root is the block root at start_slot(epoch) - 1.
|
||||
// For epoch 0, this would underflow, so we use genesis block root.
|
||||
// For next epoch requests, we use the same dependent root as current epoch since
|
||||
// we're computing from the current epoch's state and the next epoch's RANDAO
|
||||
// isn't finalized yet.
|
||||
dependentEpoch := requestedEpoch
|
||||
if requestedEpoch == nextEpoch {
|
||||
dependentEpoch = currentEpoch
|
||||
}
|
||||
var dependentRoot []byte
|
||||
if dependentEpoch == 0 {
|
||||
r, err := s.BeaconDB.GenesisBlockRoot(ctx)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get genesis block root: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
dependentRoot = r[:]
|
||||
} else {
|
||||
dependentRoot, err = ptcDependentRoot(st, dependentEpoch)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get dependent root: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isOptimistic, err := s.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &structs.GetPTCDutiesResponse{
|
||||
DependentRoot: hexutil.Encode(dependentRoot),
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Data: duties,
|
||||
}
|
||||
httputil.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// ptcDependentRoot returns the block root that PTC assignments depend on.
|
||||
// PTC depends on the shuffling, which is determined by RANDAO at epoch boundary.
|
||||
func ptcDependentRoot(st state.BeaconState, epoch primitives.Epoch) ([]byte, error) {
|
||||
epochStartSlot, err := slots.EpochStart(epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return helpers.BlockRootAtSlot(st, epochStartSlot-1)
|
||||
}
|
||||
|
||||
// GetLiveness requests the beacon node to indicate if a validator has been observed to be live in a given epoch.
|
||||
// The beacon node might detect liveness by observing messages from the validator on the network,
|
||||
// in the beacon chain, from its API or from any other source.
|
||||
@@ -1427,61 +1597,3 @@ func sortProposerDuties(duties []*structs.ProposerDuty) error {
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPayloadAttestationData produces payload attestation data for the requested slot.
|
||||
func (s *Server) GetPayloadAttestationData(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.GetPayloadAttestationData")
|
||||
defer span.End()
|
||||
|
||||
currentEpoch := slots.ToEpoch(s.TimeFetcher.CurrentSlot())
|
||||
if currentEpoch < params.BeaconConfig().GloasForkEpoch {
|
||||
httputil.HandleError(w, fmt.Sprintf("payload attestation data requires the Gloas fork, current epoch %d, Gloas epoch %d", currentEpoch, params.BeaconConfig().GloasForkEpoch), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if shared.IsSyncing(ctx, w, s.SyncChecker, s.HeadFetcher, s.TimeFetcher, s.OptimisticModeFetcher) {
|
||||
return
|
||||
}
|
||||
|
||||
_, slot, ok := shared.UintFromRoute(w, r, "slot")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
headRoot, err := s.HeadFetcher.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get head root: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
payloadPresent, blobDataAvailable, err := s.PayloadStatusFetcher.PayloadStatus(primitives.Slot(slot))
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get payload status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := ðpbalpha.PayloadAttestationData{
|
||||
BeaconBlockRoot: headRoot,
|
||||
Slot: primitives.Slot(slot),
|
||||
PayloadPresent: payloadPresent,
|
||||
BlobDataAvailable: blobDataAvailable,
|
||||
}
|
||||
|
||||
if httputil.RespondWithSsz(r) {
|
||||
sszData, err := data.MarshalSSZ()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not marshal payload attestation data: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Gloas))
|
||||
httputil.WriteSsz(w, sszData)
|
||||
return
|
||||
}
|
||||
|
||||
response := &structs.GetPayloadAttestationDataResponse{
|
||||
Version: version.String(version.Gloas),
|
||||
Data: structs.PayloadAttestationDataFromConsensus(data),
|
||||
}
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Gloas))
|
||||
httputil.WriteJson(w, response)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
mockChain "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
|
||||
builderTest "github.com/OffchainLabs/prysm/v7/beacon-chain/builder/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
|
||||
dbutil "github.com/OffchainLabs/prysm/v7/beacon-chain/db/testing"
|
||||
@@ -2959,6 +2960,208 @@ func TestGetSyncCommitteeDuties(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPTCDuties(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
// Use fixed slot 0 for deterministic tests.
|
||||
slot := primitives.Slot(0)
|
||||
genesisTime := time.Now()
|
||||
// Need enough validators for PTC selection (PTC_SIZE is 512 on mainnet, 2 on minimal)
|
||||
numVals := uint64(fieldparams.PTCSize * 2)
|
||||
st, _ := util.DeterministicGenesisStateFulu(t, numVals)
|
||||
require.NoError(t, st.SetGenesisTime(genesisTime))
|
||||
|
||||
// Set up a genesis block root for dependent_root calculation.
|
||||
genesisRoot := [32]byte{1, 2, 3}
|
||||
db := dbutil.SetupDB(t)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(t.Context(), genesisRoot))
|
||||
|
||||
mockChainService := &mockChain.ChainService{Genesis: genesisTime, State: st, Slot: &slot}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{BeaconState: st},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
TimeFetcher: mockChainService,
|
||||
HeadFetcher: mockChainService,
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
t.Run("single validator in PTC", func(t *testing.T) {
|
||||
// Request duties for validator index 0
|
||||
var body bytes.Buffer
|
||||
_, err := body.WriteString("[\"0\"]")
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", &body)
|
||||
request.SetPathValue("epoch", "0")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetPTCDutiesResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.NotEmpty(t, resp.DependentRoot)
|
||||
})
|
||||
|
||||
t.Run("verifies actual PTC membership", func(t *testing.T) {
|
||||
// Compute expected PTC for slot 0 using the same helper.
|
||||
expectedPTC, err := gloas.PayloadCommittee(t.Context(), st, 0)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, expectedPTC, "PTC should not be empty")
|
||||
|
||||
// Request duties for all validators in the expected PTC.
|
||||
var indices []string
|
||||
for _, idx := range expectedPTC {
|
||||
indices = append(indices, strconv.FormatUint(uint64(idx), 10))
|
||||
}
|
||||
indicesJSON, err := json.Marshal(indices)
|
||||
require.NoError(t, err)
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", bytes.NewReader(indicesJSON))
|
||||
request.SetPathValue("epoch", "0")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetPTCDutiesResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
|
||||
// All requested validators should have duties for slot 0.
|
||||
assert.Equal(t, len(expectedPTC), len(resp.Data), "Should return duties for all PTC members")
|
||||
for _, duty := range resp.Data {
|
||||
assert.Equal(t, "0", duty.Slot, "All duties should be for slot 0")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("multiple validators", func(t *testing.T) {
|
||||
var body bytes.Buffer
|
||||
_, err := body.WriteString("[\"0\",\"1\",\"2\",\"3\",\"4\"]")
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", &body)
|
||||
request.SetPathValue("epoch", "0")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetPTCDutiesResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.NotEmpty(t, resp.DependentRoot)
|
||||
// Verify any returned duties have correct structure
|
||||
for _, duty := range resp.Data {
|
||||
assert.NotEmpty(t, duty.Pubkey)
|
||||
assert.NotEmpty(t, duty.ValidatorIndex)
|
||||
assert.NotEmpty(t, duty.Slot)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pre-Gloas epoch returns error", func(t *testing.T) {
|
||||
// Temporarily set GloasForkEpoch to 10
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.GloasForkEpoch = 10
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
defer func() {
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
}()
|
||||
|
||||
var body bytes.Buffer
|
||||
_, err := body.WriteString("[\"0\"]")
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", &body)
|
||||
request.SetPathValue("epoch", "0")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.StringContains(t, "PTC duties are not available before Gloas fork", e.Message)
|
||||
})
|
||||
|
||||
t.Run("no body", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", nil)
|
||||
request.SetPathValue("epoch", "0")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.StringContains(t, "No data submitted", e.Message)
|
||||
})
|
||||
|
||||
t.Run("empty body", func(t *testing.T) {
|
||||
var body bytes.Buffer
|
||||
_, err := body.WriteString("[]")
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", &body)
|
||||
request.SetPathValue("epoch", "0")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.StringContains(t, "No data submitted", e.Message)
|
||||
})
|
||||
|
||||
t.Run("invalid validator index string", func(t *testing.T) {
|
||||
var body bytes.Buffer
|
||||
_, err := body.WriteString("[\"foo\"]")
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", &body)
|
||||
request.SetPathValue("epoch", "0")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
})
|
||||
|
||||
t.Run("out of bounds validator index", func(t *testing.T) {
|
||||
// Request a validator index that's way beyond the number of validators.
|
||||
var body bytes.Buffer
|
||||
_, err := body.WriteString("[\"999999999\"]")
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", &body)
|
||||
request.SetPathValue("epoch", "0")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
// OOB validator won't be in any PTC, so request succeeds with empty duties.
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetPTCDutiesResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, 0, len(resp.Data), "OOB validator should have no duties")
|
||||
})
|
||||
|
||||
t.Run("epoch too far in future", func(t *testing.T) {
|
||||
var body bytes.Buffer
|
||||
_, err := body.WriteString("[\"0\"]")
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://www.example.com/eth/v1/validator/duties/ptc/{epoch}", &body)
|
||||
request.SetPathValue("epoch", "100") // Far future epoch
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPTCDuties(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.StringContains(t, "can not be greater than next epoch", e.Message)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrepareBeaconProposer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed/operation"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/db"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/attestations"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/payloadattestation"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/synccommittee"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/core"
|
||||
@@ -39,6 +38,4 @@ type Server struct {
|
||||
BlockRewardFetcher rewards.BlockRewardsFetcher
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
PayloadIDCache *cache.PayloadIDCache
|
||||
PayloadAttestationPool payloadattestation.PoolManager
|
||||
PayloadStatusFetcher payloadattestation.PayloadStatusFetcher
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
lightClient "github.com/OffchainLabs/prysm/v7/beacon-chain/light-client"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/attestations"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/blstoexec"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/payloadattestation"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/slashings"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/synccommittee"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/operations/voluntaryexits"
|
||||
@@ -104,7 +103,6 @@ type Config struct {
|
||||
SlashingsPool slashings.PoolManager
|
||||
SyncCommitteeObjectPool synccommittee.Pool
|
||||
BLSChangesPool blstoexec.PoolManager
|
||||
PayloadAttestationPool payloadattestation.PoolManager
|
||||
SyncService chainSync.Checker
|
||||
Broadcaster p2p.Broadcaster
|
||||
PeersFetcher p2p.PeersProvider
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
### Added
|
||||
|
||||
- GET /eth/v1/validator/payload_attestation_data/{slot}
|
||||
- POST & GET /eth/v1/beacon/pool/payload_attestations
|
||||
3
changelog/satushh_ptc-duties-endpoint.md
Normal file
3
changelog/satushh_ptc-duties-endpoint.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- PTC (Payload Timeliness Committee) duties endpoint: POST /eth/v1/validator/duties/ptc/{epoch} for ePBS (Gloas fork).
|
||||
Reference in New Issue
Block a user