Skip to content

Commit

Permalink
Remove ValidatorsByPubKey.
Browse files Browse the repository at this point in the history
Rename BeaconBlockBlobs to BlobSidecars.
  • Loading branch information
mcdee committed Oct 17, 2023
1 parent a5d7547 commit 120f202
Show file tree
Hide file tree
Showing 18 changed files with 269 additions and 470 deletions.
44 changes: 44 additions & 0 deletions api/blobsidecars.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright © 2023 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
"github.com/attestantio/go-eth2-client/spec/deneb"
ssz "github.com/ferranbt/fastssz"
)

// BlobSidecars is an API construct to allow decoding an array of blob sidecars.
type BlobSidecars struct {
Sidecars []*deneb.BlobSidecar `ssz-max:"6"`
}

// UnmarshalSSZ ssz unmarshals the BlobSidecars object.
// This is a hand-crafted function, as automatic generation does not support immediate arrays.
func (b *BlobSidecars) UnmarshalSSZ(buf []byte) error {
num, err := ssz.DivideInt2(len(buf), 131256, 6)
if err != nil {
return err
}
b.Sidecars = make([]*deneb.BlobSidecar, num)
for ii := 0; ii < num; ii++ {
if b.Sidecars[ii] == nil {
b.Sidecars[ii] = new(deneb.BlobSidecar)
}
if err = b.Sidecars[ii].UnmarshalSSZ(buf[ii*131256 : (ii+1)*131256]); err != nil {
return err
}
}

return nil
}
6 changes: 3 additions & 3 deletions api/beaconblockblobsopts.go → api/blobsidecarsopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

package api

// BeaconBlockBlobsOpts are the options for obtaining beacon block blobs.
type BeaconBlockBlobsOpts struct {
// Block is the ID of the block which the data is obtained.
// BlobSidecarsOpts are the options for obtaining blob sidecars.
type BlobSidecarsOpts struct {
// Block is the ID of the block for which the data is obtained.
Block string
}
3 changes: 3 additions & 0 deletions docs/0.19-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,6 @@ The `BeaconBlockSubmitter` and associated `SubmitBeaconBlock` function have been

The `BlindedBeaconBlockSubmitter` and associated `SubmitBlindedBeaconBlock` function have been deprecated, and will not work at the Deneb hard fork. A new interface `BlindedProposalSubmitter` and associated `SubmitBlindedProposal` function have been created to provide on-going support for submitting blinded proposals.

### `ValidatorsByPubKey` removed

The `ValidatorsByPubKey` function has been removed; use `Validators` with suitable options instead.
54 changes: 0 additions & 54 deletions http/beaconblockblobs.go

This file was deleted.

84 changes: 84 additions & 0 deletions http/blobsidecars.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright © 2023 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package http

import (
"bytes"
"context"
"fmt"

"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec/deneb"
"github.com/pkg/errors"
)

// BlobSidecars fetches the blobs sidecars given options.
func (s *Service) BlobSidecars(ctx context.Context,
opts *api.BlobSidecarsOpts,
) (
*api.Response[[]*deneb.BlobSidecar],
error,
) {
if opts == nil {
return nil, errors.New("no options specified")
}
if opts.Block == "" {
return nil, errors.New("no block specified")
}

httpResponse, err := s.get2(ctx, fmt.Sprintf("/eth/v1/beacon/blob_sidecars/%s", opts.Block))
if err != nil {
return nil, err
}

var response *api.Response[[]*deneb.BlobSidecar]
switch httpResponse.contentType {
case ContentTypeSSZ:
response, err = s.blobSidecarsFromSSZ(httpResponse)
case ContentTypeJSON:
response, err = s.blobSidecarsFromJSON(httpResponse)
default:
return nil, fmt.Errorf("unhandled content type %v", httpResponse.contentType)
}
if err != nil {
return nil, err
}

return response, nil
}

func (s *Service) blobSidecarsFromSSZ(res *httpResponse) (*api.Response[[]*deneb.BlobSidecar], error) {
response := &api.Response[[]*deneb.BlobSidecar]{}

data := &api.BlobSidecars{}
if err := data.UnmarshalSSZ(res.body); err != nil {
return nil, errors.Wrap(err, "failed to decode blob sidecars")
}

response.Data = data.Sidecars

return response, nil
}

func (s *Service) blobSidecarsFromJSON(res *httpResponse) (*api.Response[[]*deneb.BlobSidecar], error) {
response := &api.Response[[]*deneb.BlobSidecar]{}

var err error
response.Data, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), []*deneb.BlobSidecar{})
if err != nil {
return nil, err
}

