From 61dd1c1052e857cc6d6531f1f5a4bb735d16fcb5 Mon Sep 17 00:00:00 2001 From: Leo Date: Sat, 7 Aug 2021 21:12:08 +0200 Subject: [PATCH] node/cmd: unary list-nodes call with details Change-Id: I9953b45d92461887b075b3456bdd9e161eefd263 --- bridge/cmd/guardiand/adminclient.go | 19 ++++---- bridge/cmd/guardiand/adminnodes.go | 64 +++++++++++++++++++++++++ bridge/go.mod | 1 + bridge/pkg/publicrpc/publicrpcserver.go | 8 ++++ 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/bridge/cmd/guardiand/adminclient.go b/bridge/cmd/guardiand/adminclient.go index ea0ee6ace..a6b629818 100644 --- a/bridge/cmd/guardiand/adminclient.go +++ b/bridge/cmd/guardiand/adminclient.go @@ -4,6 +4,7 @@ import ( "context" "fmt" publicrpcv1 "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1" + "github.com/spf13/pflag" "io/ioutil" "log" "time" @@ -16,25 +17,27 @@ import ( nodev1 "github.com/certusone/wormhole/bridge/pkg/proto/node/v1" ) -var clientSocketPath *string +var ( + clientSocketPath *string +) func init() { - pf := AdminClientInjectGuardianSetUpdateCmd.Flags() + // Shared flags for all admin commands + pf := pflag.NewFlagSet("commonAdminFlags", pflag.ContinueOnError) clientSocketPath = pf.String("socket", "", "gRPC admin server socket to connect to") err := cobra.MarkFlagRequired(pf, "socket") if err != nil { panic(err) } - pf = AdminClientListNodesStream.Flags() - clientSocketPath = pf.String("socket", "", "gRPC admin server socket to connect to") - err = cobra.MarkFlagRequired(pf, "socket") - if err != nil { - panic(err) - } + + AdminClientInjectGuardianSetUpdateCmd.Flags().AddFlagSet(pf) + AdminClientListNodesStream.Flags().AddFlagSet(pf) + AdminClientListNodes.Flags().AddFlagSet(pf) AdminCmd.AddCommand(AdminClientInjectGuardianSetUpdateCmd) AdminCmd.AddCommand(AdminClientGovernanceVAAVerifyCmd) AdminCmd.AddCommand(AdminClientListNodesStream) + AdminCmd.AddCommand(AdminClientListNodes) } var AdminCmd = &cobra.Command{ diff --git a/bridge/cmd/guardiand/adminnodes.go b/bridge/cmd/guardiand/adminnodes.go index 0d44f4735..6f08c1402 100644 --- a/bridge/cmd/guardiand/adminnodes.go +++ b/bridge/cmd/guardiand/adminnodes.go @@ -3,12 +3,16 @@ package guardiand import ( "context" "fmt" + gossipv1 "github.com/certusone/wormhole/bridge/pkg/proto/gossip/v1" publicrpcv1 "github.com/certusone/wormhole/bridge/pkg/proto/publicrpc/v1" + "github.com/certusone/wormhole/bridge/pkg/vaa" "github.com/spf13/cobra" "io" "log" "os" + "sort" "text/tabwriter" + "time" ) // How to test in container: @@ -55,3 +59,63 @@ func runListNodesStream(cmd *cobra.Command, args []string) { seen[hb.GuardianAddr] = true } } + +var AdminClientListNodes = &cobra.Command{ + Use: "list-nodes", + Short: "Fetches an aggregated list of guardian nodes", + Run: runListNodes, +} + +func runListNodes(cmd *cobra.Command, args []string) { + ctx := context.Background() + conn, err, c := getPublicrpcClient(ctx, *clientSocketPath) + defer conn.Close() + if err != nil { + log.Fatalf("failed to get publicrpc client: %v", err) + } + + lastHeartbeats, err := c.GetLastHeartbeats(ctx, &publicrpcv1.GetLastHeartbeatRequest{}) + if err != nil { + log.Fatalf("failed to list nodes: %v", err) + } + + nodes := make([]*gossipv1.Heartbeat, len(lastHeartbeats.RawHeartbeats)) + i := 0 + for _, v := range lastHeartbeats.RawHeartbeats { + nodes[i] = v + i += 1 + } + sort.Slice(nodes, func(i, j int) bool { + return nodes[i].NodeName < nodes[j].NodeName + }) + + log.Printf("%d nodes in guardian state set", len(nodes)) + + w := tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', 0) + + w.Write([]byte("Guardian key\tNode name\tVersion\tLast seen\tUptime\tSolana\tEthereum\tTerra\tBSC\n")) + + for _, h := range nodes { + last := time.Unix(0, h.Timestamp) + + heights := map[vaa.ChainID]int64{} + for _, n := range h.Networks { + heights[vaa.ChainID(n.Id)] = n.Height + } + + fmt.Fprintf(w, + "%s\t%s\t%s\t%s\t%d\t%d\t%d\t%d\t%d\n", + h.GuardianAddr, + h.NodeName, + h.Version, + time.Since(last), + h.Counter, + heights[vaa.ChainIDSolana], + heights[vaa.ChainIDEthereum], + heights[vaa.ChainIDTerra], + heights[vaa.ChainIDBSC], + ) + } + + w.Flush() +} diff --git a/bridge/go.mod b/bridge/go.mod index 79c063352..3b9e4fcd9 100644 --- a/bridge/go.mod +++ b/bridge/go.mod @@ -26,6 +26,7 @@ require ( github.com/near/borsh-go v0.3.0 github.com/prometheus/client_golang v1.10.0 github.com/spf13/cobra v1.1.1 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 github.com/stretchr/testify v1.7.0 diff --git a/bridge/pkg/publicrpc/publicrpcserver.go b/bridge/pkg/publicrpc/publicrpcserver.go index 2ddba8590..fcb73c668 100644 --- a/bridge/pkg/publicrpc/publicrpcserver.go +++ b/bridge/pkg/publicrpc/publicrpcserver.go @@ -47,11 +47,19 @@ func (s *PublicrpcServer) GetLastHeartbeats(ctx context.Context, req *publicrpcv RawHeartbeats: make(map[string]*gossipv1.Heartbeat), } + // Request heartbeat for every guardian set entry. This ensures that + // offline guardians will be listed with a null heartbeat. for _, addr := range gs.Keys { hb := s.gst.LastHeartbeat(addr) resp.RawHeartbeats[addr.Hex()] = hb } + // Fetch all heartbeats (including from nodes not in the guardian set - which + // can happen either with --disableHeartbeatVerify or when the guardian set changes) + for addr, hb := range s.gst.GetAll() { + resp.RawHeartbeats[addr.Hex()] = hb + } + return resp, nil }