neoneo-go/pkg/addrmgr/addrmgr.go
2019-02-25 22:44:14 +00:00

285 lines
7.6 KiB
Go

package addrmgr
import (
"errors"
"fmt"
"math/rand"
"sync"
"time"
"github.com/CityOfZion/neo-go/pkg/peer"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
)
const (
maxTries = 5 // Try to connect five times, if we cannot then we say it is bad
// We will keep bad addresses, so that we do not attempt to connect to them in the future.
// nodes at the moment seem to send a large percentage of bad nodes.
maxFailures = 12 // This will be incremented when we connect to a node and for whatever reason, they keep disconnecting.
// The number is high because there could be a period of time, where a node is behaving unrepsonsively and
// we do not want to immmediately put it inside of the bad bucket.
// This is the maximum amount of addresses that the addrmgr will hold
maxAllowedAddrs = 2000
)
type addrStats struct {
tries uint8
failures uint8
permanent bool // permanent = inbound, temp = outbound
lastTried time.Time
lastSuccess time.Time
}
type Addrmgr struct {
addrmtx sync.RWMutex
goodAddrs map[*payload.Net_addr]addrStats
newAddrs map[*payload.Net_addr]struct{}
badAddrs map[*payload.Net_addr]addrStats
knownList map[string]*payload.Net_addr // this contains all of the addresses in badAddrs, newAddrs and goodAddrs
}
func New() *Addrmgr {
return &Addrmgr{
sync.RWMutex{},
make(map[*payload.Net_addr]addrStats, 100),
make(map[*payload.Net_addr]struct{}, 100),
make(map[*payload.Net_addr]addrStats, 100),
make(map[string]*payload.Net_addr, 100),
}
}
//AddAddrs will add new add new addresses into the newaddr list
// This is safe for concurrent access.
func (a *Addrmgr) AddAddrs(newAddrs []*payload.Net_addr) {
newAddrs = removeDuplicates(newAddrs)
var nas []*payload.Net_addr
for _, addr := range newAddrs {
a.addrmtx.Lock()
if _, ok := a.knownList[addr.IPPort()]; !ok { // filter unique addresses
nas = append(nas, addr)
}
a.addrmtx.Unlock()
}
for _, addr := range nas {
a.addrmtx.Lock()
a.newAddrs[addr] = struct{}{}
a.knownList[addr.IPPort()] = addr
a.addrmtx.Unlock()
}
}
// Good returns the good addresses.
// A good address is:
// - Known
// - has been successfully connected to in the last week
// - Note: that while users are launching full nodes from their laptops, the one week marker will need to be modified.
func (a *Addrmgr) Good() []payload.Net_addr {
var goodAddrs []payload.Net_addr
var oneWeekAgo = time.Now().Add(((time.Hour * 24) * 7) * -1)
a.addrmtx.RLock()
// TODO: sort addresses, permanent ones go first
for addr, stat := range a.goodAddrs {
if stat.lastTried.Before(oneWeekAgo) {
continue
}
goodAddrs = append(goodAddrs, *addr)
}
a.addrmtx.RUnlock()
return goodAddrs
}
// Unconnected Addrs are addresses in the newAddr List
func (a *Addrmgr) Unconnected() []payload.Net_addr {
var unconnAddrs []payload.Net_addr
a.addrmtx.RLock()
for addr := range a.newAddrs {
unconnAddrs = append(unconnAddrs, *addr)
}
a.addrmtx.RUnlock()
return unconnAddrs
}
// Bad Addrs are addresses in the badAddr List
// They are put there if we have tried to connect
// to them in the past and it failed.
func (a *Addrmgr) Bad() []payload.Net_addr {
var badAddrs []payload.Net_addr
a.addrmtx.RLock()
for addr := range a.badAddrs {
badAddrs = append(badAddrs, *addr)
}
a.addrmtx.RUnlock()
return badAddrs
}
// FetchMoreAddresses will return true if the numOfKnownAddrs are less than 100
// This number is kept low because at the moment, there are not a lot of Good Addresses
// Tests have shown that at most there are < 100
func (a *Addrmgr) FetchMoreAddresses() bool {
var numOfKnownAddrs int
a.addrmtx.RLock()
numOfKnownAddrs = len(a.knownList)
a.addrmtx.RUnlock()
return numOfKnownAddrs < maxAllowedAddrs
}
// ConnectionComplete will be called by the server when we have done the handshake
// and Not when we have successfully Connected with net.Conn
// It is to tell the AddrMgr that we have connected to a peer
// This peer should already be known to the AddrMgr because
// We send it to the Connmgr
func (a *Addrmgr) ConnectionComplete(addressport string, inbound bool) {
a.addrmtx.Lock()
defer a.addrmtx.Unlock()
// if addrmgr does not know this, then we just return
if _, ok := a.knownList[addressport]; !ok {
fmt.Println("Connected to an unknown address:port ", addressport)
return
}
na := a.knownList[addressport]
// move it from newAddrs List to GoodAddr List
stats := a.goodAddrs[na]
stats.lastSuccess = time.Now()
stats.lastTried = time.Now()
stats.permanent = inbound
stats.tries++
a.goodAddrs[na] = stats
// remove it from new and bad, if it was there
delete(a.newAddrs, na)
delete(a.badAddrs, na)
// Unfortunately, Will have a lot of bad nodes because of people connecting to nodes
// from their laptop. TODO Sort function in good will mitigate.
}
// Failed will be called by ConnMgr
// It is used to tell the AddrMgr that they had tried connecting an address an have failed
// This is concurrent safe
func (a *Addrmgr) Failed(addressport string) {
a.addrmtx.Lock()
defer a.addrmtx.Unlock()
// if addrmgr does not know this, then we just return
if _, ok := a.knownList[addressport]; !ok {
fmt.Println("Connected to an unknown address:port ", addressport)
return
}
na := a.knownList[addressport]
// HMM: logic here could be simpler if we make it one list instead
if stats, ok := a.badAddrs[na]; ok {
stats.lastTried = time.Now()
stats.failures++
stats.tries++
if float32(stats.failures)/float32(stats.tries) > 0.8 && stats.tries > 5 {
delete(a.badAddrs, na)
return
}
a.badAddrs[na] = stats
return
}
if stats, ok := a.goodAddrs[na]; ok {
fmt.Println("We have a good addr", na.IPPort())
stats.lastTried = time.Now()
stats.failures++
stats.tries++
if float32(stats.failures)/float32(stats.tries) > 0.5 && stats.tries > 10 {
delete(a.goodAddrs, na)
a.badAddrs[na] = stats
return
}
a.goodAddrs[na] = stats
return
}
if _, ok := a.newAddrs[na]; ok {
delete(a.newAddrs, na)
a.badAddrs[na] = addrStats{}
}
}
//OnAddr is the responder for the Config file
// when a OnAddr is received by a peer
func (a *Addrmgr) OnAddr(p *peer.Peer, msg *payload.AddrMessage) {
a.AddAddrs(msg.AddrList)
}
// OnGetAddr Is called when a peer sends a request for the addressList
// We will give them the best addresses we have from good.
func (a *Addrmgr) OnGetAddr(p *peer.Peer, msg *payload.GetAddrMessage) {
a.addrmtx.RLock()
defer a.addrmtx.RUnlock()
// Push most recent peers to peer
addrMsg, err := payload.NewAddrMessage()
if err != nil {
return
}
for _, add := range a.Good() {
addrMsg.AddNetAddr(&add)
}
p.Write(addrMsg)
}
// NewAddr will return an address for the external caller to
// connect to. In our case, it will be the connection manager.
func (a *Addrmgr) NewAddr() (string, error) {
// For now it just returns a random value from unconnected
// TODO: When an address is tried, the address manager is notified.
// When asked for a new address, this should be taken into account
// when choosing a new one, also the number of retries.
unconnected := a.Unconnected()
if len(unconnected) == 0 {
return "", errors.New("No Addresses to give")
}
rand := rand.Intn(len(unconnected))
return unconnected[rand].IPPort(), nil
}
// https://www.dotnetperls.com/duplicates-go
func removeDuplicates(elements []*payload.Net_addr) []*payload.Net_addr {
encountered := map[string]bool{}
result := []*payload.Net_addr{}
for _, element := range elements {
if encountered[element.IPPort()] == true {
// Do not add duplicate.
} else {
// Record this element as an encountered element.
encountered[element.IPPort()] = true
// Append to result slice.
result = append(result, element)
}
}
// Return the new slice.
return result
}