Skip to content

Commit a03eca5

Browse files
committed
add node identity
1 parent c6c1e4a commit a03eca5

9 files changed

Lines changed: 275 additions & 1 deletion

File tree

api/nodeidentityopts.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright © 2026 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package api
15+
16+
// NodeIdentityOpts are the options for obtaining the node identity.
17+
type NodeIdentityOpts struct {
18+
Common CommonOpts
19+
}

api/v1/identity.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package v1
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
// NodeIdentity contains the node identity information.
11+
type NodeIdentity struct {
12+
PeerID string `json:"peer_id"`
13+
Enr string `json:"enr"`
14+
P2PAddresses []string `json:"p2p_addresses"`
15+
DiscoveryAddresses []string `json:"discovery_addresses"`
16+
Metadata map[string]string `json:"metadata"`
17+
}
18+
19+
type nodeIdentityJSON struct {
20+
PeerID string `json:"peer_id"`
21+
Enr string `json:"enr"`
22+
P2PAddresses []string `json:"p2p_addresses"`
23+
DiscoveryAddresses []string `json:"discovery_addresses"`
24+
Metadata map[string]string `json:"metadata"`
25+
}
26+
27+
// MarshalJSON implements json.Marshaler.
28+
func (n *NodeIdentity) MarshalJSON() ([]byte, error) {
29+
return json.Marshal(&nodeIdentityJSON{
30+
PeerID: n.PeerID,
31+
Enr: n.Enr,
32+
P2PAddresses: n.P2PAddresses,
33+
DiscoveryAddresses: n.DiscoveryAddresses,
34+
Metadata: n.Metadata,
35+
})
36+
}
37+
38+
// UnmarshalJSON implements json.Unmarshaler.
39+
func (n *NodeIdentity) UnmarshalJSON(input []byte) error {
40+
var nodeIdentityJSON nodeIdentityJSON
41+
42+
if err := json.Unmarshal(input, &nodeIdentityJSON); err != nil {
43+
return errors.Wrap(err, "invalid JSON")
44+
}
45+
46+
n.PeerID = nodeIdentityJSON.PeerID
47+
n.Enr = nodeIdentityJSON.Enr
48+
n.P2PAddresses = nodeIdentityJSON.P2PAddresses
49+
n.DiscoveryAddresses = nodeIdentityJSON.DiscoveryAddresses
50+
n.Metadata = nodeIdentityJSON.Metadata
51+
52+
return nil
53+
}
54+
55+
func (n *NodeIdentity) String() string {
56+
data, err := json.Marshal(n)
57+
if err != nil {
58+
return fmt.Sprintf("ERR: %v", err)
59+
}
60+
61+
return string(data)
62+
}

http/nodeidentity.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright © 2026 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package http
15+
16+
import (
17+
"bytes"
18+
"context"
19+
"fmt"
20+
21+
client "github.com/attestantio/go-eth2-client"
22+
"github.com/attestantio/go-eth2-client/api"
23+
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
24+
)
25+
26+
// NodeIdentity provides the identity information of the node.
27+
func (s *Service) NodeIdentity(ctx context.Context,
28+
opts *api.NodeIdentityOpts,
29+
) (
30+
*api.Response[*apiv1.NodeIdentity],
31+
error,
32+
) {
33+
if err := s.assertIsActive(ctx); err != nil {
34+
return nil, err
35+
}
36+
if opts == nil {
37+
return nil, client.ErrNoOptions
38+
}
39+
40+
endpoint := "/eth/v1/node/identity"
41+
httpResponse, err := s.get(ctx, endpoint, "", &opts.Common, false)
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
if httpResponse.contentType != ContentTypeJSON {
47+
return nil, fmt.Errorf("unexpected content type %v (expected JSON)", httpResponse.contentType)
48+
}
49+
50+
data, metadata, err := decodeJSONResponse(bytes.NewReader(httpResponse.body), &apiv1.NodeIdentity{})
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
return &api.Response[*apiv1.NodeIdentity]{
56+
Data: data,
57+
Metadata: metadata,
58+
}, nil
59+
}

mock/nodeidentity.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright © 2026 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package mock
15+
16+
import (
17+
"context"
18+
19+
"github.com/attestantio/go-eth2-client/api"
20+
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
21+
)
22+
23+
// NodeIdentity provides the identity information of the node.
24+
func (s *Service) NodeIdentity(ctx context.Context,
25+
opts *api.NodeIdentityOpts,
26+
) (
27+
*api.Response[*apiv1.NodeIdentity],
28+
error,
29+
) {
30+
if s.NodeIdentityFunc != nil {
31+
return s.NodeIdentityFunc(ctx, opts)
32+
}
33+
34+
return &api.Response[*apiv1.NodeIdentity]{
35+
Data: &apiv1.NodeIdentity{
36+
PeerID: "16Uiu2HAmMockPeerID",
37+
Enr: "enr:-mock-enr-value",
38+
P2PAddresses: []string{"/ip4/127.0.0.1/tcp/9000"},
39+
DiscoveryAddresses: []string{"/ip4/127.0.0.1/udp/9000"},
40+
Metadata: map[string]string{
41+
"seq_number": "1",
42+
"attnets": "0xffffffffffffffff",
43+
},
44+
},
45+
}, nil
46+
}

