// 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 } func get_peer_list_specific(addr string) (peers []Peer_Info) { plist := get_peer_list() sort.SliceStable(plist, func(i, j int) bool { return plist[i].Addr < plist[j].Addr }) if len(plist) <= 31 { peers = plist } else { index := sort.Search(len(plist), func(i int) bool { return plist[i].Addr < addr }) for i := range plist { peers = append(peers, plist[(i+index)%len(plist)]) if len(peers) >= 31 { break } } } return peers }