mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 21:38:05 -05:00
Compare commits
20 Commits
new-cache-
...
discoveryC
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
131db624f2 | ||
|
|
ef16d27322 | ||
|
|
b8c7fa70a4 | ||
|
|
30af80bc82 | ||
|
|
ebcbde4ef8 | ||
|
|
c2e0a3e764 | ||
|
|
12e0cc29fe | ||
|
|
b30beed484 | ||
|
|
48a35c08aa | ||
|
|
352d3306f3 | ||
|
|
2dd48343a2 | ||
|
|
7f931bf65b | ||
|
|
fda4589251 | ||
|
|
34593d34d4 | ||
|
|
4d18e590ed | ||
|
|
ec8b67cb12 | ||
|
|
a817aa0a8d | ||
|
|
d76f55e97a | ||
|
|
2de21eb22f | ||
|
|
58b8c31c93 |
@@ -304,6 +304,8 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
|
||||
}
|
||||
versionOpt := func(r *http.Request) {
|
||||
r.Header.Add("Eth-Consensus-Version", version.String(version.Bellatrix))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
r.Header.Set("Accept", "application/json")
|
||||
}
|
||||
rb, err := c.do(ctx, http.MethodPost, postBlindedBeaconBlockPath, bytes.NewBuffer(body), versionOpt)
|
||||
|
||||
@@ -341,6 +343,8 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
|
||||
}
|
||||
versionOpt := func(r *http.Request) {
|
||||
r.Header.Add("Eth-Consensus-Version", version.String(version.Capella))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
r.Header.Set("Accept", "application/json")
|
||||
}
|
||||
rb, err := c.do(ctx, http.MethodPost, postBlindedBeaconBlockPath, bytes.NewBuffer(body), versionOpt)
|
||||
|
||||
@@ -379,6 +383,8 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
|
||||
|
||||
versionOpt := func(r *http.Request) {
|
||||
r.Header.Add("Eth-Consensus-Version", version.String(version.Deneb))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
r.Header.Set("Accept", "application/json")
|
||||
}
|
||||
rb, err := c.do(ctx, http.MethodPost, postBlindedBeaconBlockPath, bytes.NewBuffer(body), versionOpt)
|
||||
if err != nil {
|
||||
|
||||
@@ -321,6 +321,8 @@ func TestSubmitBlindedBlock(t *testing.T) {
|
||||
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
|
||||
require.Equal(t, postBlindedBeaconBlockPath, r.URL.Path)
|
||||
require.Equal(t, "bellatrix", r.Header.Get("Eth-Consensus-Version"))
|
||||
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
require.Equal(t, "application/json", r.Header.Get("Accept"))
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(testExampleExecutionPayload)),
|
||||
@@ -347,6 +349,8 @@ func TestSubmitBlindedBlock(t *testing.T) {
|
||||
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
|
||||
require.Equal(t, postBlindedBeaconBlockPath, r.URL.Path)
|
||||
require.Equal(t, "capella", r.Header.Get("Eth-Consensus-Version"))
|
||||
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
require.Equal(t, "application/json", r.Header.Get("Accept"))
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(testExampleExecutionPayloadCapella)),
|
||||
@@ -376,6 +380,8 @@ func TestSubmitBlindedBlock(t *testing.T) {
|
||||
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
|
||||
require.Equal(t, postBlindedBeaconBlockPath, r.URL.Path)
|
||||
require.Equal(t, "deneb", r.Header.Get("Eth-Consensus-Version"))
|
||||
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
require.Equal(t, "application/json", r.Header.Get("Accept"))
|
||||
var req structs.SignedBlindedBeaconBlockDeneb
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -18,17 +18,63 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) state.ReadOnlyBeaconState {
|
||||
headEpoch := slots.ToEpoch(s.HeadSlot())
|
||||
if c.Epoch < headEpoch {
|
||||
return nil
|
||||
}
|
||||
if !s.cfg.ForkChoiceStore.IsCanonical([32]byte(c.Root)) {
|
||||
return nil
|
||||
}
|
||||
if c.Epoch == headEpoch {
|
||||
targetSlot, err := s.cfg.ForkChoiceStore.Slot([32]byte(c.Root))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if slots.ToEpoch(targetSlot)+1 < headEpoch {
|
||||
return nil
|
||||
}
|
||||
st, err := s.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return st
|
||||
}
|
||||
slot, err := slots.EpochStart(c.Epoch)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Try if we have already set the checkpoint cache
|
||||
epochKey := strconv.FormatUint(uint64(c.Epoch), 10 /* base 10 */)
|
||||
lock := async.NewMultilock(string(c.Root) + epochKey)
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
cachedState, err := s.checkpointStateCache.StateByCheckpoint(c)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if cachedState != nil && !cachedState.IsNil() {
|
||||
return cachedState
|
||||
}
|
||||
st, err := s.HeadState(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
st, err = transition.ProcessSlotsUsingNextSlotCache(ctx, st, c.Root, slot)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := s.checkpointStateCache.AddCheckpointState(c, st); err != nil {
|
||||
return nil
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
// getAttPreState retrieves the att pre state by either from the cache or the DB.
|
||||
func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (state.ReadOnlyBeaconState, error) {
|
||||
// If the attestation is recent and canonical we can use the head state to compute the shuffling.
|
||||
headEpoch := slots.ToEpoch(s.HeadSlot())
|
||||
if c.Epoch == headEpoch {
|
||||
targetSlot, err := s.cfg.ForkChoiceStore.Slot([32]byte(c.Root))
|
||||
if err == nil && slots.ToEpoch(targetSlot)+1 >= headEpoch {
|
||||
if s.cfg.ForkChoiceStore.IsCanonical([32]byte(c.Root)) {
|
||||
return s.HeadStateReadOnly(ctx)
|
||||
}
|
||||
}
|
||||
if st := s.getRecentPreState(ctx, c); st != nil {
|
||||
return st, nil
|
||||
}
|
||||
// Use a multilock to allow scoped holding of a mutex by a checkpoint root + epoch
|
||||
// allowing us to behave smarter in terms of how this function is used concurrently.
|
||||
|
||||
@@ -146,6 +146,28 @@ func TestStore_OnAttestation_Ok_DoublyLinkedTree(t *testing.T) {
|
||||
require.NoError(t, service.OnAttestation(ctx, att[0], 0))
|
||||
}
|
||||
|
||||
func TestService_GetRecentPreState(t *testing.T) {
|
||||
service, _ := minimalTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
s, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
ckRoot := bytesutil.PadTo([]byte{'A'}, fieldparams.RootLength)
|
||||
cp0 := ðpb.Checkpoint{Epoch: 0, Root: ckRoot}
|
||||
err = s.SetFinalizedCheckpoint(cp0)
|
||||
require.NoError(t, err)
|
||||
|
||||
st, root, err := prepareForkchoiceState(ctx, 31, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, cp0, cp0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root))
|
||||
service.head = &head{
|
||||
root: [32]byte(ckRoot),
|
||||
state: s,
|
||||
slot: 31,
|
||||
}
|
||||
require.NotNil(t, service.getRecentPreState(ctx, ðpb.Checkpoint{Epoch: 1, Root: ckRoot}))
|
||||
}
|
||||
|
||||
func TestService_GetAttPreState_Concurrency(t *testing.T) {
|
||||
service, _ := minimalTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
8
beacon-chain/cache/skip_slot_cache.go
vendored
8
beacon-chain/cache/skip_slot_cache.go
vendored
@@ -109,10 +109,6 @@ func (c *SkipSlotCache) Get(ctx context.Context, r [32]byte) (state.BeaconState,
|
||||
// MarkInProgress a request so that any other similar requests will block on
|
||||
// Get until MarkNotInProgress is called.
|
||||
func (c *SkipSlotCache) MarkInProgress(r [32]byte) error {
|
||||
if c.disabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
@@ -126,10 +122,6 @@ func (c *SkipSlotCache) MarkInProgress(r [32]byte) error {
|
||||
// MarkNotInProgress will release the lock on a given request. This should be
|
||||
// called after put.
|
||||
func (c *SkipSlotCache) MarkNotInProgress(r [32]byte) {
|
||||
if c.disabled {
|
||||
return
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
|
||||
26
beacon-chain/cache/skip_slot_cache_test.go
vendored
26
beacon-chain/cache/skip_slot_cache_test.go
vendored
@@ -2,6 +2,7 @@ package cache_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
@@ -35,3 +36,28 @@ func TestSkipSlotCache_RoundTrip(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, res.ToProto(), s.ToProto(), "Expected equal protos to return from cache")
|
||||
}
|
||||
|
||||
func TestSkipSlotCache_DisabledAndEnabled(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c := cache.NewSkipSlotCache()
|
||||
|
||||
r := [32]byte{'a'}
|
||||
c.Disable()
|
||||
|
||||
require.NoError(t, c.MarkInProgress(r))
|
||||
|
||||
c.Enable()
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
// Get call will only terminate when
|
||||
// it is not longer in progress.
|
||||
obj, err := c.Get(ctx, r)
|
||||
require.NoError(t, err)
|
||||
require.IsNil(t, obj)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
c.MarkNotInProgress(r)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ go_test(
|
||||
"//beacon-chain/operations/attestations/kv:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation/aggregation/attestations:go_default_library",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/v5/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
// pruneAttsPool prunes attestations pool on every slot interval.
|
||||
@@ -66,7 +67,18 @@ func (s *Service) pruneExpiredAtts() {
|
||||
|
||||
// Return true if the input slot has been expired.
|
||||
// Expired is defined as one epoch behind than current time.
|
||||
func (s *Service) expired(slot primitives.Slot) bool {
|
||||
func (s *Service) expired(providedSlot primitives.Slot) bool {
|
||||
providedEpoch := slots.ToEpoch(providedSlot)
|
||||
currSlot := slots.CurrentSlot(s.genesisTime)
|
||||
currEpoch := slots.ToEpoch(currSlot)
|
||||
if currEpoch < params.BeaconConfig().DenebForkEpoch {
|
||||
return s.expiredPreDeneb(providedSlot)
|
||||
}
|
||||
return providedEpoch+1 < currEpoch
|
||||
}
|
||||
|
||||
// Handles expiration of attestations before deneb.
|
||||
func (s *Service) expiredPreDeneb(slot primitives.Slot) bool {
|
||||
expirationSlot := slot + params.BeaconConfig().SlotsPerEpoch
|
||||
expirationTime := s.genesisTime + uint64(expirationSlot.Mul(params.BeaconConfig().SecondsPerSlot))
|
||||
currentTime := uint64(prysmTime.Now().Unix())
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/async"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"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/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
@@ -127,3 +128,22 @@ func TestPruneExpired_Expired(t *testing.T) {
|
||||
assert.Equal(t, true, s.expired(0), "Should be expired")
|
||||
assert.Equal(t, false, s.expired(1), "Should not be expired")
|
||||
}
|
||||
|
||||
func TestPruneExpired_ExpiredDeneb(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.DenebForkEpoch = 3
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
s, err := NewService(context.Background(), &Config{Pool: NewPool()})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Rewind back 4 epochs + 10 slots worth of time.
|
||||
s.genesisTime = uint64(prysmTime.Now().Unix()) - (4*uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot)) + 10)
|
||||
secondEpochStart := primitives.Slot(2 * uint64(params.BeaconConfig().SlotsPerEpoch))
|
||||
thirdEpochStart := primitives.Slot(3 * uint64(params.BeaconConfig().SlotsPerEpoch))
|
||||
|
||||
assert.Equal(t, true, s.expired(secondEpochStart), "Should be expired")
|
||||
assert.Equal(t, false, s.expired(thirdEpochStart), "Should not be expired")
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -68,7 +69,7 @@ func (s *Service) BroadcastAttestation(ctx context.Context, subnet uint64, att *
|
||||
}
|
||||
|
||||
// Non-blocking broadcast, with attempts to discover a subnet peer if none available.
|
||||
go s.broadcastAttestation(ctx, subnet, att, forkDigest)
|
||||
go s.internalBroadcastAttestation(ctx, subnet, att, forkDigest)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -94,8 +95,8 @@ func (s *Service) BroadcastSyncCommitteeMessage(ctx context.Context, subnet uint
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) broadcastAttestation(ctx context.Context, subnet uint64, att *ethpb.Attestation, forkDigest [4]byte) {
|
||||
_, span := trace.StartSpan(ctx, "p2p.broadcastAttestation")
|
||||
func (s *Service) internalBroadcastAttestation(ctx context.Context, subnet uint64, att *ethpb.Attestation, forkDigest [4]byte) {
|
||||
_, span := trace.StartSpan(ctx, "p2p.internalBroadcastAttestation")
|
||||
defer span.End()
|
||||
ctx = trace.NewContext(context.Background(), span) // clear parent context / deadline.
|
||||
|
||||
@@ -137,7 +138,10 @@ func (s *Service) broadcastAttestation(ctx context.Context, subnet uint64, att *
|
||||
// acceptable threshold, we exit early and do not broadcast it.
|
||||
currSlot := slots.CurrentSlot(uint64(s.genesisTime.Unix()))
|
||||
if att.Data.Slot+params.BeaconConfig().SlotsPerEpoch < currSlot {
|
||||
log.Warnf("Attestation is too old to broadcast, discarding it. Current Slot: %d , Attestation Slot: %d", currSlot, att.Data.Slot)
|
||||
log.WithFields(logrus.Fields{
|
||||
"attestationSlot": att.Data.Slot,
|
||||
"currentSlot": currSlot,
|
||||
}).Warning("Attestation is too old to broadcast, discarding it")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -218,13 +222,13 @@ func (s *Service) BroadcastBlob(ctx context.Context, subnet uint64, blob *ethpb.
|
||||
}
|
||||
|
||||
// Non-blocking broadcast, with attempts to discover a subnet peer if none available.
|
||||
go s.broadcastBlob(ctx, subnet, blob, forkDigest)
|
||||
go s.internalBroadcastBlob(ctx, subnet, blob, forkDigest)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) broadcastBlob(ctx context.Context, subnet uint64, blobSidecar *ethpb.BlobSidecar, forkDigest [4]byte) {
|
||||
_, span := trace.StartSpan(ctx, "p2p.broadcastBlob")
|
||||
func (s *Service) internalBroadcastBlob(ctx context.Context, subnet uint64, blobSidecar *ethpb.BlobSidecar, forkDigest [4]byte) {
|
||||
_, span := trace.StartSpan(ctx, "p2p.internalBroadcastBlob")
|
||||
defer span.End()
|
||||
ctx = trace.NewContext(context.Background(), span) // clear parent context / deadline.
|
||||
|
||||
|
||||
@@ -277,58 +277,69 @@ func (s *Service) startDiscoveryV5(
|
||||
// filterPeer validates each node that we retrieve from our dht. We
|
||||
// try to ascertain that the peer can be a valid protocol peer.
|
||||
// Validity Conditions:
|
||||
// 1. The local node is still actively looking for peers to
|
||||
// connect to.
|
||||
// 2. Peer has a valid IP and TCP port set in their enr.
|
||||
// 3. Peer hasn't been marked as 'bad'
|
||||
// 4. Peer is not currently active or connected.
|
||||
// 5. Peer is ready to receive incoming connections.
|
||||
// 6. Peer's fork digest in their ENR matches that of
|
||||
// 1. Peer has a valid IP and TCP port set in their enr.
|
||||
// 2. Peer hasn't been marked as 'bad'.
|
||||
// 3. Peer is not currently active or connected.
|
||||
// 4. Peer is ready to receive incoming connections.
|
||||
// 5. Peer's fork digest in their ENR matches that of
|
||||
// our localnodes.
|
||||
func (s *Service) filterPeer(node *enode.Node) bool {
|
||||
// Ignore nil node entries passed in.
|
||||
if node == nil {
|
||||
return false
|
||||
}
|
||||
// ignore nodes with no ip address stored.
|
||||
|
||||
// Ignore nodes with no IP address stored.
|
||||
if node.IP() == nil {
|
||||
return false
|
||||
}
|
||||
// do not dial nodes with their tcp ports not set
|
||||
|
||||
// Ignore nodes with their TCP ports not set.
|
||||
if err := node.Record().Load(enr.WithEntry("tcp", new(enr.TCP))); err != nil {
|
||||
if !enr.IsNotFound(err) {
|
||||
log.WithError(err).Debug("Could not retrieve tcp port")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
peerData, multiAddr, err := convertToAddrInfo(node)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Could not convert to peer data")
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore bad nodes.
|
||||
if s.peers.IsBad(peerData.ID) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore nodes that are already active.
|
||||
if s.peers.IsActive(peerData.ID) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore nodes that are already connected.
|
||||
if s.host.Network().Connectedness(peerData.ID) == network.Connected {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore nodes that are not ready to receive incoming connections.
|
||||
if !s.peers.IsReadyToDial(peerData.ID) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore nodes that don't match our fork digest.
|
||||
nodeENR := node.Record()
|
||||
// Decide whether or not to connect to peer that does not
|
||||
// match the proper fork ENR data with our local node.
|
||||
if s.genesisValidatorsRoot != nil {
|
||||
if err := s.compareForkENR(nodeENR); err != nil {
|
||||
log.WithError(err).Trace("Fork ENR mismatches between peer and local node")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Add peer to peer handler.
|
||||
s.peers.Add(nodeENR, peerData.ID, multiAddr, network.DirUnknown)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -73,9 +73,9 @@ func (s *Service) FindPeersWithSubnet(ctx context.Context, topic string,
|
||||
return false, errors.New("no subnet exists for provided topic")
|
||||
}
|
||||
|
||||
currNum := len(s.pubsub.ListPeers(topic))
|
||||
wg := new(sync.WaitGroup)
|
||||
for {
|
||||
currNum := len(s.pubsub.ListPeers(topic))
|
||||
if currNum >= threshold {
|
||||
break
|
||||
}
|
||||
@@ -99,7 +99,6 @@ func (s *Service) FindPeersWithSubnet(ctx context.Context, topic string,
|
||||
}
|
||||
// Wait for all dials to be completed.
|
||||
wg.Wait()
|
||||
currNum = len(s.pubsub.ListPeers(topic))
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -110,18 +109,13 @@ func (s *Service) filterPeerForAttSubnet(index uint64) func(node *enode.Node) bo
|
||||
if !s.filterPeer(node) {
|
||||
return false
|
||||
}
|
||||
|
||||
subnets, err := attSubnets(node.Record())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
indExists := false
|
||||
for _, comIdx := range subnets {
|
||||
if comIdx == index {
|
||||
indExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return indExists
|
||||
|
||||
return subnets[index]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,8 +199,10 @@ func initializePersistentSubnets(id enode.ID, epoch primitives.Epoch) error {
|
||||
//
|
||||
// return [compute_subscribed_subnet(node_id, epoch, index) for index in range(SUBNETS_PER_NODE)]
|
||||
func computeSubscribedSubnets(nodeID enode.ID, epoch primitives.Epoch) ([]uint64, error) {
|
||||
subs := []uint64{}
|
||||
for i := uint64(0); i < params.BeaconConfig().SubnetsPerNode; i++ {
|
||||
subnetsPerNode := params.BeaconConfig().SubnetsPerNode
|
||||
subs := make([]uint64, 0, subnetsPerNode)
|
||||
|
||||
for i := uint64(0); i < subnetsPerNode; i++ {
|
||||
sub, err := computeSubscribedSubnet(nodeID, epoch, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -281,19 +277,20 @@ func initializeSyncCommSubnets(node *enode.LocalNode) *enode.LocalNode {
|
||||
|
||||
// Reads the attestation subnets entry from a node's ENR and determines
|
||||
// the committee indices of the attestation subnets the node is subscribed to.
|
||||
func attSubnets(record *enr.Record) ([]uint64, error) {
|
||||
func attSubnets(record *enr.Record) (map[uint64]bool, error) {
|
||||
bitV, err := attBitvector(record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
committeeIdxs := make(map[uint64]bool)
|
||||
// lint:ignore uintcast -- subnet count can be safely cast to int.
|
||||
if len(bitV) != byteCount(int(attestationSubnetCount)) {
|
||||
return []uint64{}, errors.Errorf("invalid bitvector provided, it has a size of %d", len(bitV))
|
||||
return committeeIdxs, errors.Errorf("invalid bitvector provided, it has a size of %d", len(bitV))
|
||||
}
|
||||
var committeeIdxs []uint64
|
||||
|
||||
for i := uint64(0); i < attestationSubnetCount; i++ {
|
||||
if bitV.BitAt(i) {
|
||||
committeeIdxs = append(committeeIdxs, i)
|
||||
committeeIdxs[i] = true
|
||||
}
|
||||
}
|
||||
return committeeIdxs, nil
|
||||
|
||||
@@ -3,49 +3,46 @@ package p2p
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/wrapper"
|
||||
ecdsaprysm "github.com/prysmaticlabs/prysm/v5/crypto/ecdsa"
|
||||
pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func TestStartDiscV5_DiscoverPeersWithSubnets(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
// This test needs to be entirely rewritten and should be done in a follow up PR from #7885.
|
||||
t.Skip("This test is now failing after PR 7885 due to false positive")
|
||||
gFlags := new(flags.GlobalFlags)
|
||||
gFlags.MinimumPeersPerSubnet = 4
|
||||
flags.Init(gFlags)
|
||||
// Reset config.
|
||||
defer flags.Init(new(flags.GlobalFlags))
|
||||
port := 2000
|
||||
ipAddr, pkey := createAddrAndPrivKey(t)
|
||||
genesisTime := time.Now()
|
||||
genesisValidatorsRoot := make([]byte, 32)
|
||||
s := &Service{
|
||||
cfg: &Config{UDPPort: uint(port)},
|
||||
genesisTime: genesisTime,
|
||||
genesisValidatorsRoot: genesisValidatorsRoot,
|
||||
}
|
||||
bootListener, err := s.createListener(ipAddr, pkey)
|
||||
require.NoError(t, err)
|
||||
defer bootListener.Close()
|
||||
// Topology of this test:
|
||||
//
|
||||
//
|
||||
// Node 1 (subscribed to subnet 1) --\
|
||||
// |
|
||||
// Node 2 (subscribed to subnet 2) -----> BootNode (not subscribed to any subnet) <------- Node 0 (not subscribed to any subnet)
|
||||
// |
|
||||
// Node 3 (subscribed to subnet 3) --/
|
||||
//
|
||||
// The purpose of this test is to ensure that the "Node 0" (connected only to the boot node) is able to
|
||||
// find and connect to a node already subscribed to a specific subnet.
|
||||
// In our case: The node i is subscribed to subnet i, with i = 1, 2, 3
|
||||
|
||||
// Define the genesis validators root, to ensure everybody is on the same network.
|
||||
const genesisValidatorRootStr = "0xdeadbeefcafecafedeadbeefcafecafedeadbeefcafecafedeadbeefcafecafe"
|
||||
genesisValidatorsRoot, err := hex.DecodeString(genesisValidatorRootStr[2:])
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a context.
|
||||
ctx := context.Background()
|
||||
|
||||
bootNode := bootListener.Self()
|
||||
// Use shorter period for testing.
|
||||
currentPeriod := pollingPeriod
|
||||
pollingPeriod = 1 * time.Second
|
||||
@@ -53,111 +50,147 @@ func TestStartDiscV5_DiscoverPeersWithSubnets(t *testing.T) {
|
||||
pollingPeriod = currentPeriod
|
||||
}()
|
||||
|
||||
var listeners []*discover.UDPv5
|
||||
// Create flags.
|
||||
params.SetupTestConfigCleanup(t)
|
||||
gFlags := new(flags.GlobalFlags)
|
||||
gFlags.MinimumPeersPerSubnet = 1
|
||||
flags.Init(gFlags)
|
||||
|
||||
params.BeaconNetworkConfig().MinimumPeersInSubnetSearch = 1
|
||||
|
||||
// Reset config.
|
||||
defer flags.Init(new(flags.GlobalFlags))
|
||||
|
||||
// First, generate a bootstrap node.
|
||||
ipAddr, pkey := createAddrAndPrivKey(t)
|
||||
genesisTime := time.Now()
|
||||
|
||||
bootNodeService := &Service{
|
||||
cfg: &Config{TCPPort: 2000, UDPPort: 3000},
|
||||
genesisTime: genesisTime,
|
||||
genesisValidatorsRoot: genesisValidatorsRoot,
|
||||
}
|
||||
|
||||
bootNodeForkDigest, err := bootNodeService.currentForkDigest()
|
||||
require.NoError(t, err)
|
||||
|
||||
bootListener, err := bootNodeService.createListener(ipAddr, pkey)
|
||||
require.NoError(t, err)
|
||||
defer bootListener.Close()
|
||||
|
||||
bootNodeENR := bootListener.Self().String()
|
||||
|
||||
// Create 3 nodes, each subscribed to a different subnet.
|
||||
// Each node is connected to the boostrap node.
|
||||
services := make([]*Service, 0, 3)
|
||||
|
||||
for i := 1; i <= 3; i++ {
|
||||
port = 3000 + i
|
||||
cfg := &Config{
|
||||
Discv5BootStrapAddrs: []string{bootNode.String()},
|
||||
subnet := uint64(i)
|
||||
service, err := NewService(ctx, &Config{
|
||||
Discv5BootStrapAddrs: []string{bootNodeENR},
|
||||
MaxPeers: 30,
|
||||
UDPPort: uint(port),
|
||||
}
|
||||
ipAddr, pkey := createAddrAndPrivKey(t)
|
||||
s = &Service{
|
||||
cfg: cfg,
|
||||
genesisTime: genesisTime,
|
||||
genesisValidatorsRoot: genesisValidatorsRoot,
|
||||
}
|
||||
listener, err := s.startDiscoveryV5(ipAddr, pkey)
|
||||
assert.NoError(t, err, "Could not start discovery for node")
|
||||
TCPPort: uint(2000 + i),
|
||||
UDPPort: uint(3000 + i),
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
service.genesisTime = genesisTime
|
||||
service.genesisValidatorsRoot = genesisValidatorsRoot
|
||||
|
||||
nodeForkDigest, err := service.currentForkDigest()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, nodeForkDigest == bootNodeForkDigest, "fork digest of the node doesn't match the boot node")
|
||||
|
||||
// Start the service.
|
||||
service.Start()
|
||||
|
||||
// Set the ENR `attnets`, used by Prysm to filter peers by subnet.
|
||||
bitV := bitfield.NewBitvector64()
|
||||
bitV.SetBitAt(uint64(i), true)
|
||||
|
||||
bitV.SetBitAt(subnet, true)
|
||||
entry := enr.WithEntry(attSubnetEnrKey, &bitV)
|
||||
listener.LocalNode().Set(entry)
|
||||
listeners = append(listeners, listener)
|
||||
service.dv5Listener.LocalNode().Set(entry)
|
||||
|
||||
// Join and subscribe to the subnet, needed by libp2p.
|
||||
topic, err := service.pubsub.Join(fmt.Sprintf(AttestationSubnetTopicFormat, bootNodeForkDigest, subnet) + "/ssz_snappy")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = topic.Subscribe()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Memoize the service.
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
// Stop the services.
|
||||
defer func() {
|
||||
// Close down all peers.
|
||||
for _, listener := range listeners {
|
||||
listener.Close()
|
||||
for _, service := range services {
|
||||
err := service.Stop()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make one service on port 4001.
|
||||
port = 4001
|
||||
gs := startup.NewClockSynchronizer()
|
||||
cfg := &Config{
|
||||
Discv5BootStrapAddrs: []string{bootNode.String()},
|
||||
Discv5BootStrapAddrs: []string{bootNodeENR},
|
||||
MaxPeers: 30,
|
||||
UDPPort: uint(port),
|
||||
ClockWaiter: gs,
|
||||
TCPPort: 2010,
|
||||
UDPPort: 3010,
|
||||
}
|
||||
s, err = NewService(context.Background(), cfg)
|
||||
|
||||
service, err := NewService(ctx, cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
exitRoutine := make(chan bool)
|
||||
go func() {
|
||||
s.Start()
|
||||
<-exitRoutine
|
||||
service.genesisTime = genesisTime
|
||||
service.genesisValidatorsRoot = genesisValidatorsRoot
|
||||
|
||||
service.Start()
|
||||
defer func() {
|
||||
err := service.Stop()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
// Send in a loop to ensure it is delivered (busy wait for the service to subscribe to the state feed).
|
||||
var vr [32]byte
|
||||
require.NoError(t, gs.SetClock(startup.NewClock(time.Now(), vr)))
|
||||
|
||||
// Wait for the nodes to have their local routing tables to be populated with the other nodes
|
||||
time.Sleep(6 * discoveryWaitTime)
|
||||
|
||||
// look up 3 different subnets
|
||||
ctx := context.Background()
|
||||
exists, err := s.FindPeersWithSubnet(ctx, "", 1, flags.Get().MinimumPeersPerSubnet)
|
||||
require.NoError(t, err)
|
||||
exists2, err := s.FindPeersWithSubnet(ctx, "", 2, flags.Get().MinimumPeersPerSubnet)
|
||||
require.NoError(t, err)
|
||||
exists3, err := s.FindPeersWithSubnet(ctx, "", 3, flags.Get().MinimumPeersPerSubnet)
|
||||
require.NoError(t, err)
|
||||
if !exists || !exists2 || !exists3 {
|
||||
t.Fatal("Peer with subnet doesn't exist")
|
||||
// Wait for the nodes to have their local routing tables to be populated with the other nodes.
|
||||
totalNodeCount := 4
|
||||
for {
|
||||
// Wait for bootnode to be populated with all the nodes
|
||||
// initialized in the test.
|
||||
if len(bootListener.AllNodes()) < totalNodeCount {
|
||||
// To prevent the loop from spinning endlessly.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Update ENR of a peer.
|
||||
testService := &Service{
|
||||
dv5Listener: listeners[0],
|
||||
metaData: wrapper.WrappedMetadataV0(&pb.MetaDataV0{
|
||||
Attnets: bitfield.NewBitvector64(),
|
||||
}),
|
||||
// Look up 3 different subnets.
|
||||
exists := make([]bool, 0, 3)
|
||||
for i := 1; i <= 3; i++ {
|
||||
subnet := uint64(i)
|
||||
topic := fmt.Sprintf(AttestationSubnetTopicFormat, bootNodeForkDigest, subnet)
|
||||
exist, err := service.FindPeersWithSubnet(ctx, topic, subnet, 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
exists = append(exists, exist)
|
||||
}
|
||||
cache.SubnetIDs.AddAttesterSubnetID(0, 10)
|
||||
testService.RefreshENR()
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
exists, err = s.FindPeersWithSubnet(ctx, "", 2, flags.Get().MinimumPeersPerSubnet)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, true, exists, "Peer with subnet doesn't exist")
|
||||
assert.NoError(t, s.Stop())
|
||||
exitRoutine <- true
|
||||
// Check if all peers are found.
|
||||
for _, exist := range exists {
|
||||
require.Equal(t, true, exist, "Peer with subnet doesn't exist")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AttSubnets(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
record func(t *testing.T) *enr.Record
|
||||
record func(localNode *enode.LocalNode) *enr.Record
|
||||
want []uint64
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "valid record",
|
||||
record: func(t *testing.T) *enr.Record {
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record: func(localNode *enode.LocalNode) *enr.Record {
|
||||
localNode = initializeAttSubnets(localNode)
|
||||
return localNode.Node().Record()
|
||||
},
|
||||
@@ -166,14 +199,7 @@ func Test_AttSubnets(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "too small subnet",
|
||||
record: func(t *testing.T) *enr.Record {
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record: func(localNode *enode.LocalNode) *enr.Record {
|
||||
entry := enr.WithEntry(attSubnetEnrKey, []byte{})
|
||||
localNode.Set(entry)
|
||||
return localNode.Node().Record()
|
||||
@@ -184,14 +210,7 @@ func Test_AttSubnets(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "half sized subnet",
|
||||
record: func(t *testing.T) *enr.Record {
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record: func(localNode *enode.LocalNode) *enr.Record {
|
||||
entry := enr.WithEntry(attSubnetEnrKey, make([]byte, 4))
|
||||
localNode.Set(entry)
|
||||
return localNode.Node().Record()
|
||||
@@ -202,14 +221,7 @@ func Test_AttSubnets(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "too large subnet",
|
||||
record: func(t *testing.T) *enr.Record {
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record: func(localNode *enode.LocalNode) *enr.Record {
|
||||
entry := enr.WithEntry(attSubnetEnrKey, make([]byte, byteCount(int(attestationSubnetCount))+1))
|
||||
localNode.Set(entry)
|
||||
return localNode.Node().Record()
|
||||
@@ -220,14 +232,7 @@ func Test_AttSubnets(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "very large subnet",
|
||||
record: func(t *testing.T) *enr.Record {
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record: func(localNode *enode.LocalNode) *enr.Record {
|
||||
entry := enr.WithEntry(attSubnetEnrKey, make([]byte, byteCount(int(attestationSubnetCount))+100))
|
||||
localNode.Set(entry)
|
||||
return localNode.Node().Record()
|
||||
@@ -238,14 +243,7 @@ func Test_AttSubnets(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "single subnet",
|
||||
record: func(t *testing.T) *enr.Record {
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record: func(localNode *enode.LocalNode) *enr.Record {
|
||||
bitV := bitfield.NewBitvector64()
|
||||
bitV.SetBitAt(0, true)
|
||||
entry := enr.WithEntry(attSubnetEnrKey, bitV.Bytes())
|
||||
@@ -257,17 +255,10 @@ func Test_AttSubnets(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "multiple subnets",
|
||||
record: func(t *testing.T) *enr.Record {
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record: func(localNode *enode.LocalNode) *enr.Record {
|
||||
bitV := bitfield.NewBitvector64()
|
||||
for i := uint64(0); i < bitV.Len(); i++ {
|
||||
// skip 2 subnets
|
||||
// Keep only odd subnets.
|
||||
if (i+1)%2 == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -285,14 +276,7 @@ func Test_AttSubnets(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all subnets",
|
||||
record: func(t *testing.T) *enr.Record {
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record: func(localNode *enode.LocalNode) *enr.Record {
|
||||
bitV := bitfield.NewBitvector64()
|
||||
for i := uint64(0); i < bitV.Len(); i++ {
|
||||
bitV.SetBitAt(i, true)
|
||||
@@ -309,16 +293,35 @@ func Test_AttSubnets(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := attSubnets(tt.record(t))
|
||||
db, err := enode.OpenDB("")
|
||||
assert.NoError(t, err)
|
||||
|
||||
priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
convertedKey, err := ecdsaprysm.ConvertFromInterfacePrivKey(priv)
|
||||
assert.NoError(t, err)
|
||||
|
||||
localNode := enode.NewLocalNode(db, convertedKey)
|
||||
record := tt.record(localNode)
|
||||
|
||||
got, err := attSubnets(record)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("syncSubnets() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if tt.wantErr {
|
||||
assert.ErrorContains(t, tt.errContains, err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("syncSubnets() got = %v, want %v", got, tt.want)
|
||||
|
||||
want := make(map[uint64]bool, len(tt.want))
|
||||
for _, subnet := range tt.want {
|
||||
want[subnet] = true
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("syncSubnets() got = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
@@ -39,6 +40,12 @@ var (
|
||||
})
|
||||
)
|
||||
|
||||
func setFeeRecipientIfBurnAddress(val *cache.TrackedValidator) {
|
||||
if val.FeeRecipient == primitives.ExecutionAddress([20]byte{}) && val.Index == 0 {
|
||||
val.FeeRecipient = primitives.ExecutionAddress(params.BeaconConfig().DefaultFeeRecipient)
|
||||
}
|
||||
}
|
||||
|
||||
// This returns the local execution payload of a given slot. The function has full awareness of pre and post merge.
|
||||
func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBeaconBlock, st state.BeaconState) (interfaces.ExecutionData, bool, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.getLocalPayload")
|
||||
@@ -62,6 +69,7 @@ func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBe
|
||||
if !tracked {
|
||||
logrus.WithFields(logFields).Warn("could not find tracked proposer index")
|
||||
}
|
||||
setFeeRecipientIfBurnAddress(&val)
|
||||
|
||||
var err error
|
||||
if ok && payloadId != [8]byte{} {
|
||||
|
||||
@@ -383,3 +383,16 @@ func TestServer_getTerminalBlockHashIfExists(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetFeeRecipientIfBurnAddress(t *testing.T) {
|
||||
val := &cache.TrackedValidator{Index: 1}
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.DefaultFeeRecipient = common.Address([20]byte{'a'})
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
require.NotEqual(t, common.Address(val.FeeRecipient), params.BeaconConfig().DefaultFeeRecipient)
|
||||
setFeeRecipientIfBurnAddress(val)
|
||||
require.NotEqual(t, common.Address(val.FeeRecipient), params.BeaconConfig().DefaultFeeRecipient)
|
||||
val.Index = 0
|
||||
setFeeRecipientIfBurnAddress(val)
|
||||
require.Equal(t, common.Address(val.FeeRecipient), params.BeaconConfig().DefaultFeeRecipient)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (w *p2pWorker) run(ctx context.Context) {
|
||||
|
||||
func (w *p2pWorker) handleBlocks(ctx context.Context, b batch) batch {
|
||||
cs := w.c.CurrentSlot()
|
||||
blobRetentionStart, err := sync.BlobsByRangeMinStartSlot(cs)
|
||||
blobRetentionStart, err := sync.BlobRPCMinValidSlot(cs)
|
||||
if err != nil {
|
||||
return b.withRetryableError(errors.Wrap(err, "configuration issue, could not compute minimum blob retention slot"))
|
||||
}
|
||||
|
||||
@@ -327,40 +327,3 @@ func TestTestcaseSetup_BlocksAndBlobs(t *testing.T) {
|
||||
require.Equal(t, true, found != nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripDenebSave(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cfg := params.BeaconConfig()
|
||||
repositionFutureEpochs(cfg)
|
||||
undo, err := params.SetActiveWithUndo(cfg)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, undo())
|
||||
}()
|
||||
parentRoot := [32]byte{}
|
||||
c := blobsTestCase{}
|
||||
chain, clock := defaultMockChain(t)
|
||||
c.chain = chain
|
||||
c.clock = clock
|
||||
oldest, err := slots.EpochStart(blobMinReqEpoch(c.chain.FinalizedCheckPoint.Epoch, slots.ToEpoch(c.clock.CurrentSlot())))
|
||||
require.NoError(t, err)
|
||||
maxBlobs := fieldparams.MaxBlobsPerBlock
|
||||
block, bsc := generateTestBlockWithSidecars(t, parentRoot, oldest, maxBlobs)
|
||||
require.Equal(t, len(block.Block.Body.BlobKzgCommitments), len(bsc))
|
||||
require.Equal(t, maxBlobs, len(bsc))
|
||||
for i := range bsc {
|
||||
require.DeepEqual(t, block.Block.Body.BlobKzgCommitments[i], bsc[i].KzgCommitment)
|
||||
}
|
||||
d := db.SetupDB(t)
|
||||
util.SaveBlock(t, ctx, d, block)
|
||||
root, err := block.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
dbBlock, err := d.Block(ctx, root)
|
||||
require.NoError(t, err)
|
||||
comms, err := dbBlock.Block().Body().BlobKzgCommitments()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, maxBlobs, len(comms))
|
||||
for i := range bsc {
|
||||
require.DeepEqual(t, comms[i], bsc[i].KzgCommitment)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,7 +478,7 @@ func (f *blocksFetcher) fetchBlobsFromPeer(ctx context.Context, bwb []blocks2.Bl
|
||||
if slots.ToEpoch(f.clock.CurrentSlot()) < params.BeaconConfig().DenebForkEpoch {
|
||||
return bwb, nil
|
||||
}
|
||||
blobWindowStart, err := prysmsync.BlobsByRangeMinStartSlot(f.clock.CurrentSlot())
|
||||
blobWindowStart, err := prysmsync.BlobRPCMinValidSlot(f.clock.CurrentSlot())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -123,10 +123,10 @@ func (s *Service) blobSidecarsByRangeRPCHandler(ctx context.Context, msg interfa
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlobsByRangeMinStartSlot returns the lowest slot that we should expect peers to respect as the
|
||||
// BlobRPCMinValidSlot returns the lowest slot that we should expect peers to respect as the
|
||||
// start slot in a BlobSidecarsByRange request. This can be used to validate incoming requests and
|
||||
// to avoid pestering peers with requests for blobs that are outside the retention window.
|
||||
func BlobsByRangeMinStartSlot(current primitives.Slot) (primitives.Slot, error) {
|
||||
func BlobRPCMinValidSlot(current primitives.Slot) (primitives.Slot, error) {
|
||||
// Avoid overflow if we're running on a config where deneb is set to far future epoch.
|
||||
if params.BeaconConfig().DenebForkEpoch == math.MaxUint64 {
|
||||
return primitives.Slot(math.MaxUint64), nil
|
||||
@@ -176,9 +176,9 @@ func validateBlobsByRange(r *pb.BlobSidecarsByRangeRequest, current primitives.S
|
||||
// [max(current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_FORK_EPOCH), current_epoch]
|
||||
// where current_epoch is defined by the current wall-clock time,
|
||||
// and clients MUST support serving requests of blobs on this range.
|
||||
minStartSlot, err := BlobsByRangeMinStartSlot(current)
|
||||
minStartSlot, err := BlobRPCMinValidSlot(current)
|
||||
if err != nil {
|
||||
return rangeParams{}, errors.Wrap(p2ptypes.ErrInvalidRequest, "BlobsByRangeMinStartSlot error")
|
||||
return rangeParams{}, errors.Wrap(p2ptypes.ErrInvalidRequest, "BlobRPCMinValidSlot error")
|
||||
}
|
||||
if rp.start > maxStart {
|
||||
return rangeParams{}, errors.Wrap(p2ptypes.ErrInvalidRequest, "start > maxStart")
|
||||
|
||||
@@ -178,7 +178,7 @@ func TestBlobsByRangeValidation(t *testing.T) {
|
||||
and clients MUST support serving requests of blobs on this range.
|
||||
*/
|
||||
defaultCurrent := denebSlot + 100 + minReqSlots
|
||||
defaultMinStart, err := BlobsByRangeMinStartSlot(defaultCurrent)
|
||||
defaultMinStart, err := BlobRPCMinValidSlot(defaultCurrent)
|
||||
require.NoError(t, err)
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -285,3 +285,67 @@ func TestBlobsByRangeValidation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlobRPCMinValidSlot(t *testing.T) {
|
||||
denebSlot, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch)
|
||||
require.NoError(t, err)
|
||||
cases := []struct {
|
||||
name string
|
||||
current func(t *testing.T) types.Slot
|
||||
expected types.Slot
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "before deneb",
|
||||
current: func(t *testing.T) types.Slot {
|
||||
st, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch - 1)
|
||||
// note: we no longer need to deal with deneb fork epoch being far future
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
},
|
||||
expected: denebSlot,
|
||||
},
|
||||
{
|
||||
name: "equal to deneb",
|
||||
current: func(t *testing.T) types.Slot {
|
||||
st, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch)
|
||||
// note: we no longer need to deal with deneb fork epoch being far future
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
},
|
||||
expected: denebSlot,
|
||||
},
|
||||
{
|
||||
name: "after deneb, before expiry starts",
|
||||
current: func(t *testing.T) types.Slot {
|
||||
st, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch + params.BeaconConfig().MinEpochsForBlobsSidecarsRequest)
|
||||
// note: we no longer need to deal with deneb fork epoch being far future
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
},
|
||||
expected: denebSlot,
|
||||
},
|
||||
{
|
||||
name: "expiry starts one epoch after deneb + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS",
|
||||
current: func(t *testing.T) types.Slot {
|
||||
st, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch + params.BeaconConfig().MinEpochsForBlobsSidecarsRequest + 1)
|
||||
// note: we no longer need to deal with deneb fork epoch being far future
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
},
|
||||
expected: denebSlot + params.BeaconConfig().SlotsPerEpoch,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
current := c.current(t)
|
||||
got, err := BlobRPCMinValidSlot(current)
|
||||
if c.err != nil {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,30 +13,12 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/types"
|
||||
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
func blobMinReqEpoch(finalized, current primitives.Epoch) primitives.Epoch {
|
||||
// max(finalized_epoch, current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_FORK_EPOCH)
|
||||
denebFork := params.BeaconConfig().DenebForkEpoch
|
||||
var reqWindow primitives.Epoch
|
||||
if current > params.BeaconConfig().MinEpochsForBlobsSidecarsRequest {
|
||||
reqWindow = current - params.BeaconConfig().MinEpochsForBlobsSidecarsRequest
|
||||
}
|
||||
if finalized >= reqWindow && finalized > denebFork {
|
||||
return finalized
|
||||
}
|
||||
if reqWindow >= finalized && reqWindow > denebFork {
|
||||
return reqWindow
|
||||
}
|
||||
return denebFork
|
||||
}
|
||||
|
||||
// blobSidecarByRootRPCHandler handles the /eth2/beacon_chain/req/blob_sidecars_by_root/1/ RPC request.
|
||||
// spec: https://github.com/ethereum/consensus-specs/blob/a7e45db9ac2b60a33e144444969ad3ac0aae3d4c/specs/deneb/p2p-interface.md#blobsidecarsbyroot-v1
|
||||
func (s *Service) blobSidecarByRootRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
|
||||
@@ -65,7 +47,13 @@ func (s *Service) blobSidecarByRootRPCHandler(ctx context.Context, msg interface
|
||||
if len(blobIdents) > batchSize {
|
||||
ticker = time.NewTicker(time.Second)
|
||||
}
|
||||
minReqEpoch := blobMinReqEpoch(s.cfg.chain.FinalizedCheckpt().Epoch, slots.ToEpoch(s.cfg.clock.CurrentSlot()))
|
||||
|
||||
// Compute the oldest slot we'll allow a peer to request, based on the current slot.
|
||||
cs := s.cfg.clock.CurrentSlot()
|
||||
minReqSlot, err := BlobRPCMinValidSlot(cs)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unexpected error computing min valid blob request slot, current_slot=%d", cs)
|
||||
}
|
||||
|
||||
for i := range blobIdents {
|
||||
if err := ctx.Err(); err != nil {
|
||||
@@ -95,12 +83,15 @@ func (s *Service) blobSidecarByRootRPCHandler(ctx context.Context, msg interface
|
||||
|
||||
// If any root in the request content references a block earlier than minimum_request_epoch,
|
||||
// peers MAY respond with error code 3: ResourceUnavailable or not include the blob in the response.
|
||||
if slots.ToEpoch(sc.Slot()) < minReqEpoch {
|
||||
// note: we are deviating from the spec to allow requests for blobs that are before minimum_request_epoch,
|
||||
// up to the beginning of the retention period.
|
||||
if sc.Slot() < minReqSlot {
|
||||
s.writeErrorResponseToStream(responseCodeResourceUnavailable, types.ErrBlobLTMinRequest.Error(), stream)
|
||||
log.WithError(types.ErrBlobLTMinRequest).
|
||||
Debugf("requested blob for block %#x before minimum_request_epoch", blobIdents[i].BlockRoot)
|
||||
return types.ErrBlobLTMinRequest
|
||||
}
|
||||
|
||||
SetStreamWriteDeadline(stream, defaultWriteDuration)
|
||||
if chunkErr := WriteBlobSidecarChunk(stream, s.cfg.chain, s.cfg.p2p.Encoding(), sc); chunkErr != nil {
|
||||
log.WithError(chunkErr).Debug("Could not send a chunked response")
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
func (c *blobsTestCase) defaultOldestSlotByRoot(t *testing.T) types.Slot {
|
||||
oldest, err := slots.EpochStart(blobMinReqEpoch(c.chain.FinalizedCheckPoint.Epoch, slots.ToEpoch(c.clock.CurrentSlot())))
|
||||
oldest, err := BlobRPCMinValidSlot(c.clock.CurrentSlot())
|
||||
require.NoError(t, err)
|
||||
return oldest
|
||||
}
|
||||
@@ -259,71 +259,3 @@ func TestBlobsByRootOK(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlobsByRootMinReqEpoch(t *testing.T) {
|
||||
winMin := params.BeaconConfig().MinEpochsForBlobsSidecarsRequest
|
||||
cases := []struct {
|
||||
name string
|
||||
finalized types.Epoch
|
||||
current types.Epoch
|
||||
deneb types.Epoch
|
||||
expected types.Epoch
|
||||
}{
|
||||
{
|
||||
name: "testnet genesis",
|
||||
deneb: 100,
|
||||
current: 0,
|
||||
finalized: 0,
|
||||
expected: 100,
|
||||
},
|
||||
{
|
||||
name: "underflow averted",
|
||||
deneb: 100,
|
||||
current: winMin - 1,
|
||||
finalized: 0,
|
||||
expected: 100,
|
||||
},
|
||||
{
|
||||
name: "underflow averted - finalized is higher",
|
||||
deneb: 100,
|
||||
current: winMin - 1,
|
||||
finalized: winMin - 2,
|
||||
expected: winMin - 2,
|
||||
},
|
||||
{
|
||||
name: "underflow averted - genesis at deneb",
|
||||
deneb: 0,
|
||||
current: winMin - 1,
|
||||
finalized: 0,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "max is finalized",
|
||||
deneb: 100,
|
||||
current: 99 + winMin,
|
||||
finalized: 101,
|
||||
expected: 101,
|
||||
},
|
||||
{
|
||||
name: "reqWindow > finalized, reqWindow < deneb",
|
||||
deneb: 100,
|
||||
current: 99 + winMin,
|
||||
finalized: 98,
|
||||
expected: 100,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
repositionFutureEpochs(cfg)
|
||||
cfg.DenebForkEpoch = c.deneb
|
||||
undo, err := params.SetActiveWithUndo(cfg)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, undo())
|
||||
}()
|
||||
ep := blobMinReqEpoch(c.finalized, c.current)
|
||||
require.Equal(t, c.expected, ep)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ const rangeLimit uint64 = 1024
|
||||
const seenBlockSize = 1000
|
||||
const seenBlobSize = seenBlockSize * 4 // Each block can have max 4 blobs. Worst case 164kB for cache.
|
||||
const seenUnaggregatedAttSize = 20000
|
||||
const seenAggregatedAttSize = 1024
|
||||
const seenAggregatedAttSize = 16384
|
||||
const seenSyncMsgSize = 1000 // Maximum of 512 sync committee members, 1000 is a safe amount.
|
||||
const seenSyncContributionSize = 512 // Maximum of SYNC_COMMITTEE_SIZE as specified by the spec.
|
||||
const seenExitSize = 100
|
||||
|
||||
@@ -9,6 +9,7 @@ go_library(
|
||||
"fake.go",
|
||||
"initializer.go",
|
||||
"interface.go",
|
||||
"metrics.go",
|
||||
"mock.go",
|
||||
"result.go",
|
||||
],
|
||||
@@ -35,6 +36,8 @@ go_library(
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_hashicorp_golang_lru//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -190,12 +190,15 @@ func (bv *ROBlobVerifier) ValidProposerSignature(ctx context.Context) (err error
|
||||
// First check if there is a cached verification that can be reused.
|
||||
seen, err := bv.sc.SignatureVerified(sd)
|
||||
if seen {
|
||||
blobVerificationProposerSignatureCache.WithLabelValues("hit-valid").Inc()
|
||||
if err != nil {
|
||||
log.WithFields(logging.BlobFields(bv.blob)).WithError(err).Debug("reusing failed proposer signature validation from cache")
|
||||
blobVerificationProposerSignatureCache.WithLabelValues("hit-invalid").Inc()
|
||||
return ErrInvalidProposerSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
blobVerificationProposerSignatureCache.WithLabelValues("miss").Inc()
|
||||
|
||||
// Retrieve the parent state to fallback to full verification.
|
||||
parent, err := bv.parentState(ctx)
|
||||
|
||||
16
beacon-chain/verification/metrics.go
Normal file
16
beacon-chain/verification/metrics.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package verification
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var (
|
||||
blobVerificationProposerSignatureCache = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "blob_verification_proposer_signature_cache",
|
||||
Help: "BlobSidecar proposer signature cache result.",
|
||||
},
|
||||
[]string{"result"},
|
||||
)
|
||||
)
|
||||
@@ -49,6 +49,82 @@ func TestProposerSettingsLoader(t *testing.T) {
|
||||
validatorRegistrationEnabled bool
|
||||
skipDBSavedCheck bool
|
||||
}{
|
||||
{
|
||||
name: "graffiti in db without fee recipient",
|
||||
args: args{
|
||||
proposerSettingsFlagValues: &proposerSettingsFlag{
|
||||
dir: "",
|
||||
url: "",
|
||||
defaultfee: "",
|
||||
},
|
||||
},
|
||||
want: func() *proposer.Settings {
|
||||
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
|
||||
require.NoError(t, err)
|
||||
return &proposer.Settings{
|
||||
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
|
||||
bytesutil.ToBytes48(key1): {
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "specific graffiti",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
withdb: func(db iface.ValidatorDB) error {
|
||||
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
|
||||
require.NoError(t, err)
|
||||
settings := &proposer.Settings{
|
||||
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
|
||||
bytesutil.ToBytes48(key1): {
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "specific graffiti",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return db.SaveProposerSettings(context.Background(), settings)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "graffiti from file",
|
||||
args: args{
|
||||
proposerSettingsFlagValues: &proposerSettingsFlag{
|
||||
dir: "./testdata/good-graffiti-settings.json",
|
||||
url: "",
|
||||
defaultfee: "",
|
||||
},
|
||||
},
|
||||
want: func() *proposer.Settings {
|
||||
key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a")
|
||||
require.NoError(t, err)
|
||||
return &proposer.Settings{
|
||||
ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{
|
||||
bytesutil.ToBytes48(key1): {
|
||||
FeeRecipientConfig: &proposer.FeeRecipientConfig{
|
||||
FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"),
|
||||
},
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "some graffiti",
|
||||
},
|
||||
BuilderConfig: &proposer.BuilderConfig{
|
||||
Enabled: true,
|
||||
GasLimit: validator.Uint64(30000000),
|
||||
},
|
||||
},
|
||||
},
|
||||
DefaultConfig: &proposer.Option{
|
||||
FeeRecipientConfig: &proposer.FeeRecipientConfig{
|
||||
FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"),
|
||||
},
|
||||
BuilderConfig: &proposer.BuilderConfig{
|
||||
Enabled: true,
|
||||
GasLimit: validator.Uint64(40000000),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "db settings override file settings if file default config is missing",
|
||||
args: args{
|
||||
@@ -875,6 +951,8 @@ func TestProposerSettingsLoader(t *testing.T) {
|
||||
if tt.wantErr != "" {
|
||||
require.ErrorContains(t, tt.wantErr, err)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if tt.wantLog != "" {
|
||||
assert.LogsContain(t, hook,
|
||||
|
||||
19
config/proposer/loader/testdata/good-graffiti-settings.json
vendored
Normal file
19
config/proposer/loader/testdata/good-graffiti-settings.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"proposer_config": {
|
||||
"0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": {
|
||||
"fee_recipient": "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3",
|
||||
"graffiti": "some graffiti",
|
||||
"builder": {
|
||||
"enabled": true,
|
||||
"gas_limit": "30000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default_config": {
|
||||
"fee_recipient": "0x6e35733c5af9B61374A128e6F85f553aF09ff89A",
|
||||
"builder": {
|
||||
"enabled": true,
|
||||
"gas_limit": 40000000
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,6 @@ func SettingFromConsensus(ps *validatorpb.ProposerSettingsPayload) (*Settings, e
|
||||
if ps.ProposerConfig != nil && len(ps.ProposerConfig) != 0 {
|
||||
settings.ProposeConfig = make(map[[fieldparams.BLSPubkeyLength]byte]*Option)
|
||||
for key, optionPayload := range ps.ProposerConfig {
|
||||
if optionPayload.FeeRecipient == "" {
|
||||
continue
|
||||
}
|
||||
decodedKey, err := hexutil.Decode(key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("cannot decode public key %s", key))
|
||||
@@ -29,13 +26,15 @@ func SettingFromConsensus(ps *validatorpb.ProposerSettingsPayload) (*Settings, e
|
||||
if len(decodedKey) != fieldparams.BLSPubkeyLength {
|
||||
return nil, fmt.Errorf("%v is not a bls public key", key)
|
||||
}
|
||||
if err := verifyOption(key, optionPayload); err != nil {
|
||||
return nil, err
|
||||
p := &Option{}
|
||||
if optionPayload.Graffiti != nil {
|
||||
p.GraffitiConfig = &GraffitiConfig{*optionPayload.Graffiti}
|
||||
}
|
||||
p := &Option{
|
||||
FeeRecipientConfig: &FeeRecipientConfig{
|
||||
FeeRecipient: common.HexToAddress(optionPayload.FeeRecipient),
|
||||
},
|
||||
if optionPayload.FeeRecipient != "" {
|
||||
if err := verifyOption(key, optionPayload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.FeeRecipientConfig = &FeeRecipientConfig{FeeRecipient: common.HexToAddress(optionPayload.FeeRecipient)}
|
||||
}
|
||||
if optionPayload.Builder != nil {
|
||||
p.BuilderConfig = BuilderConfigFromConsensus(optionPayload.Builder)
|
||||
@@ -141,10 +140,16 @@ type FeeRecipientConfig struct {
|
||||
FeeRecipient common.Address
|
||||
}
|
||||
|
||||
// GraffitiConfig is a prysm internal representation to see if the graffiti was set.
|
||||
type GraffitiConfig struct {
|
||||
Graffiti string
|
||||
}
|
||||
|
||||
// Option is a Prysm internal representation of the ProposerOptionPayload on the validator client in bytes format instead of hex.
|
||||
type Option struct {
|
||||
FeeRecipientConfig *FeeRecipientConfig
|
||||
BuilderConfig *BuilderConfig
|
||||
GraffitiConfig *GraffitiConfig
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of proposer option
|
||||
@@ -159,6 +164,9 @@ func (po *Option) Clone() *Option {
|
||||
if po.BuilderConfig != nil {
|
||||
p.BuilderConfig = po.BuilderConfig.Clone()
|
||||
}
|
||||
if po.GraffitiConfig != nil {
|
||||
p.GraffitiConfig = po.GraffitiConfig.Clone()
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -173,6 +181,9 @@ func (po *Option) ToConsensus() *validatorpb.ProposerOptionPayload {
|
||||
if po.BuilderConfig != nil {
|
||||
p.Builder = po.BuilderConfig.ToConsensus()
|
||||
}
|
||||
if po.GraffitiConfig != nil {
|
||||
p.Graffiti = &po.GraffitiConfig.Graffiti
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -222,6 +233,14 @@ func (bc *BuilderConfig) Clone() *BuilderConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of graffiti config
|
||||
func (gc *GraffitiConfig) Clone() *GraffitiConfig {
|
||||
if gc == nil {
|
||||
return nil
|
||||
}
|
||||
return &GraffitiConfig{gc.Graffiti}
|
||||
}
|
||||
|
||||
// ToConsensus converts Builder Config to the protobuf object
|
||||
func (bc *BuilderConfig) ToConsensus() *validatorpb.BuilderConfig {
|
||||
if bc == nil {
|
||||
|
||||
@@ -76,26 +76,14 @@ func Test_Proposer_Setting_Cloning(t *testing.T) {
|
||||
require.Equal(t, option.FeeRecipientConfig.FeeRecipient.Hex(), potion.FeeRecipient)
|
||||
require.Equal(t, settings.DefaultConfig.FeeRecipientConfig.FeeRecipient.Hex(), payload.DefaultConfig.FeeRecipient)
|
||||
require.Equal(t, settings.DefaultConfig.BuilderConfig.Enabled, payload.DefaultConfig.Builder.Enabled)
|
||||
potion.FeeRecipient = ""
|
||||
potion.FeeRecipient = fee
|
||||
newSettings, err := SettingFromConsensus(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
// when converting to settings if a fee recipient is empty string then it will be skipped
|
||||
noption, ok := newSettings.ProposeConfig[bytesutil.ToBytes48(key1)]
|
||||
require.Equal(t, false, ok)
|
||||
require.Equal(t, true, noption == nil)
|
||||
require.DeepEqual(t, newSettings.DefaultConfig, settings.DefaultConfig)
|
||||
|
||||
// if fee recipient is set it will not skip
|
||||
potion.FeeRecipient = fee
|
||||
newSettings, err = SettingFromConsensus(payload)
|
||||
require.NoError(t, err)
|
||||
noption, ok = newSettings.ProposeConfig[bytesutil.ToBytes48(key1)]
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, option.FeeRecipientConfig.FeeRecipient.Hex(), noption.FeeRecipientConfig.FeeRecipient.Hex())
|
||||
require.Equal(t, option.BuilderConfig.GasLimit, option.BuilderConfig.GasLimit)
|
||||
require.Equal(t, option.BuilderConfig.Enabled, option.BuilderConfig.Enabled)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ proto_library(
|
||||
"@com_google_protobuf//:any_proto",
|
||||
"@com_google_protobuf//:descriptor_proto",
|
||||
"@com_google_protobuf//:empty_proto",
|
||||
"@com_google_protobuf//:wrappers_proto",
|
||||
"@com_google_protobuf//:timestamp_proto",
|
||||
"@googleapis//google/api:annotations_proto",
|
||||
],
|
||||
@@ -53,6 +54,7 @@ go_proto_library(
|
||||
"@googleapis//google/api:annotations_go_proto",
|
||||
"@io_bazel_rules_go//proto/wkt:descriptor_go_proto",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@org_golang_google_protobuf//types/known/wrapperspb:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:timestamp_go_proto",
|
||||
"@org_golang_google_protobuf//reflect/protoreflect:go_default_library",
|
||||
"@org_golang_google_protobuf//runtime/protoimpl:go_default_library",
|
||||
@@ -78,6 +80,7 @@ go_proto_library(
|
||||
"@googleapis//google/api:annotations_go_proto",
|
||||
"@io_bazel_rules_go//proto/wkt:descriptor_go_proto",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@io_bazel_rules_go//proto/wkt:wrappers_go_proto",
|
||||
"@io_bazel_rules_go//proto/wkt:timestamp_go_proto",
|
||||
],
|
||||
)
|
||||
|
||||
107
proto/prysm/v1alpha1/validator-client/keymanager.pb.go
generated
107
proto/prysm/v1alpha1/validator-client/keymanager.pb.go
generated
@@ -16,6 +16,7 @@ import (
|
||||
v1alpha1 "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
_ "google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -462,6 +463,7 @@ type ProposerOptionPayload struct {
|
||||
|
||||
FeeRecipient string `protobuf:"bytes,1,opt,name=fee_recipient,json=feeRecipient,proto3" json:"fee_recipient,omitempty"`
|
||||
Builder *BuilderConfig `protobuf:"bytes,2,opt,name=builder,proto3" json:"builder,omitempty"`
|
||||
Graffiti *string `protobuf:"bytes,3,opt,name=graffiti,proto3,oneof" json:"graffiti,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ProposerOptionPayload) Reset() {
|
||||
@@ -510,6 +512,13 @@ func (x *ProposerOptionPayload) GetBuilder() *BuilderConfig {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProposerOptionPayload) GetGraffiti() string {
|
||||
if x != nil && x.Graffiti != nil {
|
||||
return *x.Graffiti
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type BuilderConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -636,7 +645,9 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte
|
||||
0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x6b, 0x65, 0x79, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65,
|
||||
0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x1a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65,
|
||||
0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
|
||||
0x65, 0x74, 0x68, 0x2f, 0x65, 0x78, 0x74, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79,
|
||||
0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x61, 0x74, 0x74, 0x65,
|
||||
@@ -771,7 +782,7 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte
|
||||
0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d,
|
||||
0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x44, 0x45, 0x4e, 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x45, 0x44, 0x10, 0x03, 0x22, 0x85, 0x01, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73,
|
||||
0x4c, 0x45, 0x44, 0x10, 0x03, 0x22, 0xb3, 0x01, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73,
|
||||
0x65, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12,
|
||||
0x23, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x65, 0x65, 0x52, 0x65, 0x63, 0x69, 0x70,
|
||||
@@ -779,54 +790,57 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte
|
||||
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d,
|
||||
0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x22, 0xa6, 0x01,
|
||||
0x0a, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x63, 0x0a, 0x09, 0x67, 0x61, 0x73,
|
||||
0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x46, 0x82, 0xb5,
|
||||
0x18, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79,
|
||||
0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d,
|
||||
0x2f, 0x76, 0x35, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79,
|
||||
0x70, 0x65, 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x55, 0x69,
|
||||
0x6e, 0x74, 0x36, 0x34, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16,
|
||||
0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06,
|
||||
0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x22, 0xe7, 0x02, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x70, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a,
|
||||
0x08, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48,
|
||||
0x00, 0x52, 0x08, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x88, 0x01, 0x01, 0x42, 0x0b,
|
||||
0x0a, 0x09, 0x5f, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x22, 0xa6, 0x01, 0x0a, 0x0d,
|
||||
0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
|
||||
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x63, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c,
|
||||
0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x46, 0x82, 0xb5, 0x18, 0x42,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d,
|
||||
0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76,
|
||||
0x35, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65,
|
||||
0x73, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x55, 0x69, 0x6e, 0x74,
|
||||
0x36, 0x34, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65,
|
||||
0x6c, 0x61, 0x79, 0x73, 0x22, 0xe7, 0x02, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65,
|
||||
0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
|
||||
0x12, 0x74, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x65, 0x74, 0x68, 0x65,
|
||||
0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f,
|
||||
0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f,
|
||||
0x61, 0x64, 0x12, 0x74, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x63,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x65, 0x74,
|
||||
0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72,
|
||||
0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x72, 0x6f,
|
||||
0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x61, 0x79,
|
||||
0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5c, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61,
|
||||
0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x35, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69,
|
||||
0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76,
|
||||
0x32, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x78, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
|
||||
0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
|
||||
0x4b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35,
|
||||
0x61, 0x64, 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5c, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c,
|
||||
0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35,
|
||||
0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61,
|
||||
0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e,
|
||||
0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61,
|
||||
0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
|
||||
0x42, 0xce, 0x01, 0x0a, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75,
|
||||
0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x42, 0x0f, 0x4b, 0x65, 0x79, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x53, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63,
|
||||
0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
|
||||
0x61, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2d, 0x63, 0x6c, 0x69,
|
||||
0x65, 0x6e, 0x74, 0x3b, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x70, 0x62, 0xaa,
|
||||
0x02, 0x1e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64,
|
||||
0x61, 0x74, 0x6f, 0x72, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x56, 0x32,
|
||||
0xca, 0x02, 0x1e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x56, 0x61, 0x6c, 0x69,
|
||||
0x64, 0x61, 0x74, 0x6f, 0x72, 0x5c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5c, 0x56,
|
||||
0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x1a, 0x78, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x4b, 0x0a,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x65,
|
||||
0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f,
|
||||
0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x72,
|
||||
0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c,
|
||||
0x6f, 0x61, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0xce,
|
||||
0x01, 0x0a, 0x22, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e,
|
||||
0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x73, 0x2e, 0x76, 0x32, 0x42, 0x0f, 0x4b, 0x65, 0x79, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x53, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61,
|
||||
0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
|
||||
0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e,
|
||||
0x74, 0x3b, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x70, 0x62, 0xaa, 0x02, 0x1e,
|
||||
0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
|
||||
0x6f, 0x72, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x56, 0x32, 0xca, 0x02,
|
||||
0x1e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61,
|
||||
0x74, 0x6f, 0x72, 0x5c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5c, 0x56, 0x32, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -979,6 +993,7 @@ func file_proto_prysm_v1alpha1_validator_client_keymanager_proto_init() {
|
||||
(*SignRequest_BlockDeneb)(nil),
|
||||
(*SignRequest_BlindedBlockDeneb)(nil),
|
||||
}
|
||||
file_proto_prysm_v1alpha1_validator_client_keymanager_proto_msgTypes[2].OneofWrappers = []interface{}{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
syntax = "proto3";
|
||||
package ethereum.validator.accounts.v2;
|
||||
|
||||
import "google/protobuf/wrappers.proto";
|
||||
import "proto/eth/ext/options.proto";
|
||||
import "proto/prysm/v1alpha1/attestation.proto";
|
||||
import "proto/prysm/v1alpha1/beacon_block.proto";
|
||||
@@ -87,6 +88,7 @@ message SignResponse {
|
||||
message ProposerOptionPayload {
|
||||
string fee_recipient = 1;
|
||||
BuilderConfig builder = 2;
|
||||
optional string graffiti = 3;
|
||||
}
|
||||
|
||||
// BuilderConfig is a property of ProposerOptionPayload
|
||||
|
||||
@@ -12,6 +12,7 @@ go_library(
|
||||
deps = [
|
||||
"//api/client/beacon:go_default_library",
|
||||
"//api/client/event:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/proposer:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/api/client/beacon"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/client/event"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/proposer"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
@@ -91,6 +92,7 @@ func (_ *Wallet) InitializeKeymanager(_ context.Context, _ iface.InitKeymanagerC
|
||||
|
||||
type Validator struct {
|
||||
Km keymanager.IKeymanager
|
||||
graffiti string
|
||||
proposerSettings *proposer.Settings
|
||||
}
|
||||
|
||||
@@ -215,6 +217,23 @@ func (m *Validator) SetProposerSettings(_ context.Context, settings *proposer.Se
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGraffiti for mocking
|
||||
func (m *Validator) GetGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte) ([]byte, error) {
|
||||
return []byte(m.graffiti), nil
|
||||
}
|
||||
|
||||
// SetGraffiti for mocking
|
||||
func (m *Validator) SetGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error {
|
||||
m.graffiti = string(graffiti)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGraffiti for mocking
|
||||
func (m *Validator) DeleteGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte) error {
|
||||
m.graffiti = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Validator) StartEventStream(_ context.Context, _ []string, _ chan<- *event.Event) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
@@ -60,10 +60,13 @@ type Validator interface {
|
||||
PushProposerSettings(ctx context.Context, km keymanager.IKeymanager, slot primitives.Slot, deadline time.Time) error
|
||||
SignValidatorRegistrationRequest(ctx context.Context, signer SigningFunc, newValidatorRegistration *ethpb.ValidatorRegistrationV1) (*ethpb.SignedValidatorRegistrationV1, error)
|
||||
StartEventStream(ctx context.Context, topics []string, eventsChan chan<- *event.Event)
|
||||
EventStreamIsRunning() bool
|
||||
ProcessEvent(event *event.Event)
|
||||
ProposerSettings() *proposer.Settings
|
||||
SetProposerSettings(context.Context, *proposer.Settings) error
|
||||
EventStreamIsRunning() bool
|
||||
GetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error)
|
||||
SetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error
|
||||
DeleteGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) error
|
||||
HealthTracker() *beacon.NodeHealthTracker
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/async"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/proposer"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
@@ -67,7 +69,7 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK
|
||||
return
|
||||
}
|
||||
|
||||
g, err := v.getGraffiti(ctx, pubKey)
|
||||
g, err := v.GetGraffiti(ctx, pubKey)
|
||||
if err != nil {
|
||||
// Graffiti is not a critical enough to fail block production and cause
|
||||
// validator to miss block reward. When failed, validator should continue
|
||||
@@ -385,9 +387,25 @@ func signVoluntaryExit(
|
||||
return sig.Marshal(), nil
|
||||
}
|
||||
|
||||
// Gets the graffiti from cli or file for the validator public key.
|
||||
func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) {
|
||||
// When specified, default graffiti from the command line takes the first priority.
|
||||
// GetGraffiti gets the graffiti from cli or file for the validator public key.
|
||||
func (v *validator) GetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) {
|
||||
if v.proposerSettings != nil {
|
||||
// Check proposer settings for specific key first
|
||||
if v.proposerSettings.ProposeConfig != nil {
|
||||
option, ok := v.proposerSettings.ProposeConfig[pubKey]
|
||||
if ok && option.GraffitiConfig != nil {
|
||||
return []byte(option.GraffitiConfig.Graffiti), nil
|
||||
}
|
||||
}
|
||||
// Check proposer settings for default settings second
|
||||
if v.proposerSettings.DefaultConfig != nil {
|
||||
if v.proposerSettings.DefaultConfig.GraffitiConfig != nil {
|
||||
return []byte(v.proposerSettings.DefaultConfig.GraffitiConfig.Graffiti), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When specified, use default graffiti from the command line.
|
||||
if len(v.graffiti) != 0 {
|
||||
return bytesutil.PadTo(v.graffiti, 32), nil
|
||||
}
|
||||
@@ -396,7 +414,7 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk
|
||||
return nil, errors.New("graffitiStruct can't be nil")
|
||||
}
|
||||
|
||||
// When specified, individual validator specified graffiti takes the second priority.
|
||||
// When specified, individual validator specified graffiti takes the third priority.
|
||||
idx, err := v.validatorClient.ValidatorIndex(ctx, ðpb.ValidatorIndexRequest{PublicKey: pubKey[:]})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -406,7 +424,7 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk
|
||||
return bytesutil.PadTo([]byte(g), 32), nil
|
||||
}
|
||||
|
||||
// When specified, a graffiti from the ordered list in the file take third priority.
|
||||
// When specified, a graffiti from the ordered list in the file take fourth priority.
|
||||
if v.graffitiOrderedIndex < uint64(len(v.graffitiStruct.Ordered)) {
|
||||
graffiti := v.graffitiStruct.Ordered[v.graffitiOrderedIndex]
|
||||
v.graffitiOrderedIndex = v.graffitiOrderedIndex + 1
|
||||
@@ -417,7 +435,7 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk
|
||||
return bytesutil.PadTo([]byte(graffiti), 32), nil
|
||||
}
|
||||
|
||||
// When specified, a graffiti from the random list in the file take fourth priority.
|
||||
// When specified, a graffiti from the random list in the file take Fifth priority.
|
||||
if len(v.graffitiStruct.Random) != 0 {
|
||||
r := rand.NewGenerator()
|
||||
r.Seed(time.Now().Unix())
|
||||
@@ -433,6 +451,44 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubk
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (v *validator) SetGraffiti(ctx context.Context, pubkey [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error {
|
||||
if graffiti == nil {
|
||||
return nil
|
||||
}
|
||||
settings := &proposer.Settings{}
|
||||
if v.proposerSettings != nil {
|
||||
settings = v.proposerSettings.Clone()
|
||||
}
|
||||
if settings.ProposeConfig == nil {
|
||||
settings.ProposeConfig = map[[48]byte]*proposer.Option{pubkey: {GraffitiConfig: &proposer.GraffitiConfig{Graffiti: string(graffiti)}}}
|
||||
return v.SetProposerSettings(ctx, settings)
|
||||
}
|
||||
option, ok := settings.ProposeConfig[pubkey]
|
||||
if !ok || option == nil {
|
||||
settings.ProposeConfig[pubkey] = &proposer.Option{GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: string(graffiti),
|
||||
}}
|
||||
} else {
|
||||
option.GraffitiConfig = &proposer.GraffitiConfig{
|
||||
Graffiti: string(graffiti),
|
||||
}
|
||||
}
|
||||
return v.SetProposerSettings(ctx, settings) // save the proposer settings
|
||||
}
|
||||
|
||||
func (v *validator) DeleteGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) error {
|
||||
if v.proposerSettings == nil || v.proposerSettings.ProposeConfig == nil {
|
||||
return errors.New("attempted to delete graffiti without proposer settings, graffiti will default to flag options")
|
||||
}
|
||||
ps := v.proposerSettings.Clone()
|
||||
option, ok := ps.ProposeConfig[pubKey]
|
||||
if !ok || option == nil {
|
||||
return fmt.Errorf("graffiti not found in proposer settings for pubkey:%s", hexutil.Encode(pubKey[:]))
|
||||
}
|
||||
option.GraffitiConfig = nil
|
||||
return v.SetProposerSettings(ctx, ps) // save the proposer settings
|
||||
}
|
||||
|
||||
func blockLogFields(pubKey [fieldparams.BLSPubkeyLength]byte, blk interfaces.ReadOnlyBeaconBlock, sig []byte) logrus.Fields {
|
||||
fields := logrus.Fields{
|
||||
"proposerPublicKey": fmt.Sprintf("%#x", pubKey),
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
|
||||
lruwrpr "github.com/prysmaticlabs/prysm/v5/cache/lru"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/proposer"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
blocktest "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
@@ -955,6 +957,13 @@ func TestGetGraffiti_Ok(t *testing.T) {
|
||||
validatorClient: validatormock.NewMockValidatorClient(ctrl),
|
||||
}
|
||||
pubKey := [fieldparams.BLSPubkeyLength]byte{'a'}
|
||||
config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option)
|
||||
config[pubKey] = &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "specific graffiti",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
v *validator
|
||||
@@ -1014,16 +1023,52 @@ func TestGetGraffiti_Ok(t *testing.T) {
|
||||
},
|
||||
want: []byte{},
|
||||
},
|
||||
{name: "graffiti from proposer settings for specific pubkey",
|
||||
v: &validator{
|
||||
validatorClient: m.validatorClient,
|
||||
proposerSettings: &proposer.Settings{
|
||||
ProposeConfig: config,
|
||||
},
|
||||
},
|
||||
want: []byte("specific graffiti"),
|
||||
},
|
||||
{name: "graffiti from proposer settings default config",
|
||||
v: &validator{
|
||||
validatorClient: m.validatorClient,
|
||||
proposerSettings: &proposer.Settings{
|
||||
DefaultConfig: &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "default graffiti",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []byte("default graffiti"),
|
||||
},
|
||||
{name: "graffiti from proposer settings , specific pubkey overrides default config",
|
||||
v: &validator{
|
||||
validatorClient: m.validatorClient,
|
||||
proposerSettings: &proposer.Settings{
|
||||
ProposeConfig: config,
|
||||
DefaultConfig: &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "default graffiti",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []byte("specific graffiti"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if !strings.Contains(tt.name, "use default cli graffiti") {
|
||||
if !strings.Contains(tt.name, "use default cli graffiti") && tt.v.proposerSettings == nil {
|
||||
m.validatorClient.EXPECT().
|
||||
ValidatorIndex(gomock.Any(), ðpb.ValidatorIndexRequest{PublicKey: pubKey[:]}).
|
||||
Return(ðpb.ValidatorIndexResponse{Index: 2}, nil)
|
||||
}
|
||||
got, err := tt.v.getGraffiti(context.Background(), pubKey)
|
||||
got, err := tt.v.GetGraffiti(context.Background(), pubKey)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, tt.want, got)
|
||||
})
|
||||
@@ -1053,10 +1098,165 @@ func TestGetGraffitiOrdered_Ok(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, want := range [][]byte{bytesutil.PadTo([]byte{'a'}, 32), bytesutil.PadTo([]byte{'b'}, 32), bytesutil.PadTo([]byte{'c'}, 32), bytesutil.PadTo([]byte{'d'}, 32), bytesutil.PadTo([]byte{'d'}, 32)} {
|
||||
got, err := v.getGraffiti(context.Background(), pubKey)
|
||||
got, err := v.GetGraffiti(context.Background(), pubKey)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validator_DeleteGraffiti(t *testing.T) {
|
||||
pubKey := [fieldparams.BLSPubkeyLength]byte{'a'}
|
||||
tests := []struct {
|
||||
name string
|
||||
proposerSettings *proposer.Settings
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "delete existing graffiti ok",
|
||||
proposerSettings: &proposer.Settings{
|
||||
ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option {
|
||||
config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option)
|
||||
config[pubKey] = &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "specific graffiti",
|
||||
},
|
||||
}
|
||||
return config
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete with proposer settings but only default configs",
|
||||
proposerSettings: &proposer.Settings{
|
||||
DefaultConfig: &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "default graffiti",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: "attempted to delete graffiti without proposer settings, graffiti will default to flag options",
|
||||
},
|
||||
{
|
||||
name: "delete with proposer settings but without the specific public key setting",
|
||||
proposerSettings: &proposer.Settings{
|
||||
ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option {
|
||||
config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option)
|
||||
pk := make([]byte, fieldparams.BLSPubkeyLength)
|
||||
config[bytesutil.ToBytes48(pk)] = &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "specific graffiti",
|
||||
},
|
||||
}
|
||||
return config
|
||||
}(),
|
||||
},
|
||||
wantErr: fmt.Sprintf("graffiti not found in proposer settings for pubkey:%s", hexutil.Encode(pubKey[:])),
|
||||
},
|
||||
{
|
||||
name: "delete without proposer settings",
|
||||
wantErr: "attempted to delete graffiti without proposer settings, graffiti will default to flag options",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &validator{
|
||||
db: testing2.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{pubKey}, false),
|
||||
proposerSettings: tt.proposerSettings,
|
||||
}
|
||||
err := v.DeleteGraffiti(context.Background(), pubKey)
|
||||
if tt.wantErr != "" {
|
||||
require.ErrorContains(t, tt.wantErr, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, v.proposerSettings.ProposeConfig[pubKey].GraffitiConfig == nil, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validator_SetGraffiti(t *testing.T) {
|
||||
pubKey := [fieldparams.BLSPubkeyLength]byte{'a'}
|
||||
tests := []struct {
|
||||
name string
|
||||
graffiti string
|
||||
proposerSettings *proposer.Settings
|
||||
wantProposerSettings *proposer.Settings
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "setting existing graffiti ok",
|
||||
graffiti: "new graffiti",
|
||||
proposerSettings: &proposer.Settings{
|
||||
ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option {
|
||||
config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option)
|
||||
config[pubKey] = &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "specific graffiti",
|
||||
},
|
||||
}
|
||||
return config
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set with proposer settings but only default configs",
|
||||
proposerSettings: &proposer.Settings{
|
||||
DefaultConfig: &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "default graffiti",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set with proposer settings but without the specific public key setting",
|
||||
proposerSettings: &proposer.Settings{
|
||||
ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option {
|
||||
config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option)
|
||||
pk := make([]byte, fieldparams.BLSPubkeyLength)
|
||||
config[bytesutil.ToBytes48(pk)] = &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "specific graffiti",
|
||||
},
|
||||
}
|
||||
return config
|
||||
}(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set without proposer settings",
|
||||
graffiti: "specific graffiti",
|
||||
wantProposerSettings: func() *proposer.Settings {
|
||||
config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option)
|
||||
config[pubKey] = &proposer.Option{
|
||||
GraffitiConfig: &proposer.GraffitiConfig{
|
||||
Graffiti: "specific graffiti",
|
||||
},
|
||||
}
|
||||
return &proposer.Settings{ProposeConfig: config}
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := &validator{
|
||||
db: testing2.SetupDB(t, [][fieldparams.BLSPubkeyLength]byte{pubKey}, false),
|
||||
proposerSettings: tt.proposerSettings,
|
||||
}
|
||||
err := v.SetGraffiti(context.Background(), pubKey, []byte(tt.graffiti))
|
||||
if tt.wantErr != "" {
|
||||
require.ErrorContains(t, tt.wantErr, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
if tt.wantProposerSettings != nil {
|
||||
require.DeepEqual(t, tt.wantProposerSettings, v.proposerSettings)
|
||||
} else {
|
||||
require.Equal(t, v.proposerSettings.ProposeConfig[pubKey].GraffitiConfig.Graffiti, tt.graffiti)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,3 +358,24 @@ func (v *ValidatorService) GenesisInfo(ctx context.Context) (*ethpb.Genesis, err
|
||||
nc := ethpb.NewNodeClient(v.conn.GetGrpcClientConn())
|
||||
return nc.GetGenesis(ctx, &emptypb.Empty{})
|
||||
}
|
||||
|
||||
func (v *ValidatorService) GetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) ([]byte, error) {
|
||||
if v.validator == nil {
|
||||
return nil, errors.New("validator is unavailable")
|
||||
}
|
||||
return v.validator.GetGraffiti(ctx, pubKey)
|
||||
}
|
||||
|
||||
func (v *ValidatorService) SetGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error {
|
||||
if v.validator == nil {
|
||||
return errors.New("validator is unavailable")
|
||||
}
|
||||
return v.validator.SetGraffiti(ctx, pubKey, graffiti)
|
||||
}
|
||||
|
||||
func (v *ValidatorService) DeleteGraffiti(ctx context.Context, pubKey [fieldparams.BLSPubkeyLength]byte) error {
|
||||
if v.validator == nil {
|
||||
return errors.New("validator is unavailable")
|
||||
}
|
||||
return v.validator.DeleteGraffiti(ctx, pubKey)
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ type FakeValidator struct {
|
||||
proposerSettings *proposer.Settings
|
||||
ProposerSettingWait time.Duration
|
||||
Km keymanager.IKeymanager
|
||||
graffiti string
|
||||
Tracker *beacon.NodeHealthTracker
|
||||
}
|
||||
|
||||
@@ -282,7 +283,25 @@ func (fv *FakeValidator) SetProposerSettings(_ context.Context, settings *propos
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGraffiti for mocking
|
||||
func (f *FakeValidator) GetGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte) ([]byte, error) {
|
||||
return []byte(f.graffiti), nil
|
||||
}
|
||||
|
||||
// SetGraffiti for mocking
|
||||
func (f *FakeValidator) SetGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte, graffiti []byte) error {
|
||||
f.graffiti = string(graffiti)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGraffiti for mocking
|
||||
func (f *FakeValidator) DeleteGraffiti(_ context.Context, _ [fieldparams.BLSPubkeyLength]byte) error {
|
||||
f.graffiti = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*FakeValidator) StartEventStream(_ context.Context, _ []string, _ chan<- *event.Event) {
|
||||
|
||||
}
|
||||
|
||||
func (*FakeValidator) ProcessEvent(_ *event.Event) {}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
@@ -839,3 +840,86 @@ func (s *Server) DeleteGasLimit(w http.ResponseWriter, r *http.Request) {
|
||||
// we respond "not found".
|
||||
httputil.HandleError(w, fmt.Sprintf("No gas limit found for pubkey %q", rawPubkey), http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *Server) GetGraffiti(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.GetGraffiti")
|
||||
defer span.End()
|
||||
|
||||
if s.validatorService == nil {
|
||||
httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
rawPubkey, pubkey, ok := shared.HexFromRoute(w, r, "pubkey", fieldparams.BLSPubkeyLength)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
graffiti, err := s.validatorService.GetGraffiti(ctx, bytesutil.ToBytes48(pubkey))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "unavailable") {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.HandleError(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.WriteJson(w, &GetGraffitiResponse{
|
||||
Data: &GraffitiData{
|
||||
Pubkey: rawPubkey,
|
||||
Graffiti: string(graffiti),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) SetGraffiti(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.SetGraffiti")
|
||||
defer span.End()
|
||||
|
||||
if s.validatorService == nil {
|
||||
httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
_, pubkey, ok := shared.HexFromRoute(w, r, "pubkey", fieldparams.BLSPubkeyLength)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Graffiti string `json:"graffiti"`
|
||||
}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case 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 err := s.validatorService.SetGraffiti(ctx, bytesutil.ToBytes48(pubkey), []byte(req.Graffiti)); err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) DeleteGraffiti(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "validator.keymanagerAPI.DeleteGraffiti")
|
||||
defer span.End()
|
||||
|
||||
if s.validatorService == nil {
|
||||
httputil.HandleError(w, "Validator service not ready.", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
_, pubkey, ok := shared.HexFromRoute(w, r, "pubkey", fieldparams.BLSPubkeyLength)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.validatorService.DeleteGraffiti(ctx, bytesutil.ToBytes48(pubkey)); err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1898,3 +1898,48 @@ func TestServer_DeleteFeeRecipientByPubkey_InvalidPubKey(t *testing.T) {
|
||||
|
||||
require.StringContains(t, "pubkey is invalid", w.Body.String())
|
||||
}
|
||||
|
||||
func TestServer_Graffiti(t *testing.T) {
|
||||
graffiti := "graffiti"
|
||||
m := &mock.Validator{}
|
||||
vs, err := client.NewValidatorService(context.Background(), &client.Config{
|
||||
Validator: m,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
s := &Server{
|
||||
validatorService: vs,
|
||||
}
|
||||
|
||||
var request struct {
|
||||
Graffiti string `json:"graffiti"`
|
||||
}
|
||||
request.Graffiti = graffiti
|
||||
pubkey := "0xaf2e7ba294e03438ea819bd4033c6c1bf6b04320ee2075b77273c08d02f8a61bcc303c2c06bd3713cb442072ae591493"
|
||||
var buf bytes.Buffer
|
||||
err = json.NewEncoder(&buf).Encode(request)
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/eth/v1/validator/{pubkey}/graffiti"), &buf)
|
||||
req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey})
|
||||
w := httptest.NewRecorder()
|
||||
w.Body = &bytes.Buffer{}
|
||||
s.SetGraffiti(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/eth/v1/validator/{pubkey}/graffiti"), nil)
|
||||
req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey})
|
||||
w = httptest.NewRecorder()
|
||||
w.Body = &bytes.Buffer{}
|
||||
s.GetGraffiti(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
resp := &GetGraffitiResponse{}
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), resp))
|
||||
assert.Equal(t, resp.Data.Graffiti, request.Graffiti)
|
||||
assert.Equal(t, resp.Data.Pubkey, pubkey)
|
||||
|
||||
req = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/eth/v1/validator/{pubkey}/graffiti"), nil)
|
||||
req = mux.SetURLVars(req, map[string]string{"pubkey": pubkey})
|
||||
w = httptest.NewRecorder()
|
||||
w.Body = &bytes.Buffer{}
|
||||
s.DeleteGraffiti(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
@@ -230,6 +230,10 @@ func (s *Server) InitializeRoutes() error {
|
||||
s.router.HandleFunc("/eth/v1/validator/{pubkey}/feerecipient", s.SetFeeRecipientByPubkey).Methods(http.MethodPost)
|
||||
s.router.HandleFunc("/eth/v1/validator/{pubkey}/feerecipient", s.DeleteFeeRecipientByPubkey).Methods(http.MethodDelete)
|
||||
s.router.HandleFunc("/eth/v1/validator/{pubkey}/voluntary_exit", s.SetVoluntaryExit).Methods(http.MethodPost)
|
||||
s.router.HandleFunc("/eth/v1/validator/{pubkey}/graffiti", s.GetGraffiti).Methods(http.MethodGet)
|
||||
s.router.HandleFunc("/eth/v1/validator/{pubkey}/graffiti", s.SetGraffiti).Methods(http.MethodPost)
|
||||
s.router.HandleFunc("/eth/v1/validator/{pubkey}/graffiti", s.DeleteGraffiti).Methods(http.MethodDelete)
|
||||
|
||||
// auth endpoint
|
||||
s.router.HandleFunc(api.WebUrlPrefix+"initialize", s.Initialize).Methods(http.MethodGet)
|
||||
// accounts endpoints
|
||||
|
||||
@@ -21,6 +21,7 @@ func TestServer_InitializeRoutes(t *testing.T) {
|
||||
"/eth/v1/validator/{pubkey}/gas_limit": {http.MethodGet, http.MethodPost, http.MethodDelete},
|
||||
"/eth/v1/validator/{pubkey}/feerecipient": {http.MethodGet, http.MethodPost, http.MethodDelete},
|
||||
"/eth/v1/validator/{pubkey}/voluntary_exit": {http.MethodPost},
|
||||
"/eth/v1/validator/{pubkey}/graffiti": {http.MethodGet, http.MethodPost, http.MethodDelete},
|
||||
"/v2/validator/health/version": {http.MethodGet},
|
||||
"/v2/validator/health/logs/validator/stream": {http.MethodGet},
|
||||
"/v2/validator/health/logs/beacon/stream": {http.MethodGet},
|
||||
|
||||
@@ -99,6 +99,16 @@ type SetFeeRecipientByPubkeyRequest struct {
|
||||
Ethaddress string `json:"ethaddress"`
|
||||
}
|
||||
|
||||
// Graffiti keymanager api
|
||||
type GetGraffitiResponse struct {
|
||||
Data *GraffitiData `json:"data"`
|
||||
}
|
||||
|
||||
type GraffitiData struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
Graffiti string `json:"graffiti"`
|
||||
}
|
||||
|
||||
type BeaconStatusResponse struct {
|
||||
BeaconNodeEndpoint string `json:"beacon_node_endpoint"`
|
||||
Connected bool `json:"connected"`
|
||||
|
||||
Reference in New Issue
Block a user