forked from TrueCloudLab/neoneo-go
285 lines
7.6 KiB
Go
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
|
|
}
|