Skip to content

Commit

Permalink
Merge branch 'master' into pk910/spec-15.0-alpha.6
Browse files Browse the repository at this point in the history
  • Loading branch information
pk910 committed Sep 23, 2024
2 parents 82887e4 + 50179b8 commit b50d9cc
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 86 deletions.
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ output:

# All available settings of specific linters.
linters-settings:
gosec:
excludes:
- G115 # This generates a lot of false positives.

lll:
line-length: 132

Expand Down Expand Up @@ -162,6 +166,7 @@ linters:
- execinquery
- exhaustive
- exhaustruct
- exportloopref
- forcetypeassert
- funlen
- gci
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
dev:
- add attester_slashing, block_gossip, bls_to_execution_change and proposer_slashing events

0.21.10:
- better validator state when balance not supplied

Expand Down
11 changes: 8 additions & 3 deletions api/attestationpoolopts.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2023 Attestant Limited.
// Copyright © 2023, 2024 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
Expand All @@ -19,6 +19,11 @@ import "github.com/attestantio/go-eth2-client/spec/phase0"
type AttestationPoolOpts struct {
Common CommonOpts

// Slot is the slot for which the data is obtained.
Slot phase0.Slot
// Slot is the slot for which the data is obtained. If not present then
// data for all slots will be obtained.
Slot *phase0.Slot

// CommmitteeIndex is the committee index for which the data is obtained.
// If not present then data for all committee indices will be obtained.
CommitteeIndex *phase0.CommitteeIndex
}
86 changes: 86 additions & 0 deletions api/v1/blockgossipevent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright © 2024 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 (
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"

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

// BlockGossipEvent is the data for the block gossip event.
type BlockGossipEvent struct {
Slot phase0.Slot
Block phase0.Root
}

// blockGossipEventJSON is the spec representation of the struct.
type blockGossipEventJSON struct {
Slot string `json:"slot"`
Block string `json:"block"`
}

// MarshalJSON implements json.Marshaler.
func (e *BlockGossipEvent) MarshalJSON() ([]byte, error) {
return json.Marshal(&blockGossipEventJSON{
Slot: fmt.Sprintf("%d", e.Slot),
Block: fmt.Sprintf("%#x", e.Block),
})
}

// UnmarshalJSON implements json.Unmarshaler.
func (e *BlockGossipEvent) UnmarshalJSON(input []byte) error {
var err error

var data blockGossipEventJSON
if err = json.Unmarshal(input, &data); err != nil {
return errors.Wrap(err, "invalid JSON")
}
if data.Slot == "" {
return errors.New("slot missing")
}
slot, err := strconv.ParseUint(data.Slot, 10, 64)
if err != nil {
return errors.Wrap(err, "invalid value for slot")
}
e.Slot = phase0.Slot(slot)
if data.Block == "" {
return errors.New("block missing")
}
block, err := hex.DecodeString(strings.TrimPrefix(data.Block, "0x"))
if err != nil {
return errors.Wrap(err, "invalid value for block")
}
if len(block) != rootLength {
return fmt.Errorf("incorrect length %d for block", len(block))
}
copy(e.Block[:], block)

return nil
}

// String returns a string version of the structure.
func (e *BlockGossipEvent) String() string {
data, err := json.Marshal(e)
if err != nil {
return fmt.Sprintf("ERR: %v", err)
}

return string(data)
}
101 changes: 101 additions & 0 deletions api/v1/blockgossipevent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright © 2024 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_test

import (
"encoding/json"
"testing"

api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/stretchr/testify/assert"
require "github.com/stretchr/testify/require"
)

func TestBlockGossipEventJSON(t *testing.T) {
tests := []struct {
name string
input []byte
err string
}{
{
name: "Empty",
err: "unexpected end of JSON input",
},
{
name: "JSONBad",
input: []byte("[]"),
err: "invalid JSON: json: cannot unmarshal array into Go value of type v1.blockGossipEventJSON",
},
{
name: "SlotMissing",
input: []byte(`{"block":"0x99e3f24aab3dd084045a0c927a33b8463eb5c7b17eeadfecdcf4e4badf7b6028"}`),
err: "slot missing",
},
{
name: "SlotWrongType",
input: []byte(`{"slot":true,"block":"0x99e3f24aab3dd084045a0c927a33b8463eb5c7b17eeadfecdcf4e4badf7b6028"}`),
err: "invalid JSON: json: cannot unmarshal bool into Go struct field blockGossipEventJSON.slot of type string",
},
{
name: "SlotInvalid",
input: []byte(`{"slot":"-1","block":"0x99e3f24aab3dd084045a0c927a33b8463eb5c7b17eeadfecdcf4e4badf7b6028"}`),
err: "invalid value for slot: strconv.ParseUint: parsing \"-1\": invalid syntax",
},
{
name: "BlockMissing",
input: []byte(`{"slot":"525277"}`),
err: "block missing",
},
{
name: "BlockWrongType",
input: []byte(`{"slot":"525277","block":true}`),
err: "invalid JSON: json: cannot unmarshal bool into Go struct field blockGossipEventJSON.block of type string",
},
{
name: "BlockInvalid",
input: []byte(`{"slot":"525277","block":"invalid"}`),
err: "invalid value for block: encoding/hex: invalid byte: U+0069 'i'",
},
{
name: "BlockShort",
input: []byte(`{"slot":"525277","block":"0xe3f24aab3dd084045a0c927a33b8463eb5c7b17eeadfecdcf4e4badf7b6028"}`),
err: "incorrect length 31 for block",
},
{
name: "BlockLong",
input: []byte(`{"slot":"525277","block":"0x9999e3f24aab3dd084045a0c927a33b8463eb5c7b17eeadfecdcf4e4badf7b6028"}`),
err: "incorrect length 33 for block",
},
{
name: "Good",
input: []byte(`{"slot":"525277","block":"0x99e3f24aab3dd084045a0c927a33b8463eb5c7b17eeadfecdcf4e4badf7b6028"}`),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var res api.BlockGossipEvent
err := json.Unmarshal(test.input, &res)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
rt, err := json.Marshal(&res)
require.NoError(t, err)
assert.Equal(t, string(test.input), string(rt))
assert.Equal(t, string(rt), res.String())
}
})
}
}
43 changes: 28 additions & 15 deletions api/v1/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"

