github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/node/api.go (about) 1 // Copyright 2015 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The Spectrum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the Spectrum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package node 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "time" 24 25 "github.com/SmartMeshFoundation/Spectrum/common/hexutil" 26 "github.com/SmartMeshFoundation/Spectrum/crypto" 27 "github.com/SmartMeshFoundation/Spectrum/p2p" 28 "github.com/SmartMeshFoundation/Spectrum/p2p/discover" 29 "github.com/SmartMeshFoundation/Spectrum/rpc" 30 "github.com/rcrowley/go-metrics" 31 ) 32 33 // PrivateAdminAPI is the collection of administrative API methods exposed only 34 // over a secure RPC channel. 35 type PrivateAdminAPI struct { 36 node *Node // Node interfaced by this API 37 } 38 39 // NewPrivateAdminAPI creates a new API definition for the private admin methods 40 // of the node itself. 41 func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI { 42 return &PrivateAdminAPI{node: node} 43 } 44 45 // AddPeer requests connecting to a remote node, and also maintaining the new 46 // connection at all times, even reconnecting if it is lost. 47 func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { 48 // Make sure the server is running, fail otherwise 49 server := api.node.Server() 50 if server == nil { 51 return false, ErrNodeStopped 52 } 53 // Try to add the url as a static peer and return 54 node, err := discover.ParseNode(url) 55 if err != nil { 56 return false, fmt.Errorf("invalid enode: %v", err) 57 } 58 server.AddPeer(node) 59 return true, nil 60 } 61 62 // RemovePeer disconnects from a a remote node if the connection exists 63 func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { 64 // Make sure the server is running, fail otherwise 65 server := api.node.Server() 66 if server == nil { 67 return false, ErrNodeStopped 68 } 69 // Try to remove the url as a static peer and return 70 node, err := discover.ParseNode(url) 71 if err != nil { 72 return false, fmt.Errorf("invalid enode: %v", err) 73 } 74 server.RemovePeer(node) 75 return true, nil 76 } 77 78 // PeerEvents creates an RPC subscription which receives peer events from the 79 // node's p2p.Server 80 func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { 81 // Make sure the server is running, fail otherwise 82 server := api.node.Server() 83 if server == nil { 84 return nil, ErrNodeStopped 85 } 86 87 // Create the subscription 88 notifier, supported := rpc.NotifierFromContext(ctx) 89 if !supported { 90 return nil, rpc.ErrNotificationsUnsupported 91 } 92 rpcSub := notifier.CreateSubscription() 93 94 go func() { 95 events := make(chan *p2p.PeerEvent) 96 sub := server.SubscribeEvents(events) 97 defer sub.Unsubscribe() 98 99 for { 100 select { 101 case event := <-events: 102 notifier.Notify(rpcSub.ID, event) 103 case <-sub.Err(): 104 return 105 case <-rpcSub.Err(): 106 return 107 case <-notifier.Closed(): 108 return 109 } 110 } 111 }() 112 113 return rpcSub, nil 114 } 115 116 // StartRPC starts the HTTP RPC API server. 117 func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string) (bool, error) { 118 api.node.lock.Lock() 119 defer api.node.lock.Unlock() 120 121 if api.node.httpHandler != nil { 122 return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint) 123 } 124 125 if host == nil { 126 h := DefaultHTTPHost 127 if api.node.config.HTTPHost != "" { 128 h = api.node.config.HTTPHost 129 } 130 host = &h 131 } 132 if port == nil { 133 port = &api.node.config.HTTPPort 134 } 135 136 allowedOrigins := api.node.config.HTTPCors 137 if cors != nil { 138 allowedOrigins = nil 139 for _, origin := range strings.Split(*cors, ",") { 140 allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin)) 141 } 142 } 143 144 modules := api.node.httpWhitelist 145 if apis != nil { 146 modules = nil 147 for _, m := range strings.Split(*apis, ",") { 148 modules = append(modules, strings.TrimSpace(m)) 149 } 150 } 151 152 if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins); err != nil { 153 return false, err 154 } 155 return true, nil 156 } 157 158 // StopRPC terminates an already running HTTP RPC API endpoint. 159 func (api *PrivateAdminAPI) StopRPC() (bool, error) { 160 api.node.lock.Lock() 161 defer api.node.lock.Unlock() 162 163 if api.node.httpHandler == nil { 164 return false, fmt.Errorf("HTTP RPC not running") 165 } 166 api.node.stopHTTP() 167 return true, nil 168 } 169 170 // StartWS starts the websocket RPC API server. 171 func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { 172 api.node.lock.Lock() 173 defer api.node.lock.Unlock() 174 175 if api.node.wsHandler != nil { 176 return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint) 177 } 178 179 if host == nil { 180 h := DefaultWSHost 181 if api.node.config.WSHost != "" { 182 h = api.node.config.WSHost 183 } 184 host = &h 185 } 186 if port == nil { 187 port = &api.node.config.WSPort 188 } 189 190 origins := api.node.config.WSOrigins 191 if allowedOrigins != nil { 192 origins = nil 193 for _, origin := range strings.Split(*allowedOrigins, ",") { 194 origins = append(origins, strings.TrimSpace(origin)) 195 } 196 } 197 198 modules := api.node.config.WSModules 199 if apis != nil { 200 modules = nil 201 for _, m := range strings.Split(*apis, ",") { 202 modules = append(modules, strings.TrimSpace(m)) 203 } 204 } 205 206 if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, origins, api.node.config.WSExposeAll); err != nil { 207 return false, err 208 } 209 return true, nil 210 } 211 212 // StopRPC terminates an already running websocket RPC API endpoint. 213 func (api *PrivateAdminAPI) StopWS() (bool, error) { 214 api.node.lock.Lock() 215 defer api.node.lock.Unlock() 216 217 if api.node.wsHandler == nil { 218 return false, fmt.Errorf("WebSocket RPC not running") 219 } 220 api.node.stopWS() 221 return true, nil 222 } 223 224 // PublicAdminAPI is the collection of administrative API methods exposed over 225 // both secure and unsecure RPC channels. 226 type PublicAdminAPI struct { 227 node *Node // Node interfaced by this API 228 } 229 230 // NewPublicAdminAPI creates a new API definition for the public admin methods 231 // of the node itself. 232 func NewPublicAdminAPI(node *Node) *PublicAdminAPI { 233 return &PublicAdminAPI{node: node} 234 } 235 236 // Peers retrieves all the information we know about each individual peer at the 237 // protocol granularity. 238 func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { 239 server := api.node.Server() 240 if server == nil { 241 return nil, ErrNodeStopped 242 } 243 return server.PeersInfo(), nil 244 } 245 246 // NodeInfo retrieves all the information we know about the host node at the 247 // protocol granularity. 248 func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { 249 server := api.node.Server() 250 if server == nil { 251 return nil, ErrNodeStopped 252 } 253 return server.NodeInfo(), nil 254 } 255 256 // Datadir retrieves the current data directory the node is using. 257 func (api *PublicAdminAPI) Datadir() string { 258 return api.node.DataDir() 259 } 260 261 // PublicDebugAPI is the collection of debugging related API methods exposed over 262 // both secure and unsecure RPC channels. 263 type PublicDebugAPI struct { 264 node *Node // Node interfaced by this API 265 } 266 267 // NewPublicDebugAPI creates a new API definition for the public debug methods 268 // of the node itself. 269 func NewPublicDebugAPI(node *Node) *PublicDebugAPI { 270 return &PublicDebugAPI{node: node} 271 } 272 273 // Metrics retrieves all the known system metric collected by the node. 274 func (api *PublicDebugAPI) Metrics(raw bool) (map[string]interface{}, error) { 275 // Create a rate formatter 276 units := []string{"", "K", "M", "G", "T", "E", "P"} 277 round := func(value float64, prec int) string { 278 unit := 0 279 for value >= 1000 { 280 unit, value, prec = unit+1, value/1000, 2 281 } 282 return fmt.Sprintf(fmt.Sprintf("%%.%df%s", prec, units[unit]), value) 283 } 284 format := func(total float64, rate float64) string { 285 return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2)) 286 } 287 // Iterate over all the metrics, and just dump for now 288 counters := make(map[string]interface{}) 289 metrics.DefaultRegistry.Each(func(name string, metric interface{}) { 290 // Create or retrieve the counter hierarchy for this metric 291 root, parts := counters, strings.Split(name, "/") 292 for _, part := range parts[:len(parts)-1] { 293 if _, ok := root[part]; !ok { 294 root[part] = make(map[string]interface{}) 295 } 296 root = root[part].(map[string]interface{}) 297 } 298 name = parts[len(parts)-1] 299 300 // Fill the counter with the metric details, formatting if requested 301 if raw { 302 switch metric := metric.(type) { 303 case metrics.Meter: 304 root[name] = map[string]interface{}{ 305 "AvgRate01Min": metric.Rate1(), 306 "AvgRate05Min": metric.Rate5(), 307 "AvgRate15Min": metric.Rate15(), 308 "MeanRate": metric.RateMean(), 309 "Overall": float64(metric.Count()), 310 } 311 312 case metrics.Timer: 313 root[name] = map[string]interface{}{ 314 "AvgRate01Min": metric.Rate1(), 315 "AvgRate05Min": metric.Rate5(), 316 "AvgRate15Min": metric.Rate15(), 317 "MeanRate": metric.RateMean(), 318 "Overall": float64(metric.Count()), 319 "Percentiles": map[string]interface{}{ 320 "5": metric.Percentile(0.05), 321 "20": metric.Percentile(0.2), 322 "50": metric.Percentile(0.5), 323 "80": metric.Percentile(0.8), 324 "95": metric.Percentile(0.95), 325 }, 326 } 327 328 default: 329 root[name] = "Unknown metric type" 330 } 331 } else { 332 switch metric := metric.(type) { 333 case metrics.Meter: 334 root[name] = map[string]interface{}{ 335 "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), 336 "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), 337 "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), 338 "Overall": format(float64(metric.Count()), metric.RateMean()), 339 } 340 341 case metrics.Timer: 342 root[name] = map[string]interface{}{ 343 "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), 344 "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), 345 "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), 346 "Overall": format(float64(metric.Count()), metric.RateMean()), 347 "Maximum": time.Duration(metric.Max()).String(), 348 "Minimum": time.Duration(metric.Min()).String(), 349 "Percentiles": map[string]interface{}{ 350 "5": time.Duration(metric.Percentile(0.05)).String(), 351 "20": time.Duration(metric.Percentile(0.2)).String(), 352 "50": time.Duration(metric.Percentile(0.5)).String(), 353 "80": time.Duration(metric.Percentile(0.8)).String(), 354 "95": time.Duration(metric.Percentile(0.95)).String(), 355 }, 356 } 357 358 default: 359 root[name] = "Unknown metric type" 360 } 361 } 362 }) 363 return counters, nil 364 } 365 366 // PublicWeb3API offers helper utils 367 type PublicWeb3API struct { 368 stack *Node 369 } 370 371 // NewPublicWeb3API creates a new Web3Service instance 372 func NewPublicWeb3API(stack *Node) *PublicWeb3API { 373 return &PublicWeb3API{stack} 374 } 375 376 // ClientVersion returns the node name 377 func (s *PublicWeb3API) ClientVersion() string { 378 return s.stack.Server().Name 379 } 380 381 // Sha3 applies the ethereum sha3 implementation on the input. 382 // It assumes the input is hex encoded. 383 func (s *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes { 384 return crypto.Keccak256(input) 385 }