github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/ethstats/ethstats.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:38</date> 10 //</624450090668396544> 11 12 13 //包ethstats实现网络状态报告服务。 14 package ethstats 15 16 import ( 17 "context" 18 "encoding/json" 19 "errors" 20 "fmt" 21 "math/big" 22 "net" 23 "regexp" 24 "runtime" 25 "strconv" 26 "strings" 27 "time" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/common/mclock" 31 "github.com/ethereum/go-ethereum/consensus" 32 "github.com/ethereum/go-ethereum/core" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/eth" 35 "github.com/ethereum/go-ethereum/event" 36 "github.com/ethereum/go-ethereum/les" 37 "github.com/ethereum/go-ethereum/log" 38 "github.com/ethereum/go-ethereum/p2p" 39 "github.com/ethereum/go-ethereum/rpc" 40 "golang.org/x/net/websocket" 41 ) 42 43 const ( 44 //HistoryUpdateRange是节点登录时应报告的块数,或者 45 //历史要求。 46 historyUpdateRange = 50 47 48 //txchanSize是侦听newtxSevent的频道的大小。 49 //该数字是根据Tx池的大小引用的。 50 txChanSize = 4096 51 //ChainHeadChansize是侦听ChainHeadEvent的通道的大小。 52 chainHeadChanSize = 10 53 ) 54 55 type txPool interface { 56 //subscribenewtxsevent应返回的事件订阅 57 //newtxSevent并将事件发送到给定的通道。 58 SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription 59 } 60 61 type blockChain interface { 62 SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription 63 } 64 65 //服务实现一个以太坊netstats报告守护进程,该守护进程将本地 66 //将统计信息链接到监控服务器。 67 type Service struct { 68 server *p2p.Server //对等服务器检索网络信息 69 eth *eth.Ethereum //如果监视完整节点,则提供完整的以太坊服务 70 les *les.LightEthereum //光以太坊服务,如果监控光节点 71 engine consensus.Engine //用于检索可变块字段的协商引擎 72 73 node string //要在监视页上显示的节点的名称 74 pass string //Password to authorize access to the monitoring page 75 host string //监控服务的远程地址 76 77 pongCh chan struct{} //pong通知被送入此频道 78 histCh chan []uint64 //历史请求块编号被馈入该信道。 79 } 80 81 //new返回一个监控服务,准备好进行状态报告。 82 func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Service, error) { 83 //分析netstats连接URL 84 re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") 85 parts := re.FindStringSubmatch(url) 86 if len(parts) != 5 { 87 return nil, fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) 88 } 89 //集合并返回统计服务 90 var engine consensus.Engine 91 if ethServ != nil { 92 engine = ethServ.Engine() 93 } else { 94 engine = lesServ.Engine() 95 } 96 return &Service{ 97 eth: ethServ, 98 les: lesServ, 99 engine: engine, 100 node: parts[1], 101 pass: parts[3], 102 host: parts[4], 103 pongCh: make(chan struct{}), 104 histCh: make(chan []uint64, 1), 105 }, nil 106 } 107 108 //协议实现node.service,返回使用的p2p网络协议 109 //通过stats服务(无,因为它不使用devp2p覆盖网络)。 110 func (s *Service) Protocols() []p2p.Protocol { return nil } 111 112 //API实现node.service,返回由 113 //统计服务(无,因为它不提供任何用户可调用的API)。 114 func (s *Service) APIs() []rpc.API { return nil } 115 116 //start实现node.service,启动监视和报告守护进程。 117 func (s *Service) Start(server *p2p.Server) error { 118 s.server = server 119 go s.loop() 120 121 log.Info("Stats daemon started") 122 return nil 123 } 124 125 //stop实现node.service,终止监视和报告守护进程。 126 func (s *Service) Stop() error { 127 log.Info("Stats daemon stopped") 128 return nil 129 } 130 131 //循环不断尝试连接到netstats服务器,报告链事件 132 //直到终止。 133 func (s *Service) loop() { 134 //订阅链事件以在其上执行更新 135 var blockchain blockChain 136 var txpool txPool 137 if s.eth != nil { 138 blockchain = s.eth.BlockChain() 139 txpool = s.eth.TxPool() 140 } else { 141 blockchain = s.les.BlockChain() 142 txpool = s.les.TxPool() 143 } 144 145 chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize) 146 headSub := blockchain.SubscribeChainHeadEvent(chainHeadCh) 147 defer headSub.Unsubscribe() 148 149 txEventCh := make(chan core.NewTxsEvent, txChanSize) 150 txSub := txpool.SubscribeNewTxsEvent(txEventCh) 151 defer txSub.Unsubscribe() 152 153 //启动一个排出子脚本的goroutine,以避免事件堆积。 154 var ( 155 quitCh = make(chan struct{}) 156 headCh = make(chan *types.Block, 1) 157 txCh = make(chan struct{}, 1) 158 ) 159 go func() { 160 var lastTx mclock.AbsTime 161 162 HandleLoop: 163 for { 164 select { 165 //通知链头事件,但如果太频繁,则丢弃 166 case head := <-chainHeadCh: 167 select { 168 case headCh <- head.Block: 169 default: 170 } 171 172 //通知新的事务事件,但如果太频繁则删除 173 case <-txEventCh: 174 if time.Duration(mclock.Now()-lastTx) < time.Second { 175 continue 176 } 177 lastTx = mclock.Now() 178 179 select { 180 case txCh <- struct{}{}: 181 default: 182 } 183 184 //节点停止 185 case <-txSub.Err(): 186 break HandleLoop 187 case <-headSub.Err(): 188 break HandleLoop 189 } 190 } 191 close(quitCh) 192 }() 193 //循环报告直到终止 194 for { 195 //Resolve the URL, defaulting to TLS, but falling back to none too 196 path := fmt.Sprintf("%s/api", s.host) 197 urls := []string{path} 198 199 if !strings.Contains(path, "://“)//url.parse和url.isabs不适合(https://github.com/golang/go/issues/19779) 200 urls = []string{"wss://“+路径,”ws://“+路径 201 } 202 //Establish a websocket connection to the server on any supported URL 203 var ( 204 conf *websocket.Config 205 conn *websocket.Conn 206 err error 207 ) 208 for _, url := range urls { 209 if conf, err = websocket.NewConfig(url, "http://localhost/“);错误!= nIL{ 210 continue 211 } 212 conf.Dialer = &net.Dialer{Timeout: 5 * time.Second} 213 if conn, err = websocket.DialConfig(conf); err == nil { 214 break 215 } 216 } 217 if err != nil { 218 log.Warn("Stats server unreachable", "err", err) 219 time.Sleep(10 * time.Second) 220 continue 221 } 222 //向服务器验证客户端 223 if err = s.login(conn); err != nil { 224 log.Warn("Stats login failed", "err", err) 225 conn.Close() 226 time.Sleep(10 * time.Second) 227 continue 228 } 229 go s.readLoop(conn) 230 231 //发送初始统计信息,以便我们的节点从一开始就看起来不错 232 if err = s.report(conn); err != nil { 233 log.Warn("Initial stats report failed", "err", err) 234 conn.Close() 235 continue 236 } 237 //继续发送状态更新,直到连接断开 238 fullReport := time.NewTicker(15 * time.Second) 239 240 for err == nil { 241 select { 242 case <-quitCh: 243 conn.Close() 244 return 245 246 case <-fullReport.C: 247 if err = s.report(conn); err != nil { 248 log.Warn("Full stats report failed", "err", err) 249 } 250 case list := <-s.histCh: 251 if err = s.reportHistory(conn, list); err != nil { 252 log.Warn("Requested history report failed", "err", err) 253 } 254 case head := <-headCh: 255 if err = s.reportBlock(conn, head); err != nil { 256 log.Warn("Block stats report failed", "err", err) 257 } 258 if err = s.reportPending(conn); err != nil { 259 log.Warn("Post-block transaction stats report failed", "err", err) 260 } 261 case <-txCh: 262 if err = s.reportPending(conn); err != nil { 263 log.Warn("Transaction stats report failed", "err", err) 264 } 265 } 266 } 267 //确保连接已关闭 268 conn.Close() 269 } 270 } 271 272 //只要连接处于活动状态并检索数据包,readloop就会循环。 273 //从网络插座。如果其中任何一个匹配活动的请求,它将转发 274 //如果他们自己是请求,它会启动一个回复,最后它会下降。 275 //未知数据包。 276 func (s *Service) readLoop(conn *websocket.Conn) { 277 //如果存在读取循环,请关闭连接 278 defer conn.Close() 279 280 for { 281 //检索下一个通用网络包并在出错时退出 282 var msg map[string][]interface{} 283 if err := websocket.JSON.Receive(conn, &msg); err != nil { 284 log.Warn("Failed to decode stats server message", "err", err) 285 return 286 } 287 log.Trace("Received message from stats server", "msg", msg) 288 if len(msg["emit"]) == 0 { 289 log.Warn("Stats server sent non-broadcast", "msg", msg) 290 return 291 } 292 command, ok := msg["emit"][0].(string) 293 if !ok { 294 log.Warn("Invalid stats server message type", "type", msg["emit"][0]) 295 return 296 } 297 //如果消息是ping回复,则传递(必须有人正在收听!) 298 if len(msg["emit"]) == 2 && command == "node-pong" { 299 select { 300 case s.pongCh <- struct{}{}: 301 //Pong已发送,继续收听 302 continue 303 default: 304 //Ping程序死亡,中止 305 log.Warn("Stats server pinger seems to have died") 306 return 307 } 308 } 309 //如果消息是历史请求,则转发到事件处理器。 310 if len(msg["emit"]) == 2 && command == "history" { 311 //确保请求是有效的并且不会崩溃 312 request, ok := msg["emit"][1].(map[string]interface{}) 313 if !ok { 314 log.Warn("Invalid stats history request", "msg", msg["emit"][1]) 315 s.histCh <- nil 316 continue //ethstats有时发送无效的历史请求,忽略这些请求 317 } 318 list, ok := request["list"].([]interface{}) 319 if !ok { 320 log.Warn("Invalid stats history block list", "list", request["list"]) 321 return 322 } 323 //将块编号列表转换为整数列表 324 numbers := make([]uint64, len(list)) 325 for i, num := range list { 326 n, ok := num.(float64) 327 if !ok { 328 log.Warn("Invalid stats history block number", "number", num) 329 return 330 } 331 numbers[i] = uint64(n) 332 } 333 select { 334 case s.histCh <- numbers: 335 continue 336 default: 337 } 338 } 339 //报告其他内容并继续 340 log.Info("Unknown stats message", "msg", msg) 341 } 342 } 343 344 //nodeinfo是有关显示的节点的元信息的集合。 345 //在监控页面上。 346 type nodeInfo struct { 347 Name string `json:"name"` 348 Node string `json:"node"` 349 Port int `json:"port"` 350 Network string `json:"net"` 351 Protocol string `json:"protocol"` 352 API string `json:"api"` 353 Os string `json:"os"` 354 OsVer string `json:"os_v"` 355 Client string `json:"client"` 356 History bool `json:"canUpdateHistory"` 357 } 358 359 //authmsg是登录监控服务器所需的身份验证信息。 360 type authMsg struct { 361 ID string `json:"id"` 362 Info nodeInfo `json:"info"` 363 Secret string `json:"secret"` 364 } 365 366 //登录尝试在远程服务器上授权客户端。 367 func (s *Service) login(conn *websocket.Conn) error { 368 //构造并发送登录验证 369 infos := s.server.NodeInfo() 370 371 var network, protocol string 372 if info := infos.Protocols["eth"]; info != nil { 373 network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network) 374 protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) 375 } else { 376 network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) 377 protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0]) 378 } 379 auth := &authMsg{ 380 ID: s.node, 381 Info: nodeInfo{ 382 Name: s.node, 383 Node: infos.Name, 384 Port: infos.Ports.Listener, 385 Network: network, 386 Protocol: protocol, 387 API: "No", 388 Os: runtime.GOOS, 389 OsVer: runtime.GOARCH, 390 Client: "0.1.1", 391 History: true, 392 }, 393 Secret: s.pass, 394 } 395 login := map[string][]interface{}{ 396 "emit": {"hello", auth}, 397 } 398 if err := websocket.JSON.Send(conn, login); err != nil { 399 return err 400 } 401 //检索远程确认或连接终止 402 var ack map[string][]string 403 if err := websocket.JSON.Receive(conn, &ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" { 404 return errors.New("unauthorized") 405 } 406 return nil 407 } 408 409 //报告收集所有可能的数据进行报告,并将其发送到Stats服务器。 410 //这只能用于重新连接,或很少用于避免 411 //服务器。使用单独的方法报告订阅的事件。 412 func (s *Service) report(conn *websocket.Conn) error { 413 if err := s.reportLatency(conn); err != nil { 414 return err 415 } 416 if err := s.reportBlock(conn, nil); err != nil { 417 return err 418 } 419 if err := s.reportPending(conn); err != nil { 420 return err 421 } 422 if err := s.reportStats(conn); err != nil { 423 return err 424 } 425 return nil 426 } 427 428 //reportlatency向服务器发送ping请求,测量RTT时间和 429 //最后发送延迟更新。 430 func (s *Service) reportLatency(conn *websocket.Conn) error { 431 //将当前时间发送到ethstats服务器 432 start := time.Now() 433 434 ping := map[string][]interface{}{ 435 "emit": {"node-ping", map[string]string{ 436 "id": s.node, 437 "clientTime": start.String(), 438 }}, 439 } 440 if err := websocket.JSON.Send(conn, ping); err != nil { 441 return err 442 } 443 //等待PONG请求返回 444 select { 445 case <-s.pongCh: 446 //Pong delivered, report the latency 447 case <-time.After(5 * time.Second): 448 //Ping超时,中止 449 return errors.New("ping timed out") 450 } 451 latency := strconv.Itoa(int((time.Since(start) / time.Duration(2)).Nanoseconds() / 1000000)) 452 453 //发送回测量的延迟 454 log.Trace("Sending measured latency to ethstats", "latency", latency) 455 456 stats := map[string][]interface{}{ 457 "emit": {"latency", map[string]string{ 458 "id": s.node, 459 "latency": latency, 460 }}, 461 } 462 return websocket.JSON.Send(conn, stats) 463 } 464 465 //BuBSTATS是报告单个块的信息。 466 type blockStats struct { 467 Number *big.Int `json:"number"` 468 Hash common.Hash `json:"hash"` 469 ParentHash common.Hash `json:"parentHash"` 470 Timestamp *big.Int `json:"timestamp"` 471 Miner common.Address `json:"miner"` 472 GasUsed uint64 `json:"gasUsed"` 473 GasLimit uint64 `json:"gasLimit"` 474 Diff string `json:"difficulty"` 475 TotalDiff string `json:"totalDifficulty"` 476 Txs []txStats `json:"transactions"` 477 TxHash common.Hash `json:"transactionsRoot"` 478 Root common.Hash `json:"stateRoot"` 479 Uncles uncleStats `json:"uncles"` 480 } 481 482 //TxStats是要报告单个交易的信息。 483 type txStats struct { 484 Hash common.Hash `json:"hash"` 485 } 486 487 //Unclestats是一个自定义包装器,它围绕一个叔叔数组强制序列化。 488 //empty arrays instead of returning null for them. 489 type uncleStats []*types.Header 490 491 func (s uncleStats) MarshalJSON() ([]byte, error) { 492 if uncles := ([]*types.Header)(s); len(uncles) > 0 { 493 return json.Marshal(uncles) 494 } 495 return []byte("[]"), nil 496 } 497 498 //ReportBlock检索当前链头并将其报告给Stats服务器。 499 func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error { 500 //从收割台或区块链收集区块详细信息 501 details := s.assembleBlockStats(block) 502 503 //组装块报表并发送给服务器 504 log.Trace("Sending new block to ethstats", "number", details.Number, "hash", details.Hash) 505 506 stats := map[string]interface{}{ 507 "id": s.node, 508 "block": details, 509 } 510 report := map[string][]interface{}{ 511 "emit": {"block", stats}, 512 } 513 return websocket.JSON.Send(conn, report) 514 } 515 516 //assembleBlockstats检索报告单个块所需的任何元数据 517 //并汇编块统计。如果block为nil,则处理当前头。 518 func (s *Service) assembleBlockStats(block *types.Block) *blockStats { 519 //从本地区块链收集区块信息 520 var ( 521 header *types.Header 522 td *big.Int 523 txs []txStats 524 uncles []*types.Header 525 ) 526 if s.eth != nil { 527 //完整节点具有所有可用的所需信息 528 if block == nil { 529 block = s.eth.BlockChain().CurrentBlock() 530 } 531 header = block.Header() 532 td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) 533 534 txs = make([]txStats, len(block.Transactions())) 535 for i, tx := range block.Transactions() { 536 txs[i].Hash = tx.Hash() 537 } 538 uncles = block.Uncles() 539 } else { 540 //轻节点需要按需查找事务/叔叔,跳过 541 if block != nil { 542 header = block.Header() 543 } else { 544 header = s.les.BlockChain().CurrentHeader() 545 } 546 td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) 547 txs = []txStats{} 548 } 549 //集合并返回块统计信息 550 author, _ := s.engine.Author(header) 551 552 return &blockStats{ 553 Number: header.Number, 554 Hash: header.Hash(), 555 ParentHash: header.ParentHash, 556 Timestamp: header.Time, 557 Miner: author, 558 GasUsed: header.GasUsed, 559 GasLimit: header.GasLimit, 560 Diff: header.Difficulty.String(), 561 TotalDiff: td.String(), 562 Txs: txs, 563 TxHash: header.TxHash, 564 Root: header.Root, 565 Uncles: uncles, 566 } 567 } 568 569 //ReportHistory检索最近一批块并将其报告给 570 //统计服务器。 571 func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error { 572 //找出需要报告的索引 573 indexes := make([]uint64, 0, historyUpdateRange) 574 if len(list) > 0 { 575 //请求的特定索引,尤其是将其发回 576 indexes = append(indexes, list...) 577 } else { 578 //没有请求索引,请将最上面的索引发回 579 var head int64 580 if s.eth != nil { 581 head = s.eth.BlockChain().CurrentHeader().Number.Int64() 582 } else { 583 head = s.les.BlockChain().CurrentHeader().Number.Int64() 584 } 585 start := head - historyUpdateRange + 1 586 if start < 0 { 587 start = 0 588 } 589 for i := uint64(start); i <= uint64(head); i++ { 590 indexes = append(indexes, i) 591 } 592 } 593 //Gather the batch of blocks to report 594 history := make([]*blockStats, len(indexes)) 595 for i, number := range indexes { 596 //如果我们知道下一个块,就取回它 597 var block *types.Block 598 if s.eth != nil { 599 block = s.eth.BlockChain().GetBlockByNumber(number) 600 } else { 601 if header := s.les.BlockChain().GetHeaderByNumber(number); header != nil { 602 block = types.NewBlockWithHeader(header) 603 } 604 } 605 //如果我们确实有这个块,请添加到历史记录并继续 606 if block != nil { 607 history[len(history)-1-i] = s.assembleBlockStats(block) 608 continue 609 } 610 //快用完了,把报告剪短然后发送 611 history = history[len(history)-i:] 612 break 613 } 614 //组装历史报告并将其发送到服务器 615 if len(history) > 0 { 616 log.Trace("Sending historical blocks to ethstats", "first", history[0].Number, "last", history[len(history)-1].Number) 617 } else { 618 log.Trace("No history to send to stats server") 619 } 620 stats := map[string]interface{}{ 621 "id": s.node, 622 "history": history, 623 } 624 report := map[string][]interface{}{ 625 "emit": {"history", stats}, 626 } 627 return websocket.JSON.Send(conn, report) 628 } 629 630 //PendStats是有关挂起事务的报告信息。 631 type pendStats struct { 632 Pending int `json:"pending"` 633 } 634 635 //reportPending检索当前挂起的事务和报告数 636 //到统计服务器。 637 func (s *Service) reportPending(conn *websocket.Conn) error { 638 //Retrieve the pending count from the local blockchain 639 var pending int 640 if s.eth != nil { 641 pending, _ = s.eth.TxPool().Stats() 642 } else { 643 pending = s.les.TxPool().Stats() 644 } 645 //组装事务状态并将其发送到服务器 646 log.Trace("Sending pending transactions to ethstats", "count", pending) 647 648 stats := map[string]interface{}{ 649 "id": s.node, 650 "stats": &pendStats{ 651 Pending: pending, 652 }, 653 } 654 report := map[string][]interface{}{ 655 "emit": {"pending", stats}, 656 } 657 return websocket.JSON.Send(conn, report) 658 } 659 660 //nodestats是要报告的有关本地节点的信息。 661 type nodeStats struct { 662 Active bool `json:"active"` 663 Syncing bool `json:"syncing"` 664 Mining bool `json:"mining"` 665 Hashrate int `json:"hashrate"` 666 Peers int `json:"peers"` 667 GasPrice int `json:"gasPrice"` 668 Uptime int `json:"uptime"` 669 } 670 671 //reportPending检索有关网络节点的各种统计信息,以及 672 //挖掘层并将其报告给Stats服务器。 673 func (s *Service) reportStats(conn *websocket.Conn) error { 674 //从本地矿工实例收集同步和挖掘信息 675 var ( 676 mining bool 677 hashrate int 678 syncing bool 679 gasprice int 680 ) 681 if s.eth != nil { 682 mining = s.eth.Miner().Mining() 683 hashrate = int(s.eth.Miner().HashRate()) 684 685 sync := s.eth.Downloader().Progress() 686 syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock 687 688 price, _ := s.eth.APIBackend.SuggestPrice(context.Background()) 689 gasprice = int(price.Uint64()) 690 } else { 691 sync := s.les.Downloader().Progress() 692 syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock 693 } 694 //Assemble the node stats and send it to the server 695 log.Trace("Sending node details to ethstats") 696 697 stats := map[string]interface{}{ 698 "id": s.node, 699 "stats": &nodeStats{ 700 Active: true, 701 Mining: mining, 702 Hashrate: hashrate, 703 Peers: s.server.PeerCount(), 704 GasPrice: gasprice, 705 Syncing: syncing, 706 Uptime: 100, 707 }, 708 } 709 report := map[string][]interface{}{ 710 "emit": {"stats", stats}, 711 } 712 return websocket.JSON.Send(conn, report) 713 } 714