return response, nil
}
4 changes: 3 additions & 1 deletion http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,9 @@ func populateConsensusVersion(res *httpResponse, resp *http.Response) error {
if !exists {
// No consensus version supplied in response; obtain it from the body if possible.
if res.contentType != ContentTypeJSON {
return errors.New("no consensus version header")
// Not present here either. Many responses do not provide this information, so assume
// this is one of them.
return nil
}
var metadata responseMetadata
if err := json.Unmarshal(res.body, &metadata); err != nil {
Expand Down
103 changes: 100 additions & 3 deletions http/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"fmt"
"strings"

api "github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
Expand All @@ -38,6 +38,20 @@ var indexChunkSizes = map[string]int{
"teku": 1000,
}

// pubKeyChunkSizes defines the per-beacon-node size of a public key chunk.
// A request should be no more than 8,000 bytes to work with all currently-supported clients.
// A public key, including 0x header and comma separator, takes up 99 bytes.
// We also need to reserve space for the state ID and the endpoint itself, to be safe we go
// with 500 bytes for this which results in us having space for 75 public keys.
// That said, some nodes have their own built-in limits so use them where appropriate.
var pubKeyChunkSizes = map[string]int{
"default": 75,
"lighthouse": 75,
"nimbus": 75,
"prysm": 75,
"teku": 75,
}

// indexChunkSize is the maximum number of validator indices to send in each request.
func (s *Service) indexChunkSize(ctx context.Context) int {
if s.userIndexChunkSize > 0 {
Expand Down Expand Up @@ -67,6 +81,35 @@ func (s *Service) indexChunkSize(ctx context.Context) int {
}
}

// pubKeyChunkSize is the maximum number of validator public keys to send in each request.
func (s *Service) pubKeyChunkSize(ctx context.Context) int {
if s.userPubKeyChunkSize > 0 {
return s.userPubKeyChunkSize
}

nodeClient := ""
response, err := s.NodeClient(ctx)
if err == nil {
nodeClient = response.Data
} else {
// Use default.
nodeClient = "default"
}

switch {
case strings.Contains(nodeClient, "lighthouse"):
return pubKeyChunkSizes["lighthouse"]
case strings.Contains(nodeClient, "nimbus"):
return pubKeyChunkSizes["nimbus"]
case strings.Contains(nodeClient, "prysm"):
return pubKeyChunkSizes["prysm"]
case strings.Contains(nodeClient, "teku"):
return pubKeyChunkSizes["teku"]
default:
return pubKeyChunkSizes["default"]
}
}

// Validators provides the validators, with their balance and status, for the given options.
func (s *Service) Validators(ctx context.Context,
opts *api.ValidatorsOpts,
Expand Down Expand Up @@ -102,7 +145,7 @@ func (s *Service) Validators(ctx context.Context,
}
url = fmt.Sprintf("%s?id=%s", url, strings.Join(ids, ","))
case len(opts.PubKeys) > 0:
ids := make([]string, len(opts.Indices))
ids := make([]string, len(opts.PubKeys))
for i := range opts.PubKeys {
ids[i] = opts.PubKeys[i].String()
}
Expand Down Expand Up @@ -192,7 +235,26 @@ func (s *Service) validatorsFromState(ctx context.Context,
}

// chunkedValidators obtains the validators a chunk at a time.
func (s *Service) chunkedValidators(ctx context.Context, opts *api.ValidatorsOpts) (*api.Response[map[phase0.ValidatorIndex]*apiv1.Validator], error) {
func (s *Service) chunkedValidators(ctx context.Context,
opts *api.ValidatorsOpts,
) (
*api.Response[map[phase0.ValidatorIndex]*apiv1.Validator],
error,
) {
if len(opts.Indices) > 0 {
return s.chunkedValidatorsByIndex(ctx, opts)
}

return s.chunkedValidatorsByPubkey(ctx, opts)
}

// chunkedValidatorsByIndex obtains validators with index a chunk at a time.
func (s *Service) chunkedValidatorsByIndex(ctx context.Context,
opts *api.ValidatorsOpts,
) (
*api.Response[map[phase0.ValidatorIndex]*apiv1.Validator],
error,
) {
data := make(map[phase0.ValidatorIndex]*apiv1.Validator)
metadata := make(map[string]any)
indexChunkSize := s.indexChunkSize(ctx)
Expand Down Expand Up @@ -220,3 +282,38 @@ func (s *Service) chunkedValidators(ctx context.Context, opts *api.ValidatorsOpt
Metadata: metadata,
}, nil
}

// chunkedValidatorsByIndex obtains validators with public key a chunk at a time.
func (s *Service) chunkedValidatorsByPubkey(ctx context.Context,
opts *api.ValidatorsOpts,
) (
*api.Response[map[phase0.ValidatorIndex]*apiv1.Validator],
error,
) {
data := make(map[phase0.ValidatorIndex]*apiv1.Validator)
metadata := make(map[string]any)
pubkeyChunkSize := s.pubKeyChunkSize(ctx)
for i := 0; i < len(opts.PubKeys); i += pubkeyChunkSize {
chunkStart := i
chunkEnd := i + pubkeyChunkSize
if len(opts.PubKeys) < chunkEnd {
chunkEnd = len(opts.PubKeys)
}
chunk := opts.PubKeys[chunkStart:chunkEnd]
chunkRes, err := s.Validators(ctx, &api.ValidatorsOpts{State: opts.State, PubKeys: chunk})
if err != nil {
return nil, errors.Wrap(err, "failed to obtain chunk")
}
for k, v := range chunkRes.Data {
data[k] = v
}
for k, v := range chunkRes.Metadata {
metadata[k] = v
}
}

return &api.Response[map[phase0.ValidatorIndex]*apiv1.Validator]{
Data: data,
Metadata: metadata,
}, nil
}
15 changes: 13 additions & 2 deletions http/validators_test.go

Large diffs are not rendered by default.

Loading

0 comments on commit 120f202

Please sign in to comment.