github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/network/ping.go (about)

     1  package network
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"fmt"
     7  	"os"
     8  	"sort"
     9  	"time"
    10  
    11  	"github.com/piotrnar/gocoin/client/common"
    12  )
    13  
    14  const (
    15  	PingHistoryLength        = 20
    16  	PingAssumedIfUnsupported = 4999 // ms
    17  	PingAssumedIfUnknown     = 9999 // ms
    18  )
    19  
    20  func (c *OneConnection) HandlePong(pl []byte) {
    21  	if pl != nil {
    22  		if !bytes.Equal(pl, c.PingInProgress) {
    23  			common.CountSafe("PongMismatch")
    24  			return
    25  		}
    26  		common.CountSafe("PongOK")
    27  		c.ExpireHeadersAndGetData(nil, c.X.PingSentCnt)
    28  	} else {
    29  		common.CountSafe("PongTimeout")
    30  	}
    31  	ms := time.Now().Sub(c.LastPingSent) / time.Millisecond
    32  	if ms == 0 {
    33  		//println(c.ConnID, "Ping returned after 0ms")
    34  		ms = 1
    35  	}
    36  	c.Mutex.Lock()
    37  	c.X.PingHistory[c.X.PingHistoryIdx] = int(ms)
    38  	c.X.PingHistoryIdx = (c.X.PingHistoryIdx + 1) % PingHistoryLength
    39  	c.PingInProgress = nil
    40  	c.Mutex.Unlock()
    41  }
    42  
    43  // GetAveragePing returns the (median) average ping.
    44  // Make sure to call it within c.Mutex.Lock().
    45  func (c *OneConnection) GetAveragePing() int {
    46  	if !c.X.VersionReceived {
    47  		return 0
    48  	}
    49  	if c.Node.Version > 60000 {
    50  		var pgs [PingHistoryLength]int
    51  		var act_len int
    52  		for _, p := range c.X.PingHistory {
    53  			if p != 0 {
    54  				pgs[act_len] = p
    55  				act_len++
    56  			}
    57  		}
    58  		if act_len == 0 {
    59  			return PingAssumedIfUnknown
    60  		}
    61  		sort.Ints(pgs[:act_len])
    62  		return pgs[act_len/2]
    63  	} else {
    64  		return PingAssumedIfUnsupported
    65  	}
    66  }
    67  
    68  type SortedConnections []struct {
    69  	Conn          *OneConnection
    70  	Ping          int
    71  	BlockCount    int
    72  	TxsCount      int
    73  	MinutesOnline int
    74  	Special       bool
    75  }
    76  
    77  // GetSortedConnections returns the slowest peers first.
    78  // Make sure to call it with locked Mutex_net.
    79  func GetSortedConnections() (list SortedConnections, any_ping bool) {
    80  	var cnt int
    81  	var now time.Time
    82  	var tlist SortedConnections
    83  	now = time.Now()
    84  	tlist = make(SortedConnections, len(OpenCons))
    85  	for _, v := range OpenCons {
    86  		v.Mutex.Lock()
    87  		tlist[cnt].Conn = v
    88  		tlist[cnt].Ping = v.GetAveragePing()
    89  		tlist[cnt].BlockCount = len(v.blocksreceived)
    90  		tlist[cnt].TxsCount = v.X.TxsReceived
    91  		tlist[cnt].Special = v.X.IsSpecial || v.X.Authorized
    92  		if v.X.VersionReceived == false || v.X.ConnectedAt.IsZero() {
    93  			tlist[cnt].MinutesOnline = 0
    94  		} else {
    95  			tlist[cnt].MinutesOnline = int(now.Sub(v.X.ConnectedAt) / time.Minute)
    96  		}
    97  		v.Mutex.Unlock()
    98  
    99  		if tlist[cnt].Ping > 0 {
   100  			any_ping = true
   101  		}
   102  
   103  		cnt++
   104  	}
   105  	if cnt > 0 {
   106  		list = make(SortedConnections, len(tlist))
   107  		var ignore_bcnt bool // otherwise count blocks
   108  		var idx, best_idx, bcnt, best_bcnt, best_tcnt, best_ping int
   109  
   110  		for idx = len(list) - 1; idx >= 0; idx-- {
   111  			best_idx = -1
   112  			for i, v := range tlist {
   113  				if v.Conn == nil {
   114  					continue
   115  				}
   116  				if best_idx < 0 {
   117  					best_idx = i
   118  					best_tcnt = v.TxsCount
   119  					best_bcnt = v.BlockCount
   120  					best_ping = v.Ping
   121  				} else {
   122  					if ignore_bcnt {
   123  						bcnt = best_bcnt
   124  					} else {
   125  						bcnt = v.BlockCount
   126  					}
   127  					if best_bcnt < bcnt ||
   128  						best_bcnt == bcnt && best_tcnt < v.TxsCount ||
   129  						best_bcnt == bcnt && best_tcnt == v.TxsCount && best_ping > v.Ping {
   130  						best_bcnt = v.BlockCount
   131  						best_tcnt = v.TxsCount
   132  						best_ping = v.Ping
   133  						best_idx = i
   134  					}
   135  				}
   136  			}
   137  			list[idx] = tlist[best_idx]
   138  			tlist[best_idx].Conn = nil
   139  			ignore_bcnt = !ignore_bcnt
   140  		}
   141  	}
   142  	return
   143  }
   144  
   145  // drop_worst_peer should be called only when OutConsActive >= MaxOutCons.
   146  func drop_worst_peer() bool {
   147  	var list SortedConnections
   148  	var any_ping bool
   149  
   150  	Mutex_net.Lock()
   151  	defer Mutex_net.Unlock()
   152  
   153  	list, any_ping = GetSortedConnections()
   154  	if !any_ping { // if "list" is empty "any_ping" will also be false
   155  		return false
   156  	}
   157  
   158  	for _, v := range list {
   159  		if v.MinutesOnline < OnlineImmunityMinutes {
   160  			continue
   161  		}
   162  		if v.Special {
   163  			continue
   164  		}
   165  		if v.Conn.X.Incomming {
   166  			if InConsActive+2 > common.GetUint32(&common.CFG.Net.MaxInCons) {
   167  				common.CountSafe("PeerInDropped")
   168  				if common.FLAG.Log {
   169  					f, _ := os.OpenFile("drop_log.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)
   170  					if f != nil {
   171  						fmt.Fprintf(f, "%s: Drop incoming id:%d  blks:%d  txs:%d  ping:%d  mins:%d\n",
   172  							time.Now().Format("2006-01-02 15:04:05"),
   173  							v.Conn.ConnID, v.BlockCount, v.TxsCount, v.Ping, v.MinutesOnline)
   174  						f.Close()
   175  					}
   176  				}
   177  				v.Conn.Disconnect(true, "PeerInDropped")
   178  				return true
   179  			}
   180  		} else {
   181  			if OutConsActive+2 > common.GetUint32(&common.CFG.Net.MaxOutCons) {
   182  				common.CountSafe("PeerOutDropped")
   183  				if common.FLAG.Log {
   184  					f, _ := os.OpenFile("drop_log.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)
   185  					if f != nil {
   186  						fmt.Fprintf(f, "%s: Drop outgoing id:%d  blks:%d  txs:%d  ping:%d  mins:%d\n",
   187  							time.Now().Format("2006-01-02 15:04:05"),
   188  							v.Conn.ConnID, v.BlockCount, v.TxsCount, v.Ping, v.MinutesOnline)
   189  						f.Close()
   190  					}
   191  				}
   192  				v.Conn.Disconnect(true, "PeerOutDropped")
   193  				return true
   194  			}
   195  		}
   196  	}
   197  	return false
   198  }
   199  
   200  func (c *OneConnection) TryPing(now time.Time) bool {
   201  	if c.Node.Version <= 60000 {
   202  		return false // insufficient protocol version
   203  	}
   204  
   205  	pingdur := common.GetDuration(&common.PingPeerEvery)
   206  	if pingdur == 0 {
   207  		return false // pinging disabled in global config
   208  	}
   209  
   210  	if now.Sub(c.LastPingSent) < pingdur {
   211  		return false // not yet...
   212  	}
   213  
   214  	if c.PingInProgress != nil {
   215  		c.HandlePong(nil) // this will set PingInProgress to nil
   216  	}
   217  
   218  	c.Mutex.Lock()
   219  	bip := len(c.GetBlockInProgress)
   220  	c.Mutex.Unlock()
   221  	if bip > 0 {
   222  		common.CountSafe("PingHelpBIP")
   223  		c.cntLockInc("PingHelpBIP")
   224  		return false
   225  	}
   226  
   227  	c.X.PingSentCnt++
   228  	c.PingInProgress = make([]byte, 8)
   229  	rand.Read(c.PingInProgress[:])
   230  	c.SendRawMsg("ping", c.PingInProgress)
   231  	c.LastPingSent = time.Now()
   232  	//println(c.PeerAddr.Ip(), "ping...")
   233  	return true
   234  }