Skip to content

Commit e90cb93

Browse files
authored
feat: add /eth/v1/node/identity endpoint (#1066)
1 parent a9b1d77 commit e90cb93

File tree

12 files changed

+221
-25
lines changed

12 files changed

+221
-25
lines changed

go.work.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h
367367
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
368368
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY=
369369
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
370+
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
370371
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
371372
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
372373
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
@@ -439,6 +440,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
439440
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
440441
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
441442
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
443+
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
442444
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
443445
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
444446
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=

lib/beacon_api/controllers/v1/node_controller.ex

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ defmodule BeaconApi.V1.NodeController do
22
use BeaconApi, :controller
33

44
alias BeaconApi.ApiSpec
5+
alias BeaconApi.Utils
6+
alias LambdaEthereumConsensus.Libp2pPort
7+
alias LambdaEthereumConsensus.P2P.Metadata
58

69
plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true)
710

@@ -10,11 +13,37 @@ defmodule BeaconApi.V1.NodeController do
1013
def open_api_operation(:health),
1114
do: ApiSpec.spec().paths["/eth/v1/node/health"].get
1215

16+
def open_api_operation(:identity),
17+
do: ApiSpec.spec().paths["/eth/v1/node/identity"].get
18+
1319
@spec health(Plug.Conn.t(), any) :: Plug.Conn.t()
1420
def health(conn, params) do
1521
# TODO: respond with syncing status if we're still syncing
1622
_syncing_status = Map.get(params, :syncing_status, 206)
1723

1824
send_resp(conn, 200, "")
1925
end
26+
27+
@spec identity(Plug.Conn.t(), any) :: Plug.Conn.t()
28+
def identity(conn, _params) do
29+
metadata = Metadata.get_metadata() |> Utils.to_json()
30+
31+
%{
32+
pretty_peer_id: peer_id,
33+
enr: enr,
34+
p2p_addresses: p2p_addresses,
35+
discovery_addresses: discovery_addresses
36+
} = Libp2pPort.get_node_identity()
37+
38+
conn
39+
|> json(%{
40+
"data" => %{
41+
"peer_id" => peer_id,
42+
"enr" => enr,
43+
"p2p_addresses" => p2p_addresses,
44+
"discovery_addresses" => discovery_addresses,
45+
"metadata" => metadata
46+
}
47+
})
48+
end
2049
end

lib/beacon_api/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ defmodule BeaconApi.Router do
1919

2020
scope "/node" do
2121
get("/health", NodeController, :health)
22+
get("/identity", NodeController, :identity)
2223
end
2324
end
2425

lib/libp2p_port.ex

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ defmodule LambdaEthereumConsensus.Libp2pPort do
1717
alias Libp2pProto.AddPeer
1818
alias Libp2pProto.Command
1919
alias Libp2pProto.Enr
20-
alias Libp2pProto.GetId
20+
alias Libp2pProto.GetNodeIdentity
2121
alias Libp2pProto.GossipSub
2222
alias Libp2pProto.InitArgs
2323
alias Libp2pProto.JoinTopic
@@ -67,10 +67,10 @@ defmodule LambdaEthereumConsensus.Libp2pPort do
6767
* `:opts` - a Keyword list of options to pass onto the GenServer.
6868
Defaults to `[name: __MODULE__]`.
6969
70-
* `:listen_addr` - the addresses to listen on.
70+
* `:listen_addr` - the addresses to listen on, in `Multiaddr` format.
7171
* `:enable_discovery` - boolean that specifies if the discovery service
7272
should be started.
73-
* `:discovery_addr` - the address used by the discovery service.
73+
* `:discovery_addr` - the address used by the discovery service, in `host:port` format.
7474
* `:bootnodes` - a list of bootnodes to use for discovery.
7575
"""
7676
@spec start_link([{:opts, GenServer.options()} | init_arg()]) :: GenServer.on_start()
@@ -79,15 +79,27 @@ defmodule LambdaEthereumConsensus.Libp2pPort do
7979
GenServer.start_link(__MODULE__, args, opts)
8080
end
8181

82+
@type node_identity() :: %{
83+
peer_id: binary(),
84+
# Pretty-printed version of the peer ID
85+
pretty_peer_id: String.t(),
86+
enr: String.t(),
87+
p2p_addresses: [String.t()],
88+
discovery_addresses: [String.t()]
89+
}
90+
8291
@doc """
83-
Gets the unique ID of the LibP2P node. This ID is used by peers to
84-
identify and connect to it.
92+
Retrieves identity info from the underlying LibP2P node.
8593
"""
86-
@spec get_id(GenServer.server()) :: binary()
87-
def get_id(pid \\ __MODULE__) do
88-
:telemetry.execute([:port, :message], %{}, %{function: "get_id", direction: "elixir->"})
89-
{:ok, id} = call_command(pid, {:get_id, %GetId{}})
90-
id
94+
@spec get_node_identity(GenServer.server()) :: node_identity()
95+
def get_node_identity(pid \\ __MODULE__) do
96+
:telemetry.execute([:port, :message], %{}, %{
97+
function: "get_node_identity",
98+
direction: "elixir->"
99+
})
100+
101+
call_command(pid, {:get_node_identity, %GetNodeIdentity{}})
102+
|> Map.take([:peer_id, :pretty_peer_id, :enr, :p2p_addresses, :discovery_addresses])
91103
end
92104

93105
@doc """
@@ -430,6 +442,7 @@ defmodule LambdaEthereumConsensus.Libp2pPort do
430442

