Skip to content

Commit

Permalink
Prysm rpc: Get payload attestation data (#14380)
Browse files Browse the repository at this point in the history
  • Loading branch information
terencechain authored Sep 10, 2024
1 parent 674196e commit fdccbb5
Show file tree
Hide file tree
Showing 12 changed files with 138 additions and 42 deletions.
9 changes: 2 additions & 7 deletions beacon-chain/blockchain/chain_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ type ForkchoiceFetcher interface {
GetProposerHead() [32]byte
SetForkChoiceGenesisTime(uint64)
UpdateHead(context.Context, primitives.Slot)
HighestReceivedBlockSlot() primitives.Slot
HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte)
ReceivedBlocksLastEpoch() (uint64, error)
InsertNode(context.Context, state.BeaconState, [32]byte) error
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
NewSlot(context.Context, primitives.Slot) error
ProposerBoost() [32]byte
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
GetPTCVote(root [32]byte) primitives.PTCStatus
}

// TimeFetcher retrieves the Ethereum consensus data that's related to time.
Expand Down Expand Up @@ -544,12 +545,6 @@ func (s *Service) recoverStateSummary(ctx context.Context, blockRoot [32]byte) (
return nil, errBlockDoesNotExist
}

// PayloadBeingSynced returns whether the payload for the block with the given
// root is currently being synced and what is the withheld status in the payload
func (s *Service) PayloadBeingSynced(root [32]byte) (primitives.PTCStatus, bool) {
return s.payloadBeingSynced.isSyncing(root)
}

// BlockBeingSynced returns whether the block with the given root is currently being synced
func (s *Service) BlockBeingSynced(root [32]byte) bool {
return s.blockBeingSynced.isSyncing(root)
Expand Down
6 changes: 3 additions & 3 deletions beacon-chain/blockchain/chain_info_forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ func (s *Service) SetForkChoiceGenesisTime(timestamp uint64) {
s.cfg.ForkChoiceStore.SetGenesisTime(timestamp)
}

// HighestReceivedBlockSlot returns the corresponding value from forkchoice
func (s *Service) HighestReceivedBlockSlot() primitives.Slot {
// HighestReceivedBlockSlotRoot returns the corresponding value from forkchoice
func (s *Service) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.HighestReceivedBlockSlot()
return s.cfg.ForkChoiceStore.HighestReceivedBlockSlotRoot()
}

// ReceivedBlocksLastEpoch returns the corresponding value from forkchoice
Expand Down
15 changes: 11 additions & 4 deletions beacon-chain/blockchain/testing/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ type ChainService struct {
SyncingRoot [32]byte
Blobs []blocks.VerifiedROBlob
TargetRoot [32]byte
HighestReceivedSlot primitives.Slot
HighestReceivedRoot [32]byte
PayloadStatus primitives.PTCStatus
ReceivePayloadAttestationMessageErr error
}

Expand Down Expand Up @@ -570,12 +573,12 @@ func (s *ChainService) ReceivedBlocksLastEpoch() (uint64, error) {
return 0, nil
}

// HighestReceivedBlockSlot mocks the same method in the chain service
func (s *ChainService) HighestReceivedBlockSlot() primitives.Slot {
// HighestReceivedBlockSlotRoot mocks the same method in the chain service
func (s *ChainService) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
if s.ForkChoiceStore != nil {
return s.ForkChoiceStore.HighestReceivedBlockSlot()
return s.ForkChoiceStore.HighestReceivedBlockSlotRoot()
}
return 0
return s.HighestReceivedSlot, s.HighestReceivedRoot
}

// InsertNode mocks the same method in the chain service
Expand Down Expand Up @@ -645,3 +648,7 @@ func (c *ChainService) HashInForkchoice([32]byte) bool {
func (c *ChainService) ReceivePayloadAttestationMessage(_ context.Context, _ *ethpb.PayloadAttestationMessage) error {
return c.ReceivePayloadAttestationMessageErr
}

func (c *ChainService) GetPTCVote(root [32]byte) primitives.PTCStatus {
return c.PayloadStatus
}
8 changes: 4 additions & 4 deletions beacon-chain/forkchoice/doubly-linked-tree/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,12 +249,12 @@ func (s *Store) tips() ([][32]byte, []primitives.Slot) {
return roots, slots
}

// HighestReceivedBlockSlot returns the highest slot received by the forkchoice
func (f *ForkChoice) HighestReceivedBlockSlot() primitives.Slot {
// HighestReceivedBlockSlotRoot returns the highest slot and root received by the forkchoice
func (f *ForkChoice) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
if f.store.highestReceivedNode == nil {
return 0
return 0, [32]byte{}
}
return f.store.highestReceivedNode.slot
return f.store.highestReceivedNode.slot, f.store.highestReceivedNode.root
}

// HighestReceivedBlockSlotDelay returns the number of slots that the highest
Expand Down
27 changes: 18 additions & 9 deletions beacon-chain/forkchoice/doubly-linked-tree/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err := f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(1), f.HighestReceivedBlockSlot())
slot, _ := f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(1), slot)
require.Equal(t, primitives.Slot(0), f.HighestReceivedBlockDelay())

