derohe-miniblock-mod/p2p/peer_pool.go
2021-11-21 15:25:11 +00:00

326 lines
10 KiB
Go

// Copyright 2017-2021 DERO Project. All rights reserved.
// Use of this source code in any form is governed by RESEARCH license.
// license can be found in the LICENSE file.
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package p2p
/* this file implements the peer manager, keeping a list of peers which can be tried for connection etc
*
*/
import "os"
import "fmt"
import "errors"
import "sync"
import "time"
import "sort"
import "path/filepath"
import "encoding/json"
//import "encoding/binary"
//import "container/list"
//import log "github.com/sirupsen/logrus"
import "github.com/deroproject/derohe/globals"
//import "github.com/deroproject/derosuite/crypto"
// This structure is used to do book keeping for the peer list and keeps other DATA related to peer
// all peers are servers, means they have exposed a port for connections
// all peers are identified by their endpoint tcp address
// all clients are identified by ther peer id ( however ip-address is used to control amount )
// the following daemon commands interact with the list
// peer_list := print the peer list
// ban address time // ban this address for spcific time
// unban address
// enableban address // by default all addresses are bannable
// disableban address // this address will never be banned
type Peer struct {
Address string `json:"address"` // pairs in the ip:port or dns:port, basically endpoint
ID uint64 `json:"peerid"` // peer id
Miner bool `json:"miner"` // miner
//NeverBlacklist bool // this address will never be blacklisted
LastConnected uint64 `json:"lastconnected"` // epoch time when it was connected , 0 if never connected
FailCount uint64 `json:"failcount"` // how many times have we failed (tcp errors)
ConnectAfter uint64 `json:"connectafter"` // we should connect when the following timestamp passes
BlacklistBefore uint64 `json:"blacklistbefore"` // peer blacklisted till epoch , priority nodes are never blacklisted, 0 if not blacklist
GoodCount uint64 `json:"goodcount"` // how many times peer has been shared with us
Version int `json:"version"` // version 1 is original C daemon peer, version 2 is golang p2p version
Whitelist bool `json:"whitelist"`
sync.Mutex
}
var peer_map = map[string]*Peer{}
var peer_mutex sync.Mutex
// loads peers list from disk
func load_peer_list() {
defer clean_up()
peer_mutex.Lock()
defer peer_mutex.Unlock()
peer_file := filepath.Join(globals.GetDataDirectory(), "peers.json")
if _, err := os.Stat(peer_file); errors.Is(err, os.ErrNotExist) {
return // since file doesn't exist , we cannot load it
}
file, err := os.Open(peer_file)
if err != nil {
logger.Error(err, "opening peer file")
} else {
defer file.Close()
decoder := json.NewDecoder(file)
err = decoder.Decode(&peer_map)
if err != nil {
logger.Error(err, "Error unmarshalling peer data")
} else { // successfully unmarshalled data
logger.V(1).Info("Successfully loaded peers from file", "peer_count", (len(peer_map)))
}
}
}
//save peer list to disk
func save_peer_list() {
clean_up()
peer_mutex.Lock()
defer peer_mutex.Unlock()
peer_file := filepath.Join(globals.GetDataDirectory(), "peers.json")
file, err := os.Create(peer_file)
if err != nil {
logger.Error(err, "saving peer file")
} else {
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", "\t")
err = encoder.Encode(&peer_map)
if err != nil {
logger.Error(err, "Error marshalling peer data")
} else { // successfully unmarshalled data
logger.V(1).Info("Successfully saved peers to file", "peer_count", (len(peer_map)))
}
}
}
// clean up by discarding entries which are too much into future
func clean_up() {
peer_mutex.Lock()
defer peer_mutex.Unlock()
for k, v := range peer_map {
if IsAddressConnected(ParseIPNoError(v.Address)) {
continue
}
if v.FailCount >= 8 { // roughly 8 tries before we discard the peer
delete(peer_map, k)
}
if v.LastConnected == 0 { // if never connected, purge the peer
delete(peer_map, k)
}
if uint64(time.Now().UTC().Unix()) > (v.LastConnected + 3600) { // purge all peers which were not connected in
delete(peer_map, k)
}
}
}
// check whether an IP is in the map already
func IsPeerInList(address string) bool {
peer_mutex.Lock()
defer peer_mutex.Unlock()
if _, ok := peer_map[ParseIPNoError(address)]; ok {
return true
}
return false
}
func GetPeerInList(address string) *Peer {
peer_mutex.Lock()
defer peer_mutex.Unlock()
if v, ok := peer_map[ParseIPNoError(address)]; ok {
return v
}
return nil
}
// add connection to map
func Peer_Add(p *Peer) {
clean_up()
peer_mutex.Lock()
defer peer_mutex.Unlock()
if p.ID == GetPeerID() { // if peer is self do not connect
// logger.Infof("Peer is ourselves, discard")
return
}
if v, ok := peer_map[ParseIPNoError(p.Address)]; ok {
v.Lock()
// logger.Infof("Peer already in list adding good count")
v.GoodCount++
v.Unlock()
} else {
// logger.Infof("Peer adding to list")
peer_map[ParseIPNoError(p.Address)] = p
}
}
// a peer marked as fail, will only be connected based on exponential back-off based on powers of 2
func Peer_SetFail(address string) {
p := GetPeerInList(ParseIPNoError(address))
if p == nil {
return
}
peer_mutex.Lock()
defer peer_mutex.Unlock()
p.FailCount++ // increase fail count, and mark for delayed connect
p.ConnectAfter = uint64(time.Now().UTC().Unix()) + 1<<(p.FailCount-1)
}
// set peer as successfully connected
// we will only distribute peers which have been successfully connected by us
func Peer_SetSuccess(address string) {
//logger.Infof("Setting peer as success")
p := GetPeerInList(ParseIPNoError(address))
if p == nil {
return
}
peer_mutex.Lock()
defer peer_mutex.Unlock()
p.FailCount = 0 // fail count is zero again
p.ConnectAfter = 0
p.Whitelist = true
p.LastConnected = uint64(time.Now().UTC().Unix()) // set time when last connected
// logger.Infof("Setting peer as white listed")
}
/*
//TODO do we need a functionality so some peers are never banned
func Peer_DisableBan(address string) (err error){
p := GetPeerInList(address)
if p == nil {
return fmt.Errorf("Peer \"%s\" not found in list")
}
p.Lock()
defer p.Unlock()
p.NeverBlacklist = true
}
func Peer_EnableBan(address string) (err error){
p := GetPeerInList(address)
if p == nil {
return fmt.Errorf("Peer \"%s\" not found in list")
}
p.Lock()
defer p.Unlock()
p.NeverBlacklist = false
}
*/
// add connection to map
func Peer_Delete(p *Peer) {
peer_mutex.Lock()
defer peer_mutex.Unlock()
delete(peer_map, ParseIPNoError(p.Address))
}
// prints all the connection info to screen
func PeerList_Print() {
peer_mutex.Lock()
defer peer_mutex.Unlock()
fmt.Printf("Peer List\n")
fmt.Printf("%-22s %-6s %-4s %-5s %-7s %9s %3s\n", "Remote Addr", "Active", "Good", "Fail", " State", "Height", "DIR")
var list []*Peer
greycount := 0
for _, v := range peer_map {
if v.Whitelist { // only display white listed peer
list = append(list, v)
} else {
greycount++
}
}
// sort the list
sort.Slice(list, func(i, j int) bool { return list[i].Address < list[j].Address })
for i := range list {
connected := ""
if IsAddressConnected(ParseIPNoError(list[i].Address)) {
connected = "ACTIVE"
}
fmt.Printf("%-22s %-6s %4d %5d \n", list[i].Address, connected, list[i].GoodCount, list[i].FailCount)
}
fmt.Printf("\nWhitelist size %d\n", len(peer_map)-greycount)
fmt.Printf("Greylist size %d\n", greycount)
}
// this function return peer count which are in our list
func Peer_Counts() (Count uint64) {
peer_mutex.Lock()
defer peer_mutex.Unlock()
return uint64(len(peer_map))
}
// this function finds a possible peer to connect to keeping blacklist and already existing connections into picture
// it must not be already connected using outgoing connection
// we do allow loops such as both incoming/outgoing simultaneously
// this will return atmost 1 address, empty address if peer list is empty
func find_peer_to_connect(version int) *Peer {
defer clean_up()
peer_mutex.Lock()
defer peer_mutex.Unlock()
// first search the whitelisted ones
for _, v := range peer_map {
if uint64(time.Now().Unix()) > v.BlacklistBefore && // if ip is blacklisted skip it
uint64(time.Now().Unix()) > v.ConnectAfter &&
!IsAddressConnected(ParseIPNoError(v.Address)) && v.Whitelist && !IsAddressInBanList(ParseIPNoError(v.Address)) {
v.ConnectAfter = uint64(time.Now().UTC().Unix()) + 10 // minimum 10 secs gap
return v
}
}
// if we donot have any white listed, choose from the greylist
for _, v := range peer_map {
if uint64(time.Now().Unix()) > v.BlacklistBefore && // if ip is blacklisted skip it
uint64(time.Now().Unix()) > v.ConnectAfter &&
!IsAddressConnected(ParseIPNoError(v.Address)) && !v.Whitelist && !IsAddressInBanList(ParseIPNoError(v.Address)) {
v.ConnectAfter = uint64(time.Now().UTC().Unix()) + 10 // minimum 10 secs gap
return v
}
}
return nil // if no peer found, return nil
}
// return white listed peer list
// for use in handshake
func get_peer_list() (peers []Peer_Info) {
peer_mutex.Lock()
defer peer_mutex.Unlock()
for _, v := range peer_map {
if v.Whitelist {
peers = append(peers, Peer_Info{Addr: v.Address})
}
}
return
}