adminnodes.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package guardiand
  2. import (
  3. "context"
  4. "fmt"
  5. publicrpcv1 "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1"
  6. "github.com/certusone/wormhole/node/pkg/vaa"
  7. "github.com/spf13/cobra"
  8. "log"
  9. "os"
  10. "sort"
  11. "strings"
  12. "text/tabwriter"
  13. "time"
  14. )
  15. // How to test in container:
  16. // kubectl exec guardian-0 -- /guardiand admin list-nodes --socket /tmp/admin.sock
  17. var (
  18. showDetails bool
  19. only []string
  20. )
  21. func init() {
  22. AdminClientListNodes.Flags().BoolVar(&showDetails, "showDetails", false, "Show error counter and contract addresses")
  23. AdminClientListNodes.Flags().StringSliceVar(&only, "only", nil, "Show only networks with the given name")
  24. }
  25. var AdminClientListNodes = &cobra.Command{
  26. Use: "list-nodes",
  27. Short: "Fetches an aggregated list of guardian nodes",
  28. Run: runListNodes,
  29. }
  30. func runListNodes(cmd *cobra.Command, args []string) {
  31. ctx := context.Background()
  32. conn, err, c := getPublicRPCServiceClient(ctx, *clientSocketPath)
  33. defer conn.Close()
  34. if err != nil {
  35. log.Fatalf("failed to get publicrpc client: %v", err)
  36. }
  37. lastHeartbeats, err := c.GetLastHeartbeats(ctx, &publicrpcv1.GetLastHeartbeatsRequest{})
  38. if err != nil {
  39. log.Fatalf("failed to list nodes: %v", err)
  40. }
  41. gs, err := c.GetCurrentGuardianSet(ctx, &publicrpcv1.GetCurrentGuardianSetRequest{})
  42. if err != nil {
  43. log.Fatalf("failed to list current guardian get: %v", err)
  44. }
  45. log.Printf("current guardian set index: %d (%d guardians)",
  46. gs.GuardianSet.Index, len(gs.GuardianSet.Addresses))
  47. nodes := lastHeartbeats.Entries
  48. sort.Slice(nodes, func(i, j int) bool {
  49. if nodes[i].RawHeartbeat == nil || nodes[j].RawHeartbeat == nil {
  50. return false
  51. }
  52. return nodes[i].RawHeartbeat.NodeName < nodes[j].RawHeartbeat.NodeName
  53. })
  54. log.Printf("%d nodes in guardian state set", len(nodes))
  55. // Check if any node is sending Ropsten metrics
  56. var isTestnet bool
  57. for _, node := range nodes {
  58. for _, network := range node.RawHeartbeat.Networks {
  59. if vaa.ChainID(network.Id) == vaa.ChainIDEthereumRopsten {
  60. isTestnet = true
  61. }
  62. }
  63. }
  64. w := tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', 0)
  65. headers := []string{
  66. "Node key",
  67. "Guardian key",
  68. "Node name",
  69. "Version",
  70. "Last seen",
  71. }
  72. if showDetails {
  73. headers = append(headers, "Uptime")
  74. }
  75. type network struct {
  76. string
  77. vaa.ChainID
  78. }
  79. networks := []network{
  80. {"Solana", vaa.ChainIDSolana},
  81. {"Ethereum", vaa.ChainIDEthereum},
  82. {"Terra", vaa.ChainIDTerra},
  83. {"BSC", vaa.ChainIDBSC},
  84. {"Polygon", vaa.ChainIDPolygon},
  85. {"Avalanche", vaa.ChainIDAvalanche},
  86. }
  87. if isTestnet {
  88. networks = append(networks, network{"Ropsten", vaa.ChainIDEthereumRopsten})
  89. }
  90. if len(only) > 0 {
  91. var filtered []network
  92. for _, network := range networks {
  93. for _, name := range only {
  94. if strings.EqualFold(network.string, name) {
  95. filtered = append(filtered, network)
  96. }
  97. }
  98. }
  99. networks = filtered
  100. }
  101. for _, k := range networks {
  102. headers = append(headers, k.string)
  103. }
  104. for _, header := range headers {
  105. _, _ = fmt.Fprintf(w, "%s\t", header)
  106. }
  107. _, _ = fmt.Fprintln(w)
  108. for _, h := range nodes {
  109. if h.RawHeartbeat == nil {
  110. continue
  111. }
  112. last := time.Unix(0, h.RawHeartbeat.Timestamp)
  113. boot := time.Unix(0, h.RawHeartbeat.BootTimestamp)
  114. heights := map[vaa.ChainID]int64{}
  115. truncAddrs := make(map[vaa.ChainID]string)
  116. errors := map[vaa.ChainID]uint64{}
  117. for _, n := range h.RawHeartbeat.Networks {
  118. heights[vaa.ChainID(n.Id)] = n.Height
  119. errors[vaa.ChainID(n.Id)] = n.ErrorCount
  120. if len(n.ContractAddress) >= 16 {
  121. truncAddrs[vaa.ChainID(n.Id)] = n.ContractAddress[:16]
  122. } else {
  123. truncAddrs[vaa.ChainID(n.Id)] = "INVALID"
  124. }
  125. }
  126. fields := []string{
  127. h.P2PNodeAddr,
  128. h.RawHeartbeat.GuardianAddr,
  129. h.RawHeartbeat.NodeName,
  130. h.RawHeartbeat.Version,
  131. time.Since(last).String(),
  132. }
  133. if showDetails {
  134. fields = append(fields, time.Since(boot).String())
  135. }
  136. for _, n := range networks {
  137. if showDetails {
  138. fields = append(fields, fmt.Sprintf("%s %d (%d)",
  139. truncAddrs[n.ChainID], heights[n.ChainID], errors[n.ChainID]))
  140. } else {
  141. fields = append(fields, fmt.Sprintf("%d", heights[n.ChainID]))
  142. }
  143. }
  144. for _, field := range fields {
  145. _, _ = fmt.Fprintf(w, "%s\t", field)
  146. }
  147. _, _ = fmt.Fprintln(w)
  148. }
  149. w.Flush()
  150. fmt.Print("\n")
  151. for _, addr := range gs.GuardianSet.Addresses {
  152. var found bool
  153. for _, h := range nodes {
  154. if h.VerifiedGuardianAddr == addr {
  155. found = true
  156. }
  157. }
  158. if !found {
  159. fmt.Printf("Missing guardian: %s\n", addr)
  160. }
  161. }
  162. fmt.Println("\n[do not parse - use the gRPC or REST API for scripting]")
  163. }