// 64
Expand All @@ -343,7 +344,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(64), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(64), slot)
require.Equal(t, primitives.Slot(0), f.HighestReceivedBlockDelay())

// 64 65
Expand All @@ -354,7 +356,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(2), count)
require.Equal(t, primitives.Slot(65), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(65), slot)
require.Equal(t, primitives.Slot(1), f.HighestReceivedBlockDelay())

// 64 65 66
Expand All @@ -365,7 +368,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(3), count)
require.Equal(t, primitives.Slot(66), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(66), slot)

// 64 65 66
// 98
Expand All @@ -376,7 +380,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(98), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(98), slot)

// 64 65 66
// 98
Expand All @@ -388,7 +393,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(132), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(132), slot)

// 64 65 66
// 98
Expand All @@ -401,7 +407,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(132), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(132), slot)

// 64 65 66
// 98
Expand All @@ -414,7 +421,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(132), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(132), slot)

// 64 65 66
// 98
Expand All @@ -427,7 +435,8 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(2), count)
require.Equal(t, primitives.Slot(132), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(132), slot)

s.genesisTime = uint64(time.Now().Add(time.Duration(-134*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/forkchoice/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ type FastGetter interface {
FinalizedPayloadBlockHash() [32]byte
HasNode([32]byte) bool
HasHash([32]byte) bool
HighestReceivedBlockSlot() primitives.Slot
HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte)
HighestReceivedBlockDelay() primitives.Slot
IsCanonical(root [32]byte) bool
IsOptimistic(root [32]byte) (bool, error)
Expand Down
6 changes: 3 additions & 3 deletions beacon-chain/forkchoice/ro.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@ func (ro *ROForkChoice) NodeCount() int {
return ro.getter.NodeCount()
}

// HighestReceivedBlockSlot delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) HighestReceivedBlockSlot() primitives.Slot {
// HighestReceivedBlockSlotRoot delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.HighestReceivedBlockSlot()
return ro.getter.HighestReceivedBlockSlotRoot()
}

// HighestReceivedBlockDelay delegates to the underlying forkchoice call, under a lock.
Expand Down
14 changes: 7 additions & 7 deletions beacon-chain/forkchoice/ro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const (
justifiedPayloadBlockHashCalled
unrealizedJustifiedPayloadBlockHashCalled
nodeCountCalled
highestReceivedBlockSlotCalled
highestReceivedBlockSlotRootCalled
highestReceivedBlockDelayCalled
receivedBlocksLastEpochCalled
weightCalled
Expand Down Expand Up @@ -113,9 +113,9 @@ func TestROLocking(t *testing.T) {
cb: func(g FastGetter) { g.NodeCount() },
},
{
name: "highestReceivedBlockSlotCalled",
call: highestReceivedBlockSlotCalled,
cb: func(g FastGetter) { g.HighestReceivedBlockSlot() },
name: "highestReceivedBlockSlotRootCalled",
call: highestReceivedBlockSlotRootCalled,
cb: func(g FastGetter) { g.HighestReceivedBlockSlotRoot() },
},
{
name: "highestReceivedBlockDelayCalled",
Expand Down Expand Up @@ -254,9 +254,9 @@ func (ro *mockROForkchoice) NodeCount() int {
return 0
}

func (ro *mockROForkchoice) HighestReceivedBlockSlot() primitives.Slot {
ro.calls = append(ro.calls, highestReceivedBlockSlotCalled)
return 0
func (ro *mockROForkchoice) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
ro.calls = append(ro.calls, highestReceivedBlockSlotRootCalled)
return 0, [32]byte{}
}

func (ro *mockROForkchoice) HighestReceivedBlockDelay() primitives.Slot {
Expand Down
3 changes: 2 additions & 1 deletion beacon-chain/rpc/eth/beacon/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,8 @@ func (s *Server) validateConsensus(ctx context.Context, blk interfaces.ReadOnlyS
}

func (s *Server) validateEquivocation(blk interfaces.ReadOnlyBeaconBlock) error {
if s.ForkchoiceFetcher.HighestReceivedBlockSlot() == blk.Slot() {
slot, _ := s.ForkchoiceFetcher.HighestReceivedBlockSlotRoot()
if slot == blk.Slot() {
return errors.Wrapf(errEquivocatedBlock, "block for slot %d already exists in fork choice", blk.Slot())
}
return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (vs *Server) circuitBreakBuilder(s primitives.Slot) (bool, error) {
}

// Circuit breaker is active if the missing consecutive slots greater than `MaxBuilderConsecutiveMissedSlots`.
highestReceivedSlot := vs.ForkchoiceFetcher.HighestReceivedBlockSlot()
highestReceivedSlot, _ := vs.ForkchoiceFetcher.HighestReceivedBlockSlotRoot()
maxConsecutiveSkipSlotsAllowed := params.BeaconConfig().MaxBuilderConsecutiveMissedSlots
diff, err := s.SafeSubSlot(highestReceivedSlot)
if err != nil {
Expand Down
22 changes: 20 additions & 2 deletions beacon-chain/rpc/prysm/v1alpha1/validator/ptc_attester.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,32 @@ import (
"context"

"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

// GetPayloadAttestationData returns the payload attestation data for a given slot.
// The request slot must be the current slot and there must exist a block from the current slot or the request will fail.
func (vs *Server) GetPayloadAttestationData(ctx context.Context, req *ethpb.GetPayloadAttestationDataRequest) (*ethpb.PayloadAttestationData, error) {
return nil, errors.New("not implemented")
reqSlot := req.Slot
currentSlot := vs.TimeFetcher.CurrentSlot()
if reqSlot != currentSlot {
return nil, status.Errorf(codes.InvalidArgument, "Payload attestation request slot %d != current slot %d", reqSlot, currentSlot)
}

highestSlot, root := vs.ForkchoiceFetcher.HighestReceivedBlockSlotRoot()
if reqSlot != highestSlot {
return nil, status.Errorf(codes.Unavailable, "Did not receive current slot %d block ", reqSlot)
}

payloadStatus := vs.ForkchoiceFetcher.GetPTCVote(root)

return &ethpb.PayloadAttestationData{
BeaconBlockRoot: root[:],
Slot: highestSlot,
PayloadStatus: payloadStatus,
}, nil
}

// SubmitPayloadAttestation broadcasts a payload attestation message to the network and saves the payload attestation to the cache.
Expand Down
66 changes: 66 additions & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/ptc_attester_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package validator
import (
"context"
"testing"
"time"

"github.com/pkg/errors"
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
p2ptest "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/testing"

"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
Expand Down Expand Up @@ -39,3 +43,65 @@ func TestServer_SubmitPayloadAttestation(t *testing.T) {
require.NoError(t, err)
})
}

func TestServer_GetPayloadAttestationData(t *testing.T) {
ctx := context.Background()
t.Run("Not current slot", func(t *testing.T) {
s := &Server{
TimeFetcher: &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0)},
}
_, err := s.GetPayloadAttestationData(ctx, &ethpb.GetPayloadAttestationDataRequest{Slot: 2})
require.ErrorContains(t, "Payload attestation request slot 2 != current slot 1", err)
})

t.Run("Last received block is not from current slot", func(t *testing.T) {
s := &Server{
TimeFetcher: &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(2*params.BeaconConfig().SecondsPerSlot), 0)},
ForkchoiceFetcher: &mock.ChainService{HighestReceivedSlot: 1},
}
_, err := s.GetPayloadAttestationData(ctx, &ethpb.GetPayloadAttestationDataRequest{Slot: 2})
require.ErrorContains(t, "Did not receive current slot 2 block ", err)
})

t.Run("Payload is absent", func(t *testing.T) {
slot := primitives.Slot(2)
root := [32]byte{1}
s := &Server{
TimeFetcher: &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(2*params.BeaconConfig().SecondsPerSlot), 0)},
ForkchoiceFetcher: &mock.ChainService{HighestReceivedSlot: slot, HighestReceivedRoot: root, PayloadStatus: primitives.PAYLOAD_ABSENT},
}
d, err := s.GetPayloadAttestationData(ctx, &ethpb.GetPayloadAttestationDataRequest{Slot: slot})
require.NoError(t, err)
require.DeepEqual(t, root[:], d.BeaconBlockRoot)
require.Equal(t, slot, d.Slot)
require.Equal(t, primitives.PAYLOAD_ABSENT, d.PayloadStatus)
})

t.Run("Payload is present", func(t *testing.T) {
slot := primitives.Slot(2)
root := [32]byte{1}
s := &Server{
TimeFetcher: &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(2*params.BeaconConfig().SecondsPerSlot), 0)},
ForkchoiceFetcher: &mock.ChainService{HighestReceivedSlot: slot, HighestReceivedRoot: root, PayloadStatus: primitives.PAYLOAD_PRESENT},
}
d, err := s.GetPayloadAttestationData(ctx, &ethpb.GetPayloadAttestationDataRequest{Slot: slot})
require.NoError(t, err)
require.DeepEqual(t, root[:], d.BeaconBlockRoot)
require.Equal(t, slot, d.Slot)
require.Equal(t, primitives.PAYLOAD_PRESENT, d.PayloadStatus)
})

t.Run("Payload is withheld", func(t *testing.T) {
slot := primitives.Slot(2)
root := [32]byte{1}
s := &Server{
TimeFetcher: &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(2*params.BeaconConfig().SecondsPerSlot), 0)},
ForkchoiceFetcher: &mock.ChainService{HighestReceivedSlot: slot, HighestReceivedRoot: root, PayloadStatus: primitives.PAYLOAD_WITHHELD},
}
d, err := s.GetPayloadAttestationData(ctx, &ethpb.GetPayloadAttestationDataRequest{Slot: slot})
require.NoError(t, err)
require.DeepEqual(t, root[:], d.BeaconBlockRoot)
require.Equal(t, slot, d.Slot)
require.Equal(t, primitives.PAYLOAD_WITHHELD, d.PayloadStatus)
})
}

0 comments on commit fdccbb5

Please sign in to comment.