github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/network/peersdb/peerdb.go (about) 1 package peersdb 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "errors" 8 "fmt" 9 "hash/crc64" 10 "io" 11 "net" 12 "os" 13 "sort" 14 "strconv" 15 "strings" 16 "sync" 17 "time" 18 19 "github.com/piotrnar/gocoin/client/common" 20 "github.com/piotrnar/gocoin/lib/btc" 21 "github.com/piotrnar/gocoin/lib/others/qdb" 22 "github.com/piotrnar/gocoin/lib/others/sys" 23 ) 24 25 const ( 26 ExpireDeadPeerAfter = (1 * 24 * time.Hour) 27 ExpireAlivePeerAfter = (3 * 24 * time.Hour) 28 ExpireBannedPeerAfter = (7 * 24 * time.Hour) 29 MinPeersInDB = 2500 30 MaxPeersInDB = 70000 31 MaxPeersDeviation = 2500 32 ExpirePeersPeriod = (5 * time.Minute) 33 ) 34 35 /* 36 Serialized peer record (all values are LSB unless specified otherwise): 37 [0:4] - Unix timestamp of when last the peer was seen 38 [4:12] - Services 39 [12:24] - IPv6 (network order) 40 [24:28] - IPv4 (network order) 41 [28:30] - TCP port (big endian) 42 [30:34] - OPTIONAL: 43 highest bit: set to 1 of peer has been seen "alive" 44 low 31 bits: if present, unix timestamp of when the peer was banned divided by 2 45 [35] - OPTIONAL flags 46 bit(0) - Indicates BanReadon present (byte_len followed by the string) 47 bit(1) - Indicates CameFromIP present (for IP4: len byte 4 followed by 4 bytes of IP) 48 bit(2) - Agent string from the Version message 49 bits(2-7) - reserved 50 51 Extra fields are always present int the order defined by the flags (from bit 0 to 7). 52 Each extra field is one byte of length followed by the length bytes of data. 53 54 */ 55 56 var ( 57 PeerDB *qdb.DB 58 proxyPeer *PeerAddr // when this is not nil we should only connect to this single node 59 peerdb_mutex sync.Mutex 60 61 Testnet bool 62 ConnectOnly string 63 Services uint64 = 1 64 65 crctab = crc64.MakeTable(crc64.ISO) 66 ) 67 68 type PeerAddr struct { 69 btc.NetAddr 70 Time uint32 // When seen last time 71 Banned uint32 // time when this address baned or zero if never 72 SeenAlive bool 73 BanReason string 74 CameFromIP []byte 75 NodeAgent string 76 77 key_set bool // cached to avid key crc64 re-calculations 78 key_val uint64 79 80 // The fields below don't get saved, but are used internaly 81 Manual bool // Manually connected (from UI) 82 Friend bool // Connected from friends.txt 83 84 lastSaved int64 // update the record only once per minute 85 } 86 87 func DefaultTcpPort() uint16 { 88 if Testnet { 89 return 18333 90 } else { 91 return 8333 92 } 93 } 94 95 func Lock() { 96 peerdb_mutex.Lock() 97 } 98 99 func Unlock() { 100 peerdb_mutex.Unlock() 101 } 102 103 func read_extra_field(b *bytes.Buffer) []byte { 104 le, er := b.ReadByte() 105 if er != nil { 106 return nil 107 } 108 dat := make([]byte, int(le)) 109 _, er = io.ReadFull(b, dat) 110 if er != nil { 111 return nil 112 } 113 return dat 114 } 115 116 func write_extra_field(b *bytes.Buffer, dat []byte) { 117 le := len(dat) 118 if le > 255 { 119 le = 255 120 } 121 b.WriteByte(byte(le)) 122 b.Write(dat[:le]) 123 } 124 125 func NewPeer(v []byte) (p *PeerAddr) { 126 p = new(PeerAddr) 127 if v == nil || len(v) < 30 { 128 p.Ip6[10], p.Ip6[11] = 0xff, 0xff 129 p.Time = uint32(time.Now().Unix()) 130 return 131 } 132 p.Time = binary.LittleEndian.Uint32(v[0:4]) 133 p.Services = binary.LittleEndian.Uint64(v[4:12]) 134 copy(p.Ip6[:], v[12:24]) 135 copy(p.Ip4[:], v[24:28]) 136 p.Port = binary.BigEndian.Uint16(v[28:30]) 137 if len(v) >= 34 { 138 xd := binary.LittleEndian.Uint32(v[30:34]) 139 p.SeenAlive = (xd & 0x80000000) != 0 140 p.Banned = (xd & 0x7fffffff) << 1 141 if !p.SeenAlive && p.Banned > 1893452400 /*Year 2030*/ { 142 // Convert from the old DB - TODO: remove it at some point (now is 14th of July 2021) 143 p.Banned >>= 1 144 } 145 if len(v) >= 35 { 146 extra_fields := v[34] 147 if extra_fields != 0 { 148 buf := bytes.NewBuffer(v[35:]) 149 for bit := 0; bit < 8; bit++ { 150 if (extra_fields & 0x01) != 0 { 151 dat := read_extra_field(buf) 152 if dat == nil { 153 break // error 154 } 155 switch bit { 156 case 0: 157 p.BanReason = string(dat) 158 case 1: 159 p.CameFromIP = dat 160 case 2: 161 p.NodeAgent = string(dat) 162 } 163 } 164 extra_fields >>= 1 165 if extra_fields == 0 { 166 break 167 } 168 } 169 } 170 } 171 } 172 return 173 } 174 175 func (p *PeerAddr) Bytes() (res []byte) { 176 var x_flags byte 177 if p.Banned != 0 && p.BanReason != "" { 178 x_flags |= 0x01 179 } 180 if p.CameFromIP != nil { 181 x_flags |= 0x02 182 } 183 if p.NodeAgent != "" { 184 x_flags |= 0x04 185 } 186 b := new(bytes.Buffer) 187 binary.Write(b, binary.LittleEndian, p.Time) 188 binary.Write(b, binary.LittleEndian, p.Services) 189 b.Write(p.Ip6[:]) 190 b.Write(p.Ip4[:]) 191 binary.Write(b, binary.BigEndian, p.Port) 192 if p.SeenAlive || x_flags != 0 { 193 xd := p.Banned >> 1 194 if p.SeenAlive { 195 xd |= 0x80000000 196 } 197 binary.Write(b, binary.LittleEndian, xd) 198 } 199 if x_flags != 0 { 200 b.WriteByte(x_flags) 201 } 202 if (x_flags & 0x01) != 0 { 203 write_extra_field(b, []byte(p.BanReason)) 204 } 205 if (x_flags & 0x02) != 0 { 206 write_extra_field(b, p.CameFromIP) 207 } 208 if (x_flags & 0x04) != 0 { 209 write_extra_field(b, []byte(p.NodeAgent)) 210 } 211 res = b.Bytes() 212 return 213 } 214 215 func (p *PeerAddr) UniqID() uint64 { 216 if !p.key_set { 217 h := crc64.New(crctab) 218 h.Write(p.Ip6[:]) 219 h.Write(p.Ip4[:]) 220 h.Write([]byte{byte(p.Port >> 8), byte(p.Port)}) 221 p.key_set = true 222 p.key_val = h.Sum64() 223 } 224 return p.key_val 225 } 226 227 func NewAddrFromString(ipstr string, force_default_port bool) (p *PeerAddr, e error) { 228 port := DefaultTcpPort() 229 x := strings.Index(ipstr, ":") 230 if x != -1 { 231 if !force_default_port { 232 v, er := strconv.ParseUint(ipstr[x+1:], 10, 32) 233 if er != nil { 234 e = er 235 return 236 } 237 if v > 0xffff { 238 e = errors.New("Port number too big") 239 return 240 } 241 port = uint16(v) 242 } 243 ipstr = ipstr[:x] // remove port number 244 } 245 ipa, er := net.ResolveIPAddr("ip", ipstr) 246 if er == nil { 247 if ipa == nil || len(ipa.IP) != 4 && len(ipa.IP) != 16 { 248 e = errors.New("peerdb.NewAddrFromString(" + ipstr + ") - address error") 249 } else { 250 p = NewPeer(nil) 251 p.Services = Services 252 p.Port = port 253 if len(ipa.IP) == 4 { 254 copy(p.Ip4[:], ipa.IP[:]) 255 } else { 256 copy(p.Ip4[:], ipa.IP[12:16]) 257 copy(p.Ip6[:], ipa.IP[:12]) 258 } 259 if dbp := PeerDB.Get(qdb.KeyType(p.UniqID())); dbp != nil { 260 p = NewPeer(dbp) // if we already had it, take the previous record 261 } 262 } 263 } else { 264 e = errors.New("peerdb.NewAddrFromString(" + ipstr + ") - " + er.Error()) 265 } 266 return 267 } 268 269 func NewIncommingConnection(ipstr string, force_default_port bool) (p *PeerAddr, e error) { 270 p, e = NewAddrFromString(ipstr, force_default_port) 271 if e != nil { 272 return 273 } 274 275 if sys.IsIPBlocked(p.Ip4[:]) { 276 e = errors.New(ipstr + " is blocked") 277 return 278 } 279 280 if p.Banned != 0 { 281 e = errors.New(p.Ip() + " is banned") 282 // If the peer is banned but still trying to connect, update the time so it won't be expiring 283 now := time.Now().Unix() 284 p.Time = uint32(now) 285 if now-int64(p.Time) >= 60 { // do not update more often than once per minute 286 p.Time = uint32(now) 287 p.Save() 288 } 289 p = nil 290 } 291 return 292 } 293 294 func DeleteFromIP(ip []byte) int { 295 var ks []qdb.KeyType 296 peerdb_mutex.Lock() 297 PeerDB.Browse(func(k qdb.KeyType, v []byte) uint32 { 298 p := NewPeer(v) 299 if p.CameFromIP != nil && bytes.Equal(ip, p.CameFromIP) { 300 ks = append(ks, k) 301 } 302 return 0 303 }) 304 for _, k := range ks { 305 PeerDB.Del(k) 306 } 307 peerdb_mutex.Unlock() 308 return len(ks) 309 } 310 311 func ExpirePeers() { 312 peerdb_mutex.Lock() 313 defer peerdb_mutex.Unlock() 314 if PeerDB.Count() > 11*MinPeersInDB/10 { 315 common.CountSafe("PeersExpireNeeded") 316 now := time.Now() 317 expire_dead_before_time := uint32(now.Add(-ExpireDeadPeerAfter).Unix()) 318 expire_alive_before_time := uint32(now.Add(-ExpireAlivePeerAfter).Unix()) 319 expire_banned_before_time := uint32(now.Add(-ExpireBannedPeerAfter).Unix()) 320 recs := make(manyPeers, PeerDB.Count()) 321 var i, c_dead1, c_dead2, c_seen_alive, c_banned int 322 PeerDB.Browse(func(k qdb.KeyType, v []byte) uint32 { 323 if i >= len(recs) { 324 println("ERROR: PeersDB grew since we checked its size. Please report!") 325 return 0 326 } 327 recs[i] = NewPeer(v) 328 i++ 329 return 0 330 }) 331 if i < len(recs) { 332 println("ERROR: PeersDB shrunk since we checked its size. Please report!") 333 recs = recs[:i] 334 } 335 sort.Sort(recs) 336 for i = len(recs) - 1; i > MinPeersInDB; i-- { 337 var delit bool 338 rec := recs[i] 339 if !rec.SeenAlive { 340 if PeerDB.Count() > MaxPeersInDB-MaxPeersDeviation { 341 // if DB is full, we delete all the oldest never-alive records 342 delit = true 343 c_dead1++ 344 } else { 345 // otherwise we only delete those older than 24 hours (ExpireDeadPeerAfter) 346 if rec.Time < expire_dead_before_time { 347 delit = true 348 c_dead2++ 349 } else { 350 break 351 } 352 } 353 } else if rec.Time < expire_alive_before_time { 354 if rec.Banned == 0 { 355 delit = true 356 c_seen_alive++ 357 } else if rec.Banned < expire_banned_before_time { 358 delit = true 359 c_banned++ 360 } 361 } 362 if delit { 363 PeerDB.Del(qdb.KeyType(rec.UniqID())) 364 if PeerDB.Count() <= MinPeersInDB { 365 break 366 } 367 } 368 } 369 common.CounterMutex.Lock() 370 if c_dead1 > 0 { 371 common.CountAdd("PeersExpiredDead1", uint64(c_dead1)) 372 } 373 if c_dead2 > 0 { 374 common.CountAdd("PeersExpiredDead2", uint64(c_dead2)) 375 } 376 if c_seen_alive > 0 { 377 common.CountAdd("PeersExpiredAlive", uint64(c_seen_alive)) 378 } 379 if c_banned > 0 { 380 common.CountAdd("PeersExpiredBanned", uint64(c_banned)) 381 } 382 common.CounterMutex.Unlock() 383 PeerDB.Defrag(false) 384 } else { 385 common.CountSafe("PeersExpireNone") 386 } 387 } 388 389 func (p *PeerAddr) Save() { 390 if p.Banned > p.Time { 391 p.lastSaved = int64(p.Banned) 392 } else { 393 p.lastSaved = int64(p.Time) 394 } 395 peerdb_mutex.Lock() 396 PeerDB.Put(qdb.KeyType(p.UniqID()), p.Bytes()) 397 //PeerDB.Sync() 398 peerdb_mutex.Unlock() 399 } 400 401 func (p *PeerAddr) Ban(reason string) { 402 now := time.Now().Unix() 403 p.Banned = uint32(now) 404 if p.Banned == 0 || p.BanReason == "" && reason != "" || now-p.lastSaved >= 60 { 405 p.BanReason = reason 406 p.Save() 407 } 408 } 409 410 func (p *PeerAddr) Alive() { 411 now := time.Now().Unix() 412 p.Time = uint32(now) 413 if !p.SeenAlive || now-p.lastSaved >= 60 { 414 p.SeenAlive = true 415 p.Save() 416 } 417 } 418 419 func (p *PeerAddr) Dead() { 420 peerdb_mutex.Lock() 421 if !p.SeenAlive && p.Banned == 0 && PeerDB.Count() > MinPeersInDB { 422 PeerDB.Del(qdb.KeyType(p.UniqID())) 423 peerdb_mutex.Unlock() 424 return 425 } 426 peerdb_mutex.Unlock() 427 p.Time = uint32(time.Now().Unix() - 5*60) // make it last alive 5 minutes ago 428 p.Save() 429 } 430 431 func (p *PeerAddr) Ip() string { 432 return fmt.Sprintf("%d.%d.%d.%d:%d", p.Ip4[0], p.Ip4[1], p.Ip4[2], p.Ip4[3], p.Port) 433 } 434 435 func secs_to_str(t int) string { 436 if t < 0 { 437 return fmt.Sprint(t) 438 } 439 if t < 120 { 440 return fmt.Sprintf("%d sec", t) 441 } 442 if t < 5*3600 { 443 return fmt.Sprintf("%.2f min", float64(t)/60.0) 444 } 445 if t < 2*86400 { 446 return fmt.Sprintf("%.2f hrs", float64(t)/3600.0) 447 } 448 return fmt.Sprintf("%.2f dys", float64(t)/(86400.0)) 449 } 450 451 func (p *PeerAddr) String() (s string) { 452 s = fmt.Sprintf("%21s ", p.Ip()) 453 if p.Services == 0xffffffffffffffff { 454 s += "Srv:ALL" 455 } else { 456 s += fmt.Sprintf("Srv:%3x", p.Services) 457 } 458 459 now := uint32(time.Now().Unix()) 460 if p.SeenAlive { 461 s += " ALI" 462 } else { 463 s += " " 464 } 465 s += " " + secs_to_str(int(now)-int(p.Time)) 466 467 if p.Banned != 0 { 468 s += " BAN" 469 if p.BanReason != "" { 470 s += " (" + p.BanReason + ")" 471 } 472 s += " " + secs_to_str(int(now)-int(p.Banned)) 473 } 474 475 if p.NodeAgent != "" { 476 s += " [" + p.NodeAgent + "]" 477 } 478 479 if p.CameFromIP != nil { 480 s += " from " 481 if len(p.CameFromIP) == 4 { 482 s += fmt.Sprintf("%d.%d.%d.%d", p.CameFromIP[0], p.CameFromIP[1], p.CameFromIP[2], p.CameFromIP[3]) 483 } else { 484 s += hex.EncodeToString(p.CameFromIP) 485 } 486 } 487 488 return 489 } 490 491 type manyPeers []*PeerAddr 492 493 func (mp manyPeers) Len() int { 494 return len(mp) 495 } 496 497 func (mp manyPeers) Less(i, j int) bool { 498 return mp[i].Time > mp[j].Time 499 } 500 501 func (mp manyPeers) Swap(i, j int) { 502 mp[i], mp[j] = mp[j], mp[i] 503 } 504 505 // GetRecentPeersExt fetches a given number of best (most recenty seen) peers. 506 func GetRecentPeers(limit uint, sort_result bool, ignorePeer func(*PeerAddr) bool) (res manyPeers) { 507 if proxyPeer != nil { 508 if ignorePeer == nil || !ignorePeer(proxyPeer) { 509 return manyPeers{proxyPeer} 510 } 511 return manyPeers{} 512 } 513 res = make(manyPeers, 0) 514 peerdb_mutex.Lock() 515 PeerDB.Browse(func(k qdb.KeyType, v []byte) uint32 { 516 ad := NewPeer(v) 517 if sys.ValidIp4(ad.Ip4[:]) && !sys.IsIPBlocked(ad.Ip4[:]) { 518 if ignorePeer == nil || !ignorePeer(ad) { 519 res = append(res, ad) 520 if !sort_result && len(res) >= int(limit) { 521 return qdb.BR_ABORT 522 } 523 } 524 } 525 return 0 526 }) 527 peerdb_mutex.Unlock() 528 if sort_result && len(res) > 0 { 529 sort.Sort(res) 530 if int(limit) < len(res) { 531 res = res[:int(limit)] 532 } 533 } 534 return 535 } 536 537 func initSeeds(seeds []string, port uint16) { 538 for i := range seeds { 539 ad, er := net.LookupHost(seeds[i]) 540 if er == nil { 541 //println(len(ad), "addrs from", seeds[i]) 542 for j := range ad { 543 ip := net.ParseIP(ad[j]) 544 if ip != nil && len(ip) == 16 { 545 p := NewPeer(nil) 546 p.Services = 0xFFFFFFFFFFFFFFFF 547 copy(p.Ip6[:], ip[:12]) 548 copy(p.Ip4[:], ip[12:16]) 549 p.Port = port 550 if dbp := PeerDB.Get(qdb.KeyType(p.UniqID())); dbp != nil { 551 _p := NewPeer(dbp) 552 _p.Time = p.Time 553 _p.Save() // if we already had it, only update the time field 554 } else { 555 p.Save() 556 } 557 } 558 } 559 } else { 560 println("initSeeds LookupHost", seeds[i], "-", er.Error()) 561 } 562 } 563 } 564 565 // InitPeers should be called from the main thread. 566 func InitPeers(dir string) { 567 PeerDB, _ = qdb.NewDB(dir+"peers3", true) 568 569 if ConnectOnly != "" { 570 x := strings.Index(ConnectOnly, ":") 571 if x == -1 { 572 ConnectOnly = fmt.Sprint(ConnectOnly, ":", DefaultTcpPort()) 573 } 574 oa, e := net.ResolveTCPAddr("tcp4", ConnectOnly) 575 if e != nil { 576 println(e.Error(), ConnectOnly) 577 os.Exit(1) 578 } 579 proxyPeer = NewPeer(nil) 580 proxyPeer.Services = Services 581 copy(proxyPeer.Ip4[:], oa.IP[12:16]) 582 proxyPeer.Port = uint16(oa.Port) 583 fmt.Printf("Connect to bitcoin network via %d.%d.%d.%d:%d\n", 584 proxyPeer.Ip4[0], proxyPeer.Ip4[1], proxyPeer.Ip4[2], proxyPeer.Ip4[3], proxyPeer.Port) 585 } else if PeerDB.Count() < MinPeersInDB { 586 go func() { 587 if !Testnet { 588 initSeeds([]string{ 589 "seed.bitcoin.sipa.be", 590 "dnsseed.bluematt.me", 591 "dnsseed.bitcoin.dashjr.org", 592 "seed.bitcoinstats.com", 593 "seed.bitcoin.jonasschnelli.ch", 594 "seed.btc.petertodd.org", 595 "seed.bitcoin.sprovoost.nl", 596 "seed.bitnodes.io", 597 "dnsseed.emzy.de", 598 "seed.bitcoin.wiz.biz", 599 }, 8333) 600 } else { 601 initSeeds([]string{ 602 "testnet-seed.bitcoin.jonasschnelli.ch", 603 "seed.tbtc.petertodd.org", 604 "seed.testnet.bitcoin.sprovoost.nl", 605 "testnet-seed.bluematt.me", 606 }, 18333) 607 } 608 }() 609 } 610 } 611 612 func ClosePeerDB() { 613 if PeerDB != nil { 614 fmt.Println("Closing peer DB") 615 PeerDB.Sync() 616 PeerDB.Defrag(true) 617 PeerDB.Close() 618 PeerDB = nil 619 } 620 }