Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update electra specs to v1.5.0-alpha.10 #174

Merged
merged 10 commits into from
Dec 28, 2024
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ coverage.html
# Vim
*.sw?

# Intellij and friends
# IntelliJ and friends
.idea/

# Makefile
Makefile

# Local TODO
TODO.md
21 changes: 19 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,24 @@ linters-settings:
json: snake
yaml: snake

goheader:
values:
regexp:
YEARS: '(20\d\d - 20\d\d|20\d\d, 20\d\d|20\d\d)'
template: |-
Copyright © {{ YEARS }} 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.

linters:
# Enable all available linters.
# Default: false
Expand All @@ -163,7 +181,6 @@ linters:
- cyclop
- depguard
- dupl
- execinquery
- exhaustive
- exhaustruct
- exportloopref
Expand All @@ -175,13 +192,13 @@ linters:
- gocognit
- goconst
- err113
- gomnd
- ireturn
- lll
- maintidx
- mnd
- musttag
- perfsprint
- recvcheck
- varnamelen
- wrapcheck
- wsl
13 changes: 13 additions & 0 deletions api/v1/blobsidecarevent.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
// 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 v1

import (
Expand Down
13 changes: 13 additions & 0 deletions api/v1/payloadattributesevent.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
// 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 v1

import (
Expand Down
13 changes: 13 additions & 0 deletions api/v1/peers.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
// 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 v1

import (
Expand Down
30 changes: 30 additions & 0 deletions api/versionedproposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,36 @@ func (v *VersionedProposal) Timestamp() (uint64, error) {
}
}

// GasLimit returns the gas limit of the proposal.
func (v *VersionedProposal) GasLimit() (uint64, error) {
if v.Version >= spec.DataVersionBellatrix && !v.payloadPresent() {
return 0, ErrDataMissing
}

switch v.Version {
case spec.DataVersionBellatrix:
if v.Blinded {
return v.BellatrixBlinded.Body.ExecutionPayloadHeader.GasLimit, nil
}

return v.Bellatrix.Body.ExecutionPayload.GasLimit, nil
case spec.DataVersionCapella:
if v.Blinded {
return v.CapellaBlinded.Body.ExecutionPayloadHeader.GasLimit, nil
}

return v.Capella.Body.ExecutionPayload.GasLimit, nil
case spec.DataVersionDeneb:
if v.Blinded {
return v.DenebBlinded.Body.ExecutionPayloadHeader.GasLimit, nil
}

return v.Deneb.Block.Body.ExecutionPayload.GasLimit, nil
default:
return 0, ErrUnsupportedVersion
}
}

// Blobs returns the blobs of the proposal.
func (v *VersionedProposal) Blobs() ([]deneb.Blob, error) {
if v.Version >= spec.DataVersionDeneb && !v.payloadPresent() {
Expand Down
15 changes: 12 additions & 3 deletions http/attesterduties.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,22 @@ func (s *Service) AttesterDuties(ctx context.Context,
return nil, errors.Join(errors.New("failed to write end of validator index array"), err)
}

url := fmt.Sprintf("/eth/v1/validator/duties/attester/%d", opts.Epoch)
respBodyReader, err := s.post(ctx, url, &reqBodyReader)
endpoint := fmt.Sprintf("/eth/v1/validator/duties/attester/%d", opts.Epoch)
query := ""

httpResponse, err := s.post(ctx,
endpoint,
query,
&api.CommonOpts{},
&reqBodyReader,
ContentTypeJSON,
map[string]string{},
)
if err != nil {
return nil, errors.Join(errors.New("failed to request attester duties"), err)
}

data, metadata, err := decodeJSONResponse(respBodyReader, []*apiv1.AttesterDuty{})
data, metadata, err := decodeJSONResponse(bytes.NewReader(httpResponse.body), []*apiv1.AttesterDuty{})
if err != nil {
return nil, err
}
Expand Down
13 changes: 5 additions & 8 deletions http/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"math/rand"
"net"
"net/http"
"net/url"
"strings"
"time"

Expand Down Expand Up @@ -54,14 +53,12 @@ func (s *Service) Events(ctx context.Context, topics []string, handler consensus
}
}

reference, err := url.Parse(fmt.Sprintf("eth/v1/events?topics=%s", strings.Join(topics, "&topics=")))
if err != nil {
return errors.Join(errors.New("invalid endpoint"), err)
}
callURL := s.base.ResolveReference(reference).String()
log.Trace().Str("url", callURL).Msg("GET request to events stream")
endpoint := "/eth/v1/events"
query := "topics=" + strings.Join(topics, "&topics=")
callURL := urlForCall(s.base, endpoint, query)
log.Trace().Str("url", callURL.String()).Msg("GET request to events stream")

client := sse.NewClient(callURL)
client := sse.NewClient(callURL.String())
for k, v := range s.extraHeaders {
client.Headers[k] = v
}
Expand Down
125 changes: 29 additions & 96 deletions http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,96 +27,18 @@ import (

"github.com/attestantio/go-eth2-client/api"
"github.com/attestantio/go-eth2-client/spec"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)

// defaultUserAgent is sent with requests if no other user agent has been supplied.
const defaultUserAgent = "go-eth2-client/0.21.11"
const defaultUserAgent = "go-eth2-client/0.22.0"

// post sends an HTTP post request and returns the body.
func (s *Service) post(ctx context.Context, endpoint string, body io.Reader) (io.Reader, error) {
ctx, span := otel.Tracer("attestantio.go-eth2-client.http").Start(ctx, "post")
defer span.End()

// #nosec G404
log := s.log.With().Str("id", fmt.Sprintf("%02x", rand.Int31())).Str("address", s.address).Str("endpoint", endpoint).Logger()
if e := log.Trace(); e.Enabled() {
bodyBytes, err := io.ReadAll(body)
if err != nil {
return nil, errors.New("failed to read request body")
}
body = bytes.NewReader(bodyBytes)

e.RawJSON("body", bodyBytes).Msg("POST request")
}

callURL := urlForCall(s.base, endpoint, "")
log.Trace().Str("url", callURL.String()).Msg("URL to POST")
span.SetAttributes(attribute.String("url", callURL.String()))

opCtx, cancel := context.WithTimeout(ctx, s.timeout)
defer cancel()
req, err := http.NewRequestWithContext(opCtx, http.MethodPost, callURL.String(), body)
if err != nil {
span.SetStatus(codes.Error, "Failed to create request")

return nil, errors.Join(errors.New("failed to create POST request"), err)
}

s.addExtraHeaders(req)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if req.Header.Get("User-Agent") == "" {
req.Header.Set("User-Agent", defaultUserAgent)
}

resp, err := s.client.Do(req)
if err != nil {
go s.CheckConnectionState(ctx)

span.SetStatus(codes.Error, err.Error())
s.monitorPostComplete(ctx, callURL.Path, "failed")

return nil, errors.Join(errors.New("failed to call POST endpoint"), err)
}
defer resp.Body.Close()
log = log.With().Int("status_code", resp.StatusCode).Logger()

data, err := io.ReadAll(resp.Body)
if err != nil {
span.SetStatus(codes.Error, err.Error())
s.monitorPostComplete(ctx, callURL.Path, "failed")

return nil, errors.Join(errors.New("failed to read POST response"), err)
}

statusFamily := statusCodeFamily(resp.StatusCode)
if statusFamily != 2 {
trimmedResponse := bytes.ReplaceAll(bytes.ReplaceAll(data, []byte{0x0a}, []byte{}), []byte{0x0d}, []byte{})
log.Debug().Int("status_code", resp.StatusCode).RawJSON("response", trimmedResponse).Msg("POST failed")

span.SetStatus(codes.Error, fmt.Sprintf("Status code %d", resp.StatusCode))
s.monitorPostComplete(ctx, callURL.Path, "failed")

return nil, &api.Error{
Method: http.MethodPost,
StatusCode: resp.StatusCode,
Endpoint: endpoint,
Data: data,
}
}

log.Trace().Str("response", string(data)).Msg("POST response")
s.monitorPostComplete(ctx, callURL.Path, "succeeded")

return bytes.NewReader(data), nil
}

// post2 sends an HTTP post request and returns the body.
func (s *Service) post2(ctx context.Context,
func (s *Service) post(ctx context.Context,
endpoint string,
query string,
opts *api.CommonOpts,
Expand Down Expand Up @@ -244,12 +166,7 @@ func (s *Service) post2(ctx context.Context,

statusFamily := statusCodeFamily(resp.StatusCode)
if statusFamily != 2 {
if res.contentType == ContentTypeJSON {
trimmedResponse := bytes.ReplaceAll(bytes.ReplaceAll(res.body, []byte{0x0a}, []byte{}), []byte{0x0d}, []byte{})
log.Debug().Int("status_code", resp.StatusCode).RawJSON("response", trimmedResponse).Msg("POST failed")
} else {
log.Debug().Int("status_code", resp.StatusCode).Msg("POST failed")
}
s.logBadStatus(ctx, "POST", res, log)

span.SetStatus(codes.Error, fmt.Sprintf("Status code %d", resp.StatusCode))
s.monitorPostComplete(ctx, callURL.Path, "failed")
Expand All @@ -267,6 +184,23 @@ func (s *Service) post2(ctx context.Context,
return res, nil
}

func (*Service) logBadStatus(_ context.Context,
method string,
res *httpResponse,
log zerolog.Logger,
) {
if res.contentType == ContentTypeJSON {
trimmedResponse := bytes.ReplaceAll(bytes.ReplaceAll(res.body, []byte{0x0a}, []byte{}), []byte{0x0d}, []byte{})
if bytes.HasPrefix(res.body, []byte("{")) {
log.Debug().Int("status_code", res.statusCode).RawJSON("response", trimmedResponse).Msg(method + " failed")
} else {
log.Debug().Int("status_code", res.statusCode).Str("response", string(trimmedResponse)).Msg(method + " failed")
}
} else {
log.Debug().Int("status_code", res.statusCode).Msg(method + " failed")
}
}

func (s *Service) addExtraHeaders(req *http.Request) {
for k, v := range s.extraHeaders {
req.Header.Add(k, v)
Expand Down Expand Up @@ -400,17 +334,9 @@ func (s *Service) get(ctx context.Context,
attribute.String("content-type", res.contentType.String()),
))

if res.contentType == ContentTypeJSON {
if e := log.Trace(); e.Enabled() {
trimmedResponse := bytes.ReplaceAll(bytes.ReplaceAll(res.body, []byte{0x0a}, []byte{}), []byte{0x0d}, []byte{})
e.RawJSON("body", trimmedResponse).Msg("GET response")
}
}

statusFamily := statusCodeFamily(resp.StatusCode)
if statusFamily != 2 {
trimmedResponse := bytes.ReplaceAll(bytes.ReplaceAll(res.body, []byte{0x0a}, []byte{}), []byte{0x0d}, []byte{})
log.Debug().Int("status_code", resp.StatusCode).RawJSON("response", trimmedResponse).Msg("GET failed")
s.logBadStatus(ctx, "GET", res, log)

span.SetStatus(codes.Error, fmt.Sprintf("Status code %d", resp.StatusCode))
s.monitorGetComplete(ctx, callURL.Path, "failed")
Expand All @@ -423,6 +349,13 @@ func (s *Service) get(ctx context.Context,
}
}

if res.contentType == ContentTypeJSON {
if e := log.Trace(); e.Enabled() {
trimmedResponse := bytes.ReplaceAll(bytes.ReplaceAll(res.body, []byte{0x0a}, []byte{}), []byte{0x0d}, []byte{})
e.RawJSON("body", trimmedResponse).Msg("GET response")
}
}

if err := populateConsensusVersion(res, resp); err != nil {
return nil, errors.Join(errors.New("failed to parse consensus version"), err)
}
Expand Down
13 changes: 13 additions & 0 deletions http/json.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
// 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 (
Expand Down
Loading
Loading