Skip to content

Commit e8c5de4

Browse files
mpignatelli12joshua-kim
authored andcommitted
Connectrpc info api
Signed-off-by: Joshua Kim <[email protected]>
1 parent 495e5af commit e8c5de4

File tree

10 files changed

+2778
-56
lines changed

10 files changed

+2778
-56
lines changed
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package connecthandler
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"time"
10+
11+
"connectrpc.com/connect"
12+
"google.golang.org/protobuf/types/known/emptypb"
13+
"google.golang.org/protobuf/types/known/timestamppb"
14+
15+
"github.com/ava-labs/avalanchego/api/info"
16+
"github.com/ava-labs/avalanchego/ids"
17+
"github.com/ava-labs/avalanchego/upgrade"
18+
19+
v1 "github.com/ava-labs/avalanchego/proto/pb/info/v1"
20+
)
21+
22+
// NewConnectInfoService returns a ConnectRPC-compatible InfoServiceHandler
23+
// that delegates calls to the existing Info implementation
24+
func NewConnectInfoService(info *info.Info) *ConnectInfoService {
25+
return &ConnectInfoService{
26+
Info: info,
27+
}
28+
}
29+
30+
type ConnectInfoService struct {
31+
*info.Info
32+
}
33+
34+
// GetNodeVersion returns the semantic version, database version, RPC protocol version,
35+
// Git commit hash, and the list of VM versions this node is running
36+
func (s *ConnectInfoService) GetNodeVersion(
37+
_ context.Context,
38+
_ *connect.Request[emptypb.Empty],
39+
) (*connect.Response[v1.GetNodeVersionReply], error) {
40+
var jsonReply info.GetNodeVersionReply
41+
if err := s.Info.GetNodeVersion(nil, nil, &jsonReply); err != nil {
42+
return nil, connect.NewError(connect.CodeInternal, err)
43+
}
44+
45+
// Convert VM versions map to protobuf format
46+
vmVersions := make(map[string]string)
47+
for id, version := range jsonReply.VMVersions {
48+
vmVersions[id] = version
49+
}
50+
51+
reply := &v1.GetNodeVersionReply{
52+
Version: jsonReply.Version,
53+
DatabaseVersion: jsonReply.DatabaseVersion,
54+
RpcProtocolVersion: uint32(jsonReply.RPCProtocolVersion),
55+
GitCommit: jsonReply.GitCommit,
56+
VmVersions: vmVersions,
57+
}
58+
59+
return connect.NewResponse(reply), nil
60+
}
61+
62+
// GetNodeID returns this node's unique identifier and proof-of-possession bytes
63+
func (s *ConnectInfoService) GetNodeID(
64+
_ context.Context,
65+
_ *connect.Request[emptypb.Empty],
66+
) (*connect.Response[v1.GetNodeIDReply], error) {
67+
var jsonReply info.GetNodeIDReply
68+
if err := s.Info.GetNodeID(nil, nil, &jsonReply); err != nil {
69+
return nil, connect.NewError(connect.CodeInternal, err)
70+
}
71+
72+
nodePOP := []byte{}
73+
if jsonReply.NodePOP != nil {
74+
// Use Marshal to serialize ProofOfPossession to bytes
75+
// MarshalJSON is not ideal here. Ideally, we would use a binary serialization method (MarshalBinary, Marshal, etc.)
76+
var err error
77+
nodePOP, err = jsonReply.NodePOP.MarshalJSON()
78+
if err != nil {
79+
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to marshal NodePOP: %w", err))
80+
}
81+
}
82+
83+
reply := &v1.GetNodeIDReply{
84+
NodeId: jsonReply.NodeID.String(),
85+
NodePop: nodePOP,
86+
}
87+
88+
return connect.NewResponse(reply), nil
89+
}
90+
91+
// GetNodeIP returns the primary IP address this node uses for P2P networking.\
92+
func (s *ConnectInfoService) GetNodeIP(
93+
_ context.Context,
94+
_ *connect.Request[emptypb.Empty],
95+
) (*connect.Response[v1.GetNodeIPReply], error) {
96+
var jsonReply info.GetNodeIPReply
97+
if err := s.Info.GetNodeIP(nil, nil, &jsonReply); err != nil {
98+
return nil, connect.NewError(connect.CodeInternal, err)
99+
}
100+
101+
reply := &v1.GetNodeIPReply{
102+
Ip: jsonReply.IP.String(),
103+
}
104+
105+
return connect.NewResponse(reply), nil
106+
}
107+
108+
// GetNetworkID returns the numeric ID of the Avalanche network this node is connected to
109+
func (s *ConnectInfoService) GetNetworkID(
110+
_ context.Context,
111+
_ *connect.Request[emptypb.Empty],
112+
) (*connect.Response[v1.GetNetworkIDReply], error) {
113+
var jsonReply info.GetNetworkIDReply
114+
if err := s.Info.GetNetworkID(nil, nil, &jsonReply); err != nil {
115+
return nil, connect.NewError(connect.CodeInternal, err)
116+
}
117+
118+
reply := &v1.GetNetworkIDReply{
119+
NetworkId: uint32(jsonReply.NetworkID),
120+
}
121+
122+
return connect.NewResponse(reply), nil
123+
}
124+
125+
// GetNetworkName returns the name of the network
126+
func (s *ConnectInfoService) GetNetworkName(
127+
_ context.Context,
128+
_ *connect.Request[emptypb.Empty],
129+
) (*connect.Response[v1.GetNetworkNameReply], error) {
130+
var jsonReply info.GetNetworkNameReply
131+
if err := s.Info.GetNetworkName(nil, nil, &jsonReply); err != nil {
132+
return nil, connect.NewError(connect.CodeInternal, err)
133+
}
134+
135+
reply := &v1.GetNetworkNameReply{
136+
NetworkName: jsonReply.NetworkName,
137+
}
138+
139+
return connect.NewResponse(reply), nil
140+
}
141+
142+
// GetBlockchainID maps an ID string to its canonical chain ID
143+
func (s *ConnectInfoService) GetBlockchainID(
144+
_ context.Context,
145+
req *connect.Request[v1.GetBlockchainIDArgs],
146+
) (*connect.Response[v1.GetBlockchainIDReply], error) {
147+
jsonArgs := info.GetBlockchainIDArgs{
148+
Alias: req.Msg.Alias,
149+
}
150+
151+
var jsonReply info.GetBlockchainIDReply
152+
if err := s.Info.GetBlockchainID(nil, &jsonArgs, &jsonReply); err != nil {
153+
return nil, connect.NewError(connect.CodeInternal, err)
154+
}
155+
156+
reply := &v1.GetBlockchainIDReply{
157+
BlockchainId: jsonReply.BlockchainID.String(),
158+
}
159+
160+
return connect.NewResponse(reply), nil
161+
}
162+
163+
// Peers returns metadata (IP, nodeID, version, uptimes, subnets, etc.) for the given peer node IDs
164+
func (s *ConnectInfoService) Peers(
165+
_ context.Context,
166+
req *connect.Request[v1.PeersArgs],
167+
) (*connect.Response[v1.PeersReply], error) {
168+
nodeIDs := make([]ids.NodeID, 0, len(req.Msg.NodeIds))
169+
for _, nodeIDStr := range req.Msg.NodeIds {
170+
nodeID, err := ids.NodeIDFromString(nodeIDStr)
171+
if err != nil {
172+
return nil, connect.NewError(
173+
connect.CodeInvalidArgument, fmt.Errorf("invalid nodeID %s: %w", nodeIDStr, err))
174+
}
175+
nodeIDs = append(nodeIDs, nodeID)
176+
}
177+
178+
jsonArgs := info.PeersArgs{
179+
NodeIDs: nodeIDs,
180+
}
181+
182+
var jsonReply info.PeersReply
183+
if err := s.Info.Peers(nil, &jsonArgs, &jsonReply); err != nil {
184+
return nil, connect.NewError(connect.CodeInternal, err)
185+
}
186+
187+
peers := make([]*v1.PeerInfo, 0, len(jsonReply.Peers))
188+
for _, peer := range jsonReply.Peers {
189+
// Convert TrackedSubnets (set.Set[ids.ID]) to []string
190+
trackedSubnetsIDs := peer.TrackedSubnets.List()
191+
trackedSubnets := make([]string, len(trackedSubnetsIDs))
192+
for i, id := range trackedSubnetsIDs {
193+
trackedSubnets[i] = id.String()
194+
}
195+
196+
benched := make([]string, len(peer.Benched))
197+
copy(benched, peer.Benched)
198+
199+
peers = append(peers, &v1.PeerInfo{
200+
Ip: peer.IP.String(),
201+
PublicIp: peer.PublicIP.String(),
202+
NodeId: peer.ID.String(),
203+
Version: peer.Version,
204+
LastSent: formatTime(peer.LastSent),
205+
LastReceived: formatTime(peer.LastReceived),
206+
Benched: benched,
207+
ObservedUptime: uint32(peer.ObservedUptime),
208+
TrackedSubnets: trackedSubnets,
209+
})
210+
}
211+
212+
reply := &v1.PeersReply{
213+
NumPeers: uint32(jsonReply.NumPeers),
214+
Peers: peers,
215+
}
216+
217+
return connect.NewResponse(reply), nil
218+
}
219+
220+
// Helper function to format time
221+
func formatTime(t time.Time) string {
222+
return t.Format(time.RFC3339)
223+
}
224+
225+
// IsBootstrapped returns whether the named chain has finished its bootstrap process on this node
226+
func (s *ConnectInfoService) IsBootstrapped(
227+
_ context.Context,
228+
req *connect.Request[v1.IsBootstrappedArgs],
229+
) (*connect.Response[v1.IsBootstrappedResponse], error) {
230+
// Use the chain from the request
231+
jsonArgs := info.IsBootstrappedArgs{
232+
Chain: req.Msg.Chain,
233+
}
234+
235+
var jsonReply info.IsBootstrappedResponse
236+
if err := s.Info.IsBootstrapped(nil, &jsonArgs, &jsonReply); err != nil {
237+
return nil, connect.NewError(connect.CodeInternal, err)
238+
}
239+
240+
reply := &v1.IsBootstrappedResponse{
241+
IsBootstrapped: jsonReply.IsBootstrapped,
242+
}
243+
244+
return connect.NewResponse(reply), nil
245+
}
246+
247+
// Upgrades returns all the scheduled upgrade activation times and parameters for this node
248+
func (s *ConnectInfoService) Upgrades(
249+
_ context.Context,
250+
_ *connect.Request[emptypb.Empty],
251+
) (*connect.Response[v1.UpgradesReply], error) {
252+
var config upgrade.Config
253+
if err := s.Info.Upgrades(nil, nil, &config); err != nil {
254+
return nil, connect.NewError(connect.CodeInternal, err)
255+
}
256+
257+
reply := &v1.UpgradesReply{
258+
ApricotPhase1Time: timestamppb.New(config.ApricotPhase1Time),
259+
ApricotPhase2Time: timestamppb.New(config.ApricotPhase2Time),
260+
ApricotPhase3Time: timestamppb.New(config.ApricotPhase3Time),
261+
ApricotPhase4Time: timestamppb.New(config.ApricotPhase4Time),
262+
ApricotPhase4MinPChainHeight: config.ApricotPhase4MinPChainHeight,
263+
ApricotPhase5Time: timestamppb.New(config.ApricotPhase5Time),
264+
ApricotPhasePre6Time: timestamppb.New(config.ApricotPhasePre6Time),
265+
ApricotPhase6Time: timestamppb.New(config.ApricotPhase6Time),
266+
ApricotPhasePost6Time: timestamppb.New(config.ApricotPhasePost6Time),
267+
BanffTime: timestamppb.New(config.BanffTime),
268+
CortinaTime: timestamppb.New(config.CortinaTime),
269+
CortinaXChainStopVertexId: config.CortinaXChainStopVertexID.String(),
270+
DurangoTime: timestamppb.New(config.DurangoTime),
271+
EtnaTime: timestamppb.New(config.EtnaTime),
272+
FortunaTime: timestamppb.New(config.FortunaTime),
273+
GraniteTime: timestamppb.New(config.GraniteTime),
274+
}
275+
276+
return connect.NewResponse(reply), nil
277+
}
278+
279+
// Uptime returns this node's uptime metrics (rewarding stake %, weighted average %, etc.)
280+
func (s *ConnectInfoService) Uptime(
281+
_ context.Context,
282+
_ *connect.Request[emptypb.Empty],
283+
) (*connect.Response[v1.UptimeResponse], error) {
284+
var jsonReply info.UptimeResponse
285+
if err := s.Info.Uptime(nil, nil, &jsonReply); err != nil {
286+
return nil, connect.NewError(connect.CodeInternal, err)
287+
}
288+
289+
reply := &v1.UptimeResponse{
290+
RewardingStakePercentage: float64(jsonReply.RewardingStakePercentage),
291+
WeightedAveragePercentage: float64(jsonReply.WeightedAveragePercentage),
292+
}
293+
294+
return connect.NewResponse(reply), nil
295+
}
296+
297+
// GetVMs returns a map of VM IDs to their known aliases, plus FXs information
298+
func (s *ConnectInfoService) GetVMs(
299+
_ context.Context,
300+
_ *connect.Request[emptypb.Empty],
301+
) (*connect.Response[v1.GetVMsReply], error) {
302+
var jsonReply info.GetVMsReply
303+
if err := s.Info.GetVMs(nil, nil, &jsonReply); err != nil {
304+
return nil, connect.NewError(connect.CodeInternal, err)
305+
}
306+
307+
// Convert the VM map from JSON-RPC format to protobuf format
308+
vms := make(map[string]*v1.VMAliases)
309+
for vmID, aliases := range jsonReply.VMs {
310+
vms[vmID.String()] = &v1.VMAliases{
311+
Aliases: aliases,
312+
}
313+
}
314+
315+
// Convert the FXs map from JSON-RPC format to protobuf format
316+
fxs := make(map[string]string)
317+
for fxID, name := range jsonReply.Fxs {
318+
fxs[fxID.String()] = name
319+
}
320+
321+
reply := &v1.GetVMsReply{
322+
Vms: vms,
323+
Fxs: fxs,
324+
}
325+
326+
return connect.NewResponse(reply), nil
327+
}

0 commit comments

Comments
 (0)