Skip to content

Commit

Permalink
Prysm rpc: Get payload attestation data
Browse files Browse the repository at this point in the history
  • Loading branch information
terencechain committed Aug 24, 2024
1 parent 5ba33f7 commit c45122b
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 107 deletions.
2 changes: 1 addition & 1 deletion beacon-chain/blockchain/chain_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ 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)
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 @@ -36,11 +36,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
5 changes: 5 additions & 0 deletions beacon-chain/blockchain/receive_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ type BlockReceiver interface {
BlockBeingSynced([32]byte) bool
}

// ExecutionPayloadReceiver interface defines the methods of chain service for receiving and processing new execution payload envelope.
type ExecutionPayloadReceiver interface {
PayloadBeingSynced(root [32]byte) (primitives.PTCStatus, bool)
}

// BlobReceiver interface defines the methods of chain service for receiving new
// blobs
type BlobReceiver interface {
Expand Down
16 changes: 12 additions & 4 deletions beacon-chain/blockchain/testing/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type ChainService struct {
NotFinalized bool
Optimistic bool
ValidAttestation bool
HasPayload bool
ValidatorsRoot [32]byte
PublicKey [fieldparams.BLSPubkeyLength]byte
FinalizedCheckPoint *ethpb.Checkpoint
Expand Down Expand Up @@ -77,6 +78,9 @@ type ChainService struct {
SyncingRoot [32]byte
Blobs []blocks.VerifiedROBlob
TargetRoot [32]byte
HighestReceivedSlot primitives.Slot
HighestReceivedRoot [32]byte
PayloadStatus primitives.PTCStatus
}

func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
Expand Down Expand Up @@ -569,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 @@ -634,3 +638,7 @@ func (c *ChainService) ReceiveBlob(_ context.Context, b blocks.VerifiedROBlob) e
func (c *ChainService) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
return c.TargetRoot, nil
}

func (c *ChainService) PayloadBeingSynced(root [32]byte) (primitives.PTCStatus, bool) {
return c.PayloadStatus, c.HasPayload
}
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 @@ -64,7 +64,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 @@ -112,9 +112,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 @@ -253,9 +253,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) HighestReceivedBlockSlotRootCalled() (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 @@ -916,7 +916,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
27 changes: 26 additions & 1 deletion beacon-chain/rpc/prysm/v1alpha1/validator/ptc_attester.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,36 @@ import (

"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
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, ok := vs.ExecutionPayloadReceiver.PayloadBeingSynced(root)
if !ok {
payloadStatus = primitives.PAYLOAD_ABSENT
}

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

func (vs *Server) SubmitPayloadAttestation(ctx context.Context, in *ethpb.PayloadAttestationMessage) (*empty.Empty, error) {
Expand Down
76 changes: 76 additions & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/ptc_attester_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package validator

import (
"testing"
"time"

mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/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"
)

func TestServer_GetPayloadAttestationData(t *testing.T) {
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(nil, &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(nil, &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},
ExecutionPayloadReceiver: &mock.ChainService{HasPayload: false},
}
d, err := s.GetPayloadAttestationData(nil, &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},
ExecutionPayloadReceiver: &mock.ChainService{HasPayload: true, PayloadStatus: primitives.PAYLOAD_PRESENT},
}
d, err := s.GetPayloadAttestationData(nil, &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},
ExecutionPayloadReceiver: &mock.ChainService{HasPayload: true, PayloadStatus: primitives.PAYLOAD_WITHHELD},
}
d, err := s.GetPayloadAttestationData(nil, &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)
})
}
Loading

0 comments on commit c45122b

Please sign in to comment.