Skip to content

Commit

Permalink
Add events.
Browse files Browse the repository at this point in the history
  • Loading branch information
mcdee committed Aug 12, 2024
1 parent b3d8c14 commit 490d07a
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 71 deletions.
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
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
Loading

0 comments on commit 490d07a

Please sign in to comment.