mock/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ type Service struct {
6868
ForkFunc func(context.Context, *api.ForkOpts) (*api.Response[*phase0.Fork], error)
6969
ForkScheduleFunc func(context.Context, *api.ForkScheduleOpts) (*api.Response[[]*phase0.Fork], error)
7070
GenesisFunc func(context.Context, *api.GenesisOpts) (*api.Response[*apiv1.Genesis], error)
71+
NodeIdentityFunc func(context.Context, *api.NodeIdentityOpts) (*api.Response[*apiv1.NodeIdentity], error)
7172
NodePeersFunc func(context.Context, *api.NodePeersOpts) (*api.Response[[]*apiv1.Peer], error)
7273
NodePeerCountFunc func(context.Context, *api.NodePeerCountOpts) (*api.Response[*apiv1.PeerCount], error)
7374
NodeSyncingFunc func(context.Context, *api.NodeSyncingOpts) (*api.Response[*apiv1.SyncState], error)

multi/nodeidentity.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright © 2026 Attestant Limited.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package multi
15+
16+
import (
17+
"context"
18+
19+
consensusclient "github.com/attestantio/go-eth2-client"
20+
"github.com/attestantio/go-eth2-client/api"
21+
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
22+
)
23+
24+
// NodeIdentity provides the identity information of the node.
25+
func (s *Service) NodeIdentity(ctx context.Context, opts *api.NodeIdentityOpts) (*api.Response[*apiv1.NodeIdentity], error) {
26+
res, err := s.doCall(ctx, func(ctx context.Context, client consensusclient.Service) (any, error) {
27+
nodeIdentity, err := client.(consensusclient.NodeIdentityProvider).NodeIdentity(ctx, opts)
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
return nodeIdentity, nil
33+
}, nil)
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
response, isResponse := res.(*api.Response[*apiv1.NodeIdentity])
39+
if !isResponse {
40+
return nil, ErrIncorrectType
41+
}
42+
43+
return response, nil
44+
}

service.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,16 @@ type GenesisProvider interface {
526526
error,
527527
)
528528
}
529-
529+
// NodeIdentityProvider is the interface for providing node identity information.
530+
type NodeIdentityProvider interface {
531+
// NodeIdentity provides the identity information of the node.
532+
NodeIdentity(ctx context.Context,
533+
opts *api.NodeIdentityOpts,
534+
) (
535+
*api.Response[*apiv1.NodeIdentity],
536+
error,
537+
)
538+
}
530539
// NodePeersProvider is the interface for providing peer information.
531540
type NodePeersProvider interface {
532541
// NodePeers provides the peers of the node.

testclients/erroring.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,24 @@ func (s *Erroring) SlotFromStateID(ctx context.Context, stateID string) (phase0.
126126
return next.SlotFromStateID(ctx, stateID)
127127
}
128128

129+
// NodeIdentity provides the identity information of the node.
130+
func (s *Erroring) NodeIdentity(ctx context.Context,
131+
opts *api.NodeIdentityOpts,
132+
) (
133+
*api.Response[*apiv1.NodeIdentity],
134+
error,
135+
) {
136+
if err := s.maybeError(ctx); err != nil {
137+
return nil, err
138+
}
139+
next, isNext := s.next.(consensusclient.NodeIdentityProvider)
140+
if !isNext {
141+
return nil, fmt.Errorf("%s@%s does not support this call", s.next.Name(), s.next.Address())
142+
}
143+
144+
return next.NodeIdentity(ctx, opts)
145+
}
146+
129147
// NodeVersion returns a free-text string with the node version.
130148
func (s *Erroring) NodeVersion(ctx context.Context,
131149
opts *api.NodeVersionOpts,

testclients/sleepy.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,22 @@ func (s *Sleepy) SlotFromStateID(ctx context.Context, stateID string) (phase0.Sl
119119
return next.SlotFromStateID(ctx, stateID)
120120
}
121121

122+
// NodeIdentity provides the identity information of the node.
123+
func (s *Sleepy) NodeIdentity(ctx context.Context,
124+
opts *api.NodeIdentityOpts,
125+
) (
126+
*api.Response[*apiv1.NodeIdentity],
127+
error,
128+
) {
129+
s.sleep(ctx)
130+
next, isNext := s.next.(consensusclient.NodeIdentityProvider)
131+
if !isNext {
132+
return nil, errors.New("next does not support this call")
133+
}
134+
135+
return next.NodeIdentity(ctx, opts)
136+
}
137+
122138
// NodeVersion returns a free-text string with the node version.
123139
func (s *Sleepy) NodeVersion(ctx context.Context,
124140
opts *api.NodeVersionOpts,

0 commit comments

Comments
 (0)