431443
defp receive_response() do
432444
receive do
445+
{:response, {:node_identity, identity}} -> identity
433446
{:response, {res, %ResultMessage{message: []}}} -> res
434447
{:response, {res, %ResultMessage{message: message}}} -> [res | message] |> List.to_tuple()
435448
end

native/libp2p_port/internal/discovery/discovery.go

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package discovery
22

33
import (
44
"bytes"
5-
"crypto/rand"
5+
"encoding/base64"
66
"fmt"
77
"libp2p_port/internal/port"
88
"libp2p_port/internal/proto_helpers"
@@ -13,7 +13,6 @@ import (
1313
"github.com/ethereum/go-ethereum/p2p/discover"
1414
"github.com/ethereum/go-ethereum/p2p/enode"
1515
"github.com/ethereum/go-ethereum/p2p/enr"
16-
"github.com/libp2p/go-libp2p/core/crypto"
1716
"github.com/libp2p/go-libp2p/core/peer"
1817
"github.com/libp2p/go-libp2p/core/peerstore"
1918
ma "github.com/multiformats/go-multiaddr"
@@ -31,10 +30,6 @@ func NewDiscoverer(p *port.Port, listener *reqresp.Listener, config *proto_helpe
3130
utils.PanicIfError(err)
3231
conn, err := net.ListenUDP("udp", udpAddr)
3332
utils.PanicIfError(err)
34-
intPrivKey, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
35-
utils.PanicIfError(err)
36-
privKey, err := utils.ConvertFromInterfacePrivKey(intPrivKey)
37-
utils.PanicIfError(err)
3833

3934
bootnodes := make([]*enode.Node, 0, len(config.Bootnodes))
4035

@@ -45,14 +40,14 @@ func NewDiscoverer(p *port.Port, listener *reqresp.Listener, config *proto_helpe
4540
}
4641

4742
cfg := discover.Config{
48-
PrivateKey: privKey,
43+
PrivateKey: config.Privkey,
4944
Bootnodes: bootnodes, // list of bootstrap nodes
5045
}
5146

5247
db, err := enode.OpenDB("")
5348
utils.PanicIfError(err)
5449

55-
localNode := enode.NewLocalNode(db, privKey)
50+
localNode := enode.NewLocalNode(db, config.Privkey)
5651
localNode.Set(enr.IP(udpAddr.IP))
5752
localNode.Set(enr.UDP(udpAddr.Port))
5853
localNode.Set(enr.TCP(udpAddr.Port))
@@ -149,6 +144,42 @@ func filterPeer(node *enode.Node, currentForkDigest []byte) bool {
149144
return bytes.Equal(currentForkDigest, forkDigest)
150145
}
151146

147+
// SerializeENR takes the enr record in its key-value form and serializes it.
148+
func serializeENR(record *enr.Record) (string, error) {
149+
if record == nil {
150+
return "", errors.New("could not serialize nil record")
151+
}
152+
buf := bytes.NewBuffer([]byte{})
153+
if err := record.EncodeRLP(buf); err != nil {
154+
return "", errors.Wrap(err, "could not encode ENR record to bytes")
155+
}
156+
enrString := base64.RawURLEncoding.EncodeToString(buf.Bytes())
157+
return "enr:" + enrString, nil
158+
}
159+
160+
func (d *Discoverer) GetAddresses() [][]byte {
161+
if d == nil {
162+
return [][]byte{}
163+
}
164+
addrs, err := convertToUdpMultiAddr(d.discv5_service.Self())
165+
utils.PanicIfError(err)
166+
serializedAddresses := make([][]byte, len(addrs))
167+
for i := range addrs {
168+
serializedAddresses[i] = []byte(addrs[i].String())
169+
}
170+
return serializedAddresses
171+
}
172+
173+
func (d *Discoverer) GetEnr() []byte {
174+
if d == nil {
175+
return []byte{}
176+
}
177+
record := d.discv5_service.LocalNode().Node().Record()
178+
enr, err := serializeENR(record)
179+
utils.PanicIfError(err)
180+
return []byte(enr)
181+
}
182+
152183
func (d *Discoverer) UpdateEnr(enr proto_helpers.Enr) {
153184
if d == nil {
154185
return

native/libp2p_port/internal/proto_helpers/proto_helpers.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package proto_helpers
22

33
import (
4+
"crypto/ecdsa"
5+
"crypto/rand"
46
proto_defs "libp2p_port/internal/proto"
7+
"libp2p_port/internal/utils"
8+
9+
"github.com/libp2p/go-libp2p/core/crypto"
510
)
611

712
type Enr struct {
@@ -16,6 +21,7 @@ type Config struct {
1621
DiscoveryAddr string
1722
Bootnodes []string
1823
InitialEnr Enr
24+
Privkey *ecdsa.PrivateKey
1925
}
2026

2127
func ConfigFromInitArgs(initArgs *proto_defs.InitArgs) Config {
@@ -25,9 +31,18 @@ func ConfigFromInitArgs(initArgs *proto_defs.InitArgs) Config {
2531
DiscoveryAddr: initArgs.DiscoveryAddr,
2632
Bootnodes: initArgs.Bootnodes,
2733
InitialEnr: LoadEnr(initArgs.InitialEnr),
34+
Privkey: generatePrivkey(),
2835
}
2936
}
3037

38+
func generatePrivkey() *ecdsa.PrivateKey {
39+
intPrivKey, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
40+
utils.PanicIfError(err)
41+
privKey, err := utils.ConvertFromInterfacePrivKey(intPrivKey)
42+
utils.PanicIfError(err)
43+
return privKey
44+
}
45+
3146
func LoadEnr(enr *proto_defs.Enr) Enr {
3247
return Enr{Eth2: enr.Eth2, Attnets: enr.Attnets, Syncnets: enr.Syncnets}
3348
}
@@ -128,3 +143,8 @@ func ResultNotification(from []byte, result []byte, err error) *proto_defs.Notif
128143
}
129144
return &proto_defs.Notification{N: &proto_defs.Notification_Result{Result: responseNotification}}
130145
}
146+
147+
func NodeIdentityNotification(from []byte, nodeIdentity *proto_defs.NodeIdentity) *proto_defs.Notification {
148+
responseNotification := &proto_defs.Result{From: from, Result: &proto_defs.Result_NodeIdentity{NodeIdentity: nodeIdentity}}
149+
return &proto_defs.Notification{N: &proto_defs.Notification_Result{Result: responseNotification}}
150+
}

native/libp2p_port/internal/reqresp/reqresp.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ type Listener struct {
3131
}
3232

3333
func NewListener(p *port.Port, config *proto_helpers.Config) Listener {
34+
ifaceKey, err := utils.ConvertToInterfacePrivkey(config.Privkey)
35+
utils.PanicIfError(err)
3436
// as per the spec
3537
optionsSlice := []libp2p.Option{
3638
libp2p.DefaultMuxers,
@@ -41,6 +43,7 @@ func NewListener(p *port.Port, config *proto_helpers.Config) Listener {
4143
libp2p.NATPortMap(), // Allow to use UPnP
4244
libp2p.Ping(false),
4345
libp2p.ListenAddrStrings(config.ListenAddr...),
46+
libp2p.Identity(ifaceKey),
4447
}
4548

4649
h, err := libp2p.New(optionsSlice...)
@@ -56,6 +59,16 @@ func (l *Listener) HostId() []byte {
5659
return []byte(l.hostHandle.ID())
5760
}
5861

62+
func (l *Listener) GetAddresses() [][]byte {
63+
peerId := l.hostHandle.ID().String()
64+
listenAddresses := l.Host().Addrs()
65+
p2pAddresses := make([][]byte, len(listenAddresses))
66+
for i := range listenAddresses {
67+
p2pAddresses[i] = []byte(listenAddresses[i].String() + "/p2p/" + peerId)
68+
}
69+
return p2pAddresses
70+
}
71+
5972
func (l *Listener) AddPeer(id []byte, addrs []string, ttl int64) {
6073
addrInfo := peer.AddrInfo{ID: peer.ID(id)}
6174
for _, addr := range addrs {

native/libp2p_port/internal/utils/utils.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ func ConvertFromInterfacePrivKey(privkey crypto.PrivKey) (*ecdsa.PrivateKey, err
3838
return privKey, nil
3939
}
4040

41+
// Taken from Prysm: https://github.com/prysmaticlabs/prysm/blob/bcc23d2ded2548b6bce95680f49899325aedd960/crypto/ecdsa/utils.go
42+
func ConvertToInterfacePrivkey(privkey *ecdsa.PrivateKey) (crypto.PrivKey, error) {
43+
privBytes := privkey.D.Bytes()
44+
// In the event the number of bytes outputted by the big-int are less than 32,
45+
// we append bytes to the start of the sequence for the missing most significant
46+
// bytes.
47+
if len(privBytes) < 32 {
48+
privBytes = append(make([]byte, 32-len(privBytes)), privBytes...)
49+
}
50+
return crypto.UnmarshalSecp256k1PrivateKey(privBytes)
51+
}
52+
4153
// Taken from Prysm: https://github.com/prysmaticlabs/prysm/blob/bcc23d2ded2548b6bce95680f49899325aedd960/crypto/ecdsa/utils.go
4254
func ConvertToInterfacePubkey(pubkey *ecdsa.PublicKey) (crypto.PubKey, error) {
4355
xVal, yVal := new(btcec.FieldVal), new(btcec.FieldVal)

native/libp2p_port/main.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import (
1414

1515
func handleCommand(command *proto_defs.Command, listener *reqresp.Listener, subscriber *gossipsub.Subscriber, discoverer *discovery.Discoverer) *proto_defs.Notification {
1616
switch c := command.C.(type) {
17-
case *proto_defs.Command_GetId:
18-
return proto_helpers.ResultNotification(command.From, []byte(listener.HostId()), nil)
17+
case *proto_defs.Command_GetNodeIdentity:
18+
identity := getNodeIdentity(listener, discoverer)
19+
return proto_helpers.NodeIdentityNotification(command.From, identity)
1920
case *proto_defs.Command_AddPeer:
2021
listener.AddPeer(c.AddPeer.Id, c.AddPeer.Addrs, c.AddPeer.Ttl)
2122
case *proto_defs.Command_SendRequest:
@@ -45,6 +46,18 @@ func handleCommand(command *proto_defs.Command, listener *reqresp.Listener, subs
4546
return proto_helpers.ResultNotification(command.From, nil, nil)
4647
}
4748

49+
func getNodeIdentity(listener *reqresp.Listener, discoverer *discovery.Discoverer) *proto_defs.NodeIdentity {
50+
peerId := listener.HostId()
51+
// TODO: pass only raw peer ID
52+
// Pretty-printed peer ID
53+
prettyPeerId := []byte(listener.Host().ID().String())
54+
enr := discoverer.GetEnr()
55+
p2pAddresses := listener.GetAddresses()
56+
discoveryAddresses := discoverer.GetAddresses()
57+
58+
return &proto_defs.NodeIdentity{PeerId: []byte(peerId), Enr: enr, P2PAddresses: p2pAddresses, DiscoveryAddresses: discoveryAddresses, PrettyPeerId: prettyPeerId}
59+
}
60+
4861
func commandServer() {
4962
portInst := port.NewPort()
5063
initArgs := proto_defs.InitArgs{}

proto/libp2p.proto

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ message Enr {
9797
bytes syncnets = 3;
9898
}
9999

100-
message GetId {}
100+
message GetNodeIdentity {}
101101

102102
message Publish {
103103
string topic = 1;
@@ -124,7 +124,7 @@ message Command {
124124
AddPeer add_peer = 5;
125125
SendRequest send_request = 6;
126126
SendResponse send_response = 7;
127-
GetId get_id = 8;
127+
GetNodeIdentity get_node_identity = 8;
128128
Publish publish = 9;
129129
ValidateMessage validate_message = 10;
130130
Enr update_enr = 11;
@@ -163,12 +163,21 @@ message ResultMessage {
163163
repeated bytes message = 3;
164164
}
165165

166+
message NodeIdentity {
167+
bytes peer_id = 1;
168+
bytes enr = 2;
169+
repeated bytes p2p_addresses = 3;
170+
repeated bytes discovery_addresses = 4;
171+
bytes pretty_peer_id = 5;
172+
}
173+
166174
// A result from a command.
167175
message Result {
168176
bytes from = 1;
169177
oneof result {
170178
ResultMessage ok = 2;
171179
ResultMessage error = 3;
180+
NodeIdentity node_identity = 4;
172181
}
173182
}
174183

0 commit comments

Comments
 (0)