"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/capella"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
)
Expand All @@ -32,15 +33,19 @@ type Event struct {

// SupportedEventTopics is a map of supported event topics.
var SupportedEventTopics = map[string]bool{
"attestation": true,
"block": true,
"chain_reorg": true,
"finalized_checkpoint": true,
"head": true,
"voluntary_exit": true,
"contribution_and_proof": true,
"payload_attributes": true,
"blob_sidecar": true,
"attestation": true,
"attester_slashing": true,
"blob_sidecar": true,
"block": true,
"block_gossip": true,
"bls_to_execution_change": true,
"chain_reorg": true,
"contribution_and_proof": true,
"finalized_checkpoint": true,
"head": true,
"payload_attributes": true,
"proposer_slashing": true,
"voluntary_exit": true,
}

// eventJSON is the spec representation of the struct.
Expand Down Expand Up @@ -86,22 +91,30 @@ func (e *Event) UnmarshalJSON(input []byte) error {
switch eventJSON.Topic {
case "attestation":
e.Data = &phase0.Attestation{}
case "attester_slashing":
e.Data = &phase0.AttesterSlashing{}
case "blob_sidecar":
e.Data = &BlobSidecarEvent{}
case "block":
e.Data = &BlockEvent{}
case "block_gossip":
e.Data = &BlockGossipEvent{}
case "bls_to_execution_change":
e.Data = &capella.SignedBLSToExecutionChange{}
case "chain_reorg":
e.Data = &ChainReorgEvent{}
case "contribution_and_proof":
e.Data = &altair.SignedContributionAndProof{}
case "finalized_checkpoint":
e.Data = &FinalizedCheckpointEvent{}
case "head":
e.Data = &HeadEvent{}
case "voluntary_exit":
e.Data = &phase0.SignedVoluntaryExit{}
case "contribution_and_proof":
e.Data = &altair.SignedContributionAndProof{}
case "payload_attributes":
e.Data = &PayloadAttributesEvent{}
case "blob_sidecar":
e.Data = &BlobSidecarEvent{}
case "proposer_slashing":
e.Data = &phase0.ProposerSlashing{}
case "voluntary_exit":
e.Data = &phase0.SignedVoluntaryExit{}
default:
return fmt.Errorf("unsupported event topic %s", eventJSON.Topic)
}
Expand Down
2 changes: 1 addition & 1 deletion api/v1/forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (d *ForkChoiceNodeValidity) UnmarshalJSON(input []byte) error {

// String returns a string representation of the ForkChoiceNodeValidity.
func (d ForkChoiceNodeValidity) String() string {
if int(d) >= len(ForkChoiceNodeValidityStrings) {
if uint64(d) >= uint64(len(ForkChoiceNodeValidityStrings)) {
return "unknown"
}

Expand Down
16 changes: 13 additions & 3 deletions http/attestationpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"errors"
"fmt"
"strings"

client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/api"
Expand All @@ -39,8 +40,14 @@ func (s *Service) AttestationPool(ctx context.Context,
}

endpoint := "/eth/v1/beacon/pool/attestations"
query := fmt.Sprintf("slot=%d", opts.Slot)
httpResponse, err := s.get(ctx, endpoint, query, &opts.Common, false)
queryItems := make([]string, 0)
if opts.Slot != nil {
queryItems = append(queryItems, fmt.Sprintf("slot=%d", *opts.Slot))
}
if opts.CommitteeIndex != nil {
queryItems = append(queryItems, fmt.Sprintf("committee_index=%d", *opts.CommitteeIndex))
}
httpResponse, err := s.get(ctx, endpoint, strings.Join(queryItems, "&"), &opts.Common, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -77,9 +84,12 @@ func (*Service) attestationPoolFromJSON(_ context.Context,

func verifyAttestationPool(opts *api.AttestationPoolOpts, data []*phase0.Attestation) error {
for _, datum := range data {
if datum.Data.Slot != opts.Slot {
if opts.Slot != nil && datum.Data.Slot != *opts.Slot {
return errors.New("attestation data not for requested slot")
}
if opts.CommitteeIndex != nil && datum.Data.Index != *opts.CommitteeIndex {
return errors.New("attestation data not for requested committee index")
}
}

return nil
Expand Down
9 changes: 7 additions & 2 deletions http/attestationpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ import (
"github.com/stretchr/testify/require"
)

func slotptr(slot uint64) *phase0.Slot {
res := phase0.Slot(slot)
return &res
}

func TestAttestationPool(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down Expand Up @@ -57,14 +62,14 @@ func TestAttestationPool(t *testing.T) {
{
name: "Empty",
opts: &api.AttestationPoolOpts{
Slot: 1,
Slot: slotptr(1),
},
expected: make([]*phase0.Attestation, 0),
},
{
name: "Current",
opts: &api.AttestationPoolOpts{
Slot: phase0.Slot(uint64(time.Since(genesisResponse.Data.GenesisTime).Seconds()) / uint64(slotDuration.Seconds())),
Slot: slotptr(uint64(time.Since(genesisResponse.Data.GenesisTime).Seconds()) / uint64(slotDuration.Seconds())),
},
},
}
Expand Down
Loading

0 comments on commit b50d9cc

Please sign in to comment.