git.gammaspectra.live/P2Pool/consensus/v3@v3.8.0/p2pool/p2p/client.go (about) 1 package p2p 2 3 import ( 4 "bufio" 5 "crypto/rand" 6 "encoding/binary" 7 "errors" 8 "fmt" 9 "git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain" 10 p2pooltypes "git.gammaspectra.live/P2Pool/consensus/v3/p2pool/types" 11 "git.gammaspectra.live/P2Pool/consensus/v3/types" 12 "git.gammaspectra.live/P2Pool/consensus/v3/utils" 13 fasthex "github.com/tmthrgd/go-hex" 14 "io" 15 unsafeRandom "math/rand/v2" 16 "net" 17 "net/netip" 18 "slices" 19 "sync" 20 "sync/atomic" 21 "time" 22 "unsafe" 23 ) 24 25 const DefaultBanTime = time.Second * 600 26 const PeerListResponseMaxPeers = 16 27 const PeerRequestDelay = 60 28 29 const MaxBufferSize = 128 * 1024 30 31 var smallBufferPool = sync.Pool{ 32 New: func() any { 33 return make([]byte, 16384) 34 }, 35 } 36 37 func getBuffer(length int) []byte { 38 if length <= 16384 { 39 return smallBufferPool.Get().([]byte) 40 } 41 return make([]byte, length) 42 } 43 44 func returnBuffer(x []byte) { 45 if len(x) <= 16384 { 46 smallBufferPool.Put(x) 47 } 48 } 49 50 type Client struct { 51 // Peer general static-ish information 52 PeerId atomic.Uint64 53 VersionInformation p2pooltypes.PeerVersionInformation 54 ListenPort atomic.Uint32 55 ConnectionTime time.Time 56 AddressPort netip.AddrPort 57 58 // Peer general dynamic-ish information 59 BroadcastMaxHeight atomic.Uint64 60 PingDuration atomic.Uint64 61 62 // Internal values 63 Owner *Server 64 Connection *net.TCPConn 65 banErrorLock sync.Mutex 66 banError error 67 LastBroadcastTimestamp atomic.Uint64 68 LastBlockRequestTimestamp atomic.Uint64 69 LastIncomingPeerListRequestTime time.Time 70 LastActiveTimestamp atomic.Uint64 71 LastPeerListRequestTimestamp atomic.Uint64 72 NextOutgoingPeerListRequestTimestamp atomic.Uint64 73 74 expectedMessage MessageId 75 IsIncomingConnection bool 76 77 Closed atomic.Bool 78 //State properties 79 HandshakeComplete atomic.Bool 80 SentHandshakeSolution atomic.Bool 81 82 LastKnownTip atomic.Pointer[sidechain.PoolBlock] 83 84 BroadcastedHashes *utils.CircularBuffer[types.Hash] 85 RequestedHashes *utils.CircularBuffer[types.Hash] 86 87 blockPendingRequests chan types.Hash 88 89 handshakeChallenge HandshakeChallenge 90 91 closeChannel chan struct{} 92 93 sendLock sync.Mutex 94 } 95 96 func NewClient(owner *Server, conn *net.TCPConn) *Client { 97 c := &Client{ 98 Owner: owner, 99 Connection: conn, 100 ConnectionTime: time.Now(), 101 AddressPort: netip.MustParseAddrPort(conn.RemoteAddr().String()), 102 expectedMessage: MessageHandshakeChallenge, 103 closeChannel: make(chan struct{}), 104 BroadcastedHashes: utils.NewCircularBuffer[types.Hash](8), 105 RequestedHashes: utils.NewCircularBuffer[types.Hash](16), 106 blockPendingRequests: make(chan types.Hash, 100), //allow max 100 pending block requests at the same time 107 } 108 109 c.LastActiveTimestamp.Store(uint64(time.Now().Unix())) 110 111 return c 112 } 113 114 func (c *Client) BanError() error { 115 c.banErrorLock.Lock() 116 c.banErrorLock.Unlock() 117 return c.banError 118 } 119 120 func (c *Client) SetError(err error) { 121 c.banErrorLock.Lock() 122 c.banErrorLock.Unlock() 123 if c.banError == nil { 124 c.banError = err 125 } 126 } 127 128 func (c *Client) Ban(duration time.Duration, err error) { 129 130 c.SetError(err) 131 c.Owner.Ban(c.AddressPort.Addr(), duration, err) 132 c.Owner.RemoveFromPeerList(c.AddressPort.Addr()) 133 c.Close() 134 } 135 136 func (c *Client) OnAfterHandshake() { 137 c.SendListenPort() 138 c.SendBlockRequest(types.ZeroHash) 139 utils.Logf("P2PClient", "Peer %s after handshake complete: sent LISTEN_PORT + tip BLOCK_REQUEST", c.AddressPort.String()) 140 141 c.LastBroadcastTimestamp.Store(uint64(time.Now().Unix())) 142 } 143 144 func (c *Client) getNextBlockRequest() (id types.Hash, ok bool) { 145 select { 146 case id = <-c.blockPendingRequests: 147 return id, true 148 default: 149 return types.ZeroHash, false 150 } 151 } 152 153 // PreferredAddressPort Return the address and port to which the peer is most probably reachable 154 func (c *Client) PreferredAddressPort() netip.AddrPort { 155 if listenPort := c.ListenPort.Load(); listenPort != 0 { 156 return netip.AddrPortFrom(c.AddressPort.Addr(), uint16(listenPort)) 157 } 158 159 //take default from consensus 160 return netip.AddrPortFrom(c.AddressPort.Addr(), c.Owner.Consensus().DefaultPort()) 161 } 162 163 func (c *Client) SendListenPort() { 164 c.SendMessage(&ClientMessage{ 165 MessageId: MessageListenPort, 166 Buffer: binary.LittleEndian.AppendUint32(nil, uint32(c.Owner.ExternalListenPort())), 167 }) 168 } 169 170 func (c *Client) SendMissingBlockRequestAtRandom(hash types.Hash, allowedClients []*Client) []*Client { 171 if hash == types.ZeroHash || c.Owner.SideChain().GetPoolBlockByTemplateId(hash) != nil { 172 return allowedClients 173 } 174 175 if b := c.Owner.GetCachedBlock(hash); b != nil { 176 utils.Logf("P2PClient", "Using cached block for id = %s", hash.String()) 177 if _, err, _ := c.Owner.SideChain().AddPoolBlockExternal(b); err == nil { 178 return allowedClients 179 } 180 } 181 182 if len(allowedClients) == 0 { 183 allowedClients = append(allowedClients, c) 184 } 185 186 for len(allowedClients) > 0 { 187 k := unsafeRandom.IntN(len(allowedClients)) % len(allowedClients) 188 client := allowedClients[k] 189 if client.IsGood() && len(client.blockPendingRequests) < 20 { 190 client.SendBlockRequest(hash) 191 break 192 } else { 193 allowedClients = slices.Delete(allowedClients, k, k+1) 194 } 195 } 196 return allowedClients 197 } 198 199 func (c *Client) SendMissingBlockRequest(hash types.Hash) { 200 if hash == types.ZeroHash || c.Owner.SideChain().GetPoolBlockByTemplateId(hash) != nil { 201 return 202 } 203 204 if b := c.Owner.GetCachedBlock(hash); b != nil { 205 utils.Logf("P2PClient", "Using cached block for id = %s", hash.String()) 206 if missingBlocks, err, _ := c.Owner.SideChain().AddPoolBlockExternal(b); err == nil { 207 for _, id := range missingBlocks { 208 c.SendMissingBlockRequest(id) 209 } 210 return 211 } 212 } 213 214 // do not re-request hashes that have been requested 215 if !c.RequestedHashes.PushUnique(hash) { 216 return 217 } 218 219 // If the initial sync is not finished yet, try to ask the fastest peer too 220 if !c.Owner.SideChain().PreCalcFinished() { 221 fastest := c.Owner.GetFastestClient() 222 if fastest != nil && c != fastest && !c.Owner.SideChain().PreCalcFinished() { 223 //send towards the fastest peer as well 224 fastest.SendMissingBlockRequest(hash) 225 } 226 } 227 228 c.SendBlockRequest(hash) 229 } 230 231 func (c *Client) SendUniqueBlockRequest(hash types.Hash) { 232 if hash == types.ZeroHash { 233 return 234 } 235 236 // do not re-request hashes that have been requested 237 if !c.RequestedHashes.PushUnique(hash) { 238 return 239 } 240 241 c.SendBlockRequest(hash) 242 } 243 244 func (c *Client) SendBlockRequest(id types.Hash) { 245 c.SendBlockRequestWithBound(id, 80) 246 } 247 248 func (c *Client) SendBlockRequestWithBound(id types.Hash, bound int) bool { 249 if len(c.blockPendingRequests) < bound { 250 c.blockPendingRequests <- id 251 c.SendMessage(&ClientMessage{ 252 MessageId: MessageBlockRequest, 253 Buffer: id[:], 254 }) 255 return true 256 } 257 return false 258 } 259 260 func (c *Client) SendBlockNotify(id types.Hash) { 261 c.SendMessage(&ClientMessage{ 262 MessageId: MessageBlockNotify, 263 Buffer: id[:], 264 }) 265 } 266 267 func (c *Client) SendBlockResponse(block *sidechain.PoolBlock) { 268 if block != nil { 269 blockData, err := block.AppendBinaryFlags(make([]byte, 0, block.BufferLength()), false, false) 270 if err != nil { 271 utils.Logf("P2PClient", "Peer %s tried to respond with a block but received error, disconnecting: %s", c.AddressPort, err) 272 c.Close() 273 return 274 } 275 276 c.SendMessage(&ClientMessage{ 277 MessageId: MessageBlockResponse, 278 Buffer: append(binary.LittleEndian.AppendUint32(make([]byte, 0, len(blockData)+4), uint32(len(blockData))), blockData...), 279 }) 280 281 } else { 282 c.SendMessage(&ClientMessage{ 283 MessageId: MessageBlockResponse, 284 Buffer: binary.LittleEndian.AppendUint32(nil, 0), 285 }) 286 } 287 } 288 289 func (c *Client) SendPeerListRequest() { 290 c.NextOutgoingPeerListRequestTimestamp.Store(uint64(time.Now().Unix()) + PeerRequestDelay + (unsafeRandom.Uint64() % (PeerRequestDelay + 1))) 291 c.SendMessage(&ClientMessage{ 292 MessageId: MessagePeerListRequest, 293 }) 294 c.LastPeerListRequestTimestamp.Store(uint64(time.Now().UnixMicro())) 295 //utils.Logf("P2PClient", "Sending PEER_LIST_REQUEST to %s", c.AddressPort.String()) 296 } 297 298 func (c *Client) SendPeerListResponse(list []netip.AddrPort) { 299 if len(list) > PeerListResponseMaxPeers { 300 return 301 } 302 buf := make([]byte, 0, 1+len(list)*(1+16+2)) 303 buf = append(buf, byte(len(list))) 304 for i := range list { 305 //TODO: check ipv4 gets sent properly 306 if list[i].Addr().Is6() && !p2pooltypes.IsPeerVersionInformation(list[i]) { 307 buf = append(buf, 1) 308 } else { 309 buf = append(buf, 0) 310 } 311 ip := list[i].Addr().As16() 312 buf = append(buf, ip[:]...) 313 buf = binary.LittleEndian.AppendUint16(buf, list[i].Port()) 314 } 315 c.SendMessage(&ClientMessage{ 316 MessageId: MessagePeerListResponse, 317 Buffer: buf, 318 }) 319 } 320 321 func (c *Client) IsGood() bool { 322 return c.HandshakeComplete.Load() && c.ListenPort.Load() > 0 323 } 324 325 func (c *Client) OnConnection() { 326 c.LastActiveTimestamp.Store(uint64(time.Now().Unix())) 327 328 c.sendHandshakeChallenge() 329 330 var messageIdBuf [1]byte 331 var messageId MessageId 332 for !c.Closed.Load() { 333 if _, err := io.ReadFull(c, messageIdBuf[:]); err != nil { 334 c.Close() 335 return 336 } 337 338 messageId = MessageId(messageIdBuf[0]) 339 340 if !c.HandshakeComplete.Load() && messageId != c.expectedMessage { 341 c.Ban(DefaultBanTime, fmt.Errorf("unexpected pre-handshake message: got %d, expected %d", messageId, c.expectedMessage)) 342 return 343 } 344 345 switch messageId { 346 case MessageHandshakeChallenge: 347 if c.HandshakeComplete.Load() { 348 c.Ban(DefaultBanTime, errors.New("got HANDSHAKE_CHALLENGE but handshake is complete")) 349 return 350 } 351 352 var challenge HandshakeChallenge 353 var peerId uint64 354 if err := binary.Read(c, binary.LittleEndian, &challenge); err != nil { 355 c.Ban(DefaultBanTime, err) 356 return 357 } 358 if err := binary.Read(c, binary.LittleEndian, &peerId); err != nil { 359 c.Ban(DefaultBanTime, err) 360 return 361 } 362 363 if peerId == c.Owner.PeerId() { 364 c.HandshakeComplete.Store(true) 365 c.SetError(errors.New("connected to self")) 366 //tried to connect to self 367 c.Close() 368 return 369 } 370 371 c.PeerId.Store(peerId) 372 373 if ok, otherClient := func() (bool, *Client) { 374 c.Owner.clientsLock.RLock() 375 defer c.Owner.clientsLock.RUnlock() 376 for _, client := range c.Owner.clients { 377 if client != c && client.PeerId.Load() == peerId { 378 return true, client 379 } 380 } 381 return false, nil 382 }(); ok { 383 c.HandshakeComplete.Store(true) 384 c.SetError(fmt.Errorf("already connected as %s (%d)", otherClient.AddressPort, otherClient.PeerId.Load())) 385 //same peer 386 utils.Logf("P2PClient", "Connected to other same peer: %s (%d) is also %s (%d)", c.AddressPort, c.PeerId.Load(), otherClient.AddressPort, otherClient.PeerId.Load()) 387 c.Close() 388 return 389 } 390 391 c.sendHandshakeSolution(challenge) 392 393 c.expectedMessage = MessageHandshakeSolution 394 395 if c.HandshakeComplete.Load() && c.SentHandshakeSolution.Load() { 396 c.OnAfterHandshake() 397 } 398 399 case MessageHandshakeSolution: 400 if c.HandshakeComplete.Load() { 401 c.Ban(DefaultBanTime, errors.New("got HANDSHAKE_SOLUTION but handshake is complete")) 402 return 403 } 404 405 var challengeHash types.Hash 406 var solution uint64 407 if err := binary.Read(c, binary.LittleEndian, &challengeHash); err != nil { 408 c.Ban(DefaultBanTime, err) 409 return 410 } 411 if err := binary.Read(c, binary.LittleEndian, &solution); err != nil { 412 c.Ban(DefaultBanTime, err) 413 return 414 } 415 416 if c.IsIncomingConnection { 417 //TODO: consensus MergeMiningId 418 if hash, ok := CalculateChallengeHash(c.handshakeChallenge, c.Owner.HandshakeConsensusId(), solution); !ok { 419 //not enough PoW 420 c.Ban(DefaultBanTime, fmt.Errorf("not enough PoW on HANDSHAKE_SOLUTION, challenge = %s, solution = %d, calculated hash = %s, expected hash = %s", fasthex.EncodeToString(c.handshakeChallenge[:]), solution, hash.String(), challengeHash.String())) 421 return 422 } else if hash != challengeHash { 423 //wrong hash 424 c.Ban(DefaultBanTime, fmt.Errorf("wrong hash HANDSHAKE_SOLUTION, challenge = %s, solution = %d, calculated hash = %s, expected hash = %s", fasthex.EncodeToString(c.handshakeChallenge[:]), solution, hash.String(), challengeHash.String())) 425 return 426 } 427 } else { 428 //TODO: consensus MergeMiningId 429 if hash, _ := CalculateChallengeHash(c.handshakeChallenge, c.Owner.HandshakeConsensusId(), solution); hash != challengeHash { 430 //wrong hash 431 c.Ban(DefaultBanTime, fmt.Errorf("wrong hash HANDSHAKE_SOLUTION, challenge = %s, solution = %d, calculated hash = %s, expected hash = %s", fasthex.EncodeToString(c.handshakeChallenge[:]), solution, hash.String(), challengeHash.String())) 432 return 433 } 434 } 435 c.HandshakeComplete.Store(true) 436 437 if c.HandshakeComplete.Load() && c.SentHandshakeSolution.Load() { 438 c.OnAfterHandshake() 439 } 440 441 case MessageListenPort: 442 if c.ListenPort.Load() != 0 { 443 c.Ban(DefaultBanTime, errors.New("got LISTEN_PORT but we already received it")) 444 return 445 } 446 447 var listenPort uint32 448 449 if err := binary.Read(c, binary.LittleEndian, &listenPort); err != nil { 450 c.Ban(DefaultBanTime, err) 451 return 452 } 453 454 if listenPort == 0 || listenPort >= 65536 { 455 c.Ban(DefaultBanTime, fmt.Errorf("listen port out of range: %d", listenPort)) 456 return 457 } 458 c.ListenPort.Store(listenPort) 459 c.Owner.UpdateInPeerList(netip.AddrPortFrom(c.AddressPort.Addr(), uint16(c.ListenPort.Load()))) 460 case MessageBlockRequest: 461 c.LastBlockRequestTimestamp.Store(uint64(time.Now().Unix())) 462 463 var templateId types.Hash 464 if err := binary.Read(c, binary.LittleEndian, &templateId); err != nil { 465 c.Ban(DefaultBanTime, err) 466 return 467 } 468 469 var block *sidechain.PoolBlock 470 //if empty, return chain tip 471 if templateId == types.ZeroHash { 472 utils.Logf("P2PClient", "Peer %s requested tip", c.AddressPort.String()) 473 // Don't return stale chain tip 474 if block = c.Owner.SideChain().GetChainTip(); block != nil && (block.Main.Coinbase.GenHeight+2) < c.Owner.MainChain().GetMinerDataTip().Height { 475 block = nil 476 } 477 } else { 478 block = c.Owner.SideChain().GetPoolBlockByTemplateId(templateId) 479 if block == nil { 480 utils.Logf("P2PClient", "Peer %s requested id = %s, got nil", c.AddressPort.String(), templateId) 481 } else { 482 utils.Logf("P2PClient", "Peer %s requested id = %s, got height = %d, main height = %d", c.AddressPort.String(), templateId, block.Side.Height, block.Main.Coinbase.GenHeight) 483 } 484 } 485 486 if block != nil && c.Owner.VersionInformation().SupportsFeature(p2pooltypes.FeatureBlockNotify) && c.VersionInformation.SupportsFeature(p2pooltypes.FeatureBlockNotify) { 487 c.SendBlockNotify(block.Side.Parent) 488 for _, uncleId := range block.Side.Uncles { 489 c.SendBlockNotify(uncleId) 490 } 491 if parent := c.Owner.SideChain().GetParent(block); parent != nil { 492 c.SendBlockNotify(parent.Side.Parent) 493 } 494 } 495 496 c.SendBlockResponse(block) 497 case MessageBlockResponse: 498 block := &sidechain.PoolBlock{ 499 Metadata: sidechain.PoolBlockReceptionMetadata{ 500 LocalTime: time.Now().UTC(), 501 AddressPort: c.PreferredAddressPort(), 502 PeerId: c.PeerId.Load(), 503 SoftwareId: uint32(c.VersionInformation.SoftwareId), 504 SoftwareVersion: uint32(c.VersionInformation.SoftwareVersion), 505 }, 506 } 507 508 expectedBlockId, ok := c.getNextBlockRequest() 509 510 if !ok { 511 c.Ban(DefaultBanTime, errors.New("unexpected BLOCK_RESPONSE")) 512 return 513 } 514 515 isChainTipBlockRequest := expectedBlockId == types.ZeroHash 516 517 var blockSize uint32 518 if err := binary.Read(c, binary.LittleEndian, &blockSize); err != nil { 519 //TODO warn 520 c.Ban(DefaultBanTime, err) 521 return 522 } else if blockSize == 0 { 523 utils.Logf("P2PClient", "Peer %s sent nil BLOCK_RESPONSE to id = %s", c.AddressPort.String(), expectedBlockId) 524 if isChainTipBlockRequest && time.Now().Unix() >= int64(c.NextOutgoingPeerListRequestTimestamp.Load()) { 525 c.SendPeerListRequest() 526 } 527 break 528 } else { 529 if err = block.FromReader(c.Owner.Consensus(), c.Owner.SideChain().DerivationCache(), bufio.NewReaderSize(io.LimitReader(c, int64(blockSize)), int(blockSize))); err != nil { 530 //TODO warn 531 c.Ban(DefaultBanTime, err) 532 return 533 } else { 534 tipHash := block.FastSideTemplateId(c.Owner.Consensus()) 535 536 if isChainTipBlockRequest { 537 if lastTip := c.LastKnownTip.Load(); lastTip == nil || lastTip.Side.Height <= block.Side.Height { 538 if _, err = c.Owner.SideChain().PreprocessBlock(block); err == nil { 539 c.LastKnownTip.Store(block) 540 } 541 } 542 543 utils.Logf("P2PClient", "Peer %s tip is at id = %s, height = %d, main height = %d", c.AddressPort.String(), tipHash, block.Side.Height, block.Main.Coinbase.GenHeight) 544 peerHeight := block.Main.Coinbase.GenHeight 545 ourHeight := c.Owner.MainChain().GetMinerDataTip().Height 546 547 if (peerHeight + 2) < ourHeight { 548 c.Ban(DefaultBanTime, fmt.Errorf("mining on top of a stale block (mainchain peer height %d, expected >= %d)", peerHeight, ourHeight)) 549 return 550 } 551 552 //Atomic max, not necessary as no external writers exist 553 topHeight := max(c.BroadcastMaxHeight.Load(), block.Side.Height) 554 for { 555 if oldHeight := c.BroadcastMaxHeight.Swap(topHeight); oldHeight <= topHeight { 556 break 557 } else { 558 topHeight = oldHeight 559 } 560 } 561 562 if time.Now().Unix() >= int64(c.NextOutgoingPeerListRequestTimestamp.Load()) { 563 c.SendPeerListRequest() 564 } 565 } 566 if c.Owner.SideChain().BlockSeen(block) { 567 //utils.Logf("P2PClient", "Peer %s block id = %s, height = %d (nonce %d, extra_nonce %d) was received before, skipping it", c.AddressPort.String(), types.HashFromBytes(block.CoinbaseExtra(sidechain.SideIdentifierHash)), block.Side.Height, block.Main.Nonce, block.ExtraNonce()) 568 break 569 } 570 if missingBlocks, err, ban := c.Owner.SideChain().AddPoolBlockExternal(block); err != nil { 571 if ban { 572 c.Ban(DefaultBanTime, err) 573 return 574 } else { 575 utils.Logf("P2PClient", "Peer %s error adding block id = %s, height = %d, main height = %d, timestamp = %d", c.AddressPort.String(), tipHash, block.Side.Height, block.Main.Coinbase.GenHeight, block.Main.Timestamp) 576 break 577 } 578 } else { 579 if !isChainTipBlockRequest && expectedBlockId != block.SideTemplateId(c.Owner.SideChain().Consensus()) { 580 c.Ban(DefaultBanTime, fmt.Errorf("expected block id = %s, got %s", expectedBlockId.String(), block.SideTemplateId(c.Owner.SideChain().Consensus()).String())) 581 return 582 } 583 for _, id := range missingBlocks { 584 c.SendMissingBlockRequest(id) 585 } 586 } 587 } 588 } 589 590 case MessageBlockBroadcast, MessageBlockBroadcastCompact: 591 block := &sidechain.PoolBlock{ 592 Metadata: sidechain.PoolBlockReceptionMetadata{ 593 LocalTime: time.Now().UTC(), 594 AddressPort: c.PreferredAddressPort(), 595 PeerId: c.PeerId.Load(), 596 SoftwareId: uint32(c.VersionInformation.SoftwareId), 597 SoftwareVersion: uint32(c.VersionInformation.SoftwareVersion), 598 }, 599 } 600 var blockSize uint32 601 if err := binary.Read(c, binary.LittleEndian, &blockSize); err != nil { 602 //TODO warn 603 c.Ban(DefaultBanTime, err) 604 return 605 } else if blockSize == 0 { 606 //NOT found 607 //TODO log 608 break 609 } else if messageId == MessageBlockBroadcastCompact { 610 if err = block.FromCompactReader(c.Owner.Consensus(), c.Owner.SideChain().DerivationCache(), bufio.NewReaderSize(io.LimitReader(c, int64(blockSize)), int(blockSize))); err != nil { 611 //TODO warn 612 c.Ban(DefaultBanTime, err) 613 return 614 } 615 } else { 616 if err = block.FromReader(c.Owner.Consensus(), c.Owner.SideChain().DerivationCache(), bufio.NewReaderSize(io.LimitReader(c, int64(blockSize)), int(blockSize))); err != nil { 617 //TODO warn 618 c.Ban(DefaultBanTime, err) 619 return 620 } 621 } 622 623 //Atomic max, not necessary as no external writers exist 624 topHeight := max(c.BroadcastMaxHeight.Load(), block.Side.Height) 625 for { 626 if oldHeight := c.BroadcastMaxHeight.Swap(topHeight); oldHeight <= topHeight { 627 break 628 } else { 629 topHeight = oldHeight 630 } 631 } 632 633 //utils.Logf("P2PClient", "Peer %s broadcast tip is at id = %s, height = %d, main height = %d", c.AddressPort.String(), tipHash, block.Side.Height, block.Main.Coinbase.GenHeight) 634 635 if missingBlocks, err := c.Owner.SideChain().PreprocessBlock(block); err != nil { 636 for _, id := range missingBlocks { 637 c.SendMissingBlockRequest(id) 638 } 639 //TODO: ban here, but sort blocks properly, maybe a queue to re-try? 640 break 641 } else { 642 tipHash := block.FastSideTemplateId(c.Owner.Consensus()) 643 644 c.BroadcastedHashes.Push(tipHash) 645 646 c.LastBroadcastTimestamp.Store(uint64(time.Now().Unix())) 647 648 if lastTip := c.LastKnownTip.Load(); lastTip == nil || lastTip.Side.Height <= block.Side.Height { 649 c.LastKnownTip.Store(block) 650 } 651 652 ourMinerData := c.Owner.MainChain().GetMinerDataTip() 653 654 if block.Main.PreviousId != ourMinerData.PrevId { 655 // This peer is mining on top of a different Monero block, investigate it 656 657 peerHeight := block.Main.Coinbase.GenHeight 658 ourHeight := ourMinerData.Height 659 660 if peerHeight < ourHeight { 661 if (ourHeight - peerHeight) < 5 { 662 elapsedTime := time.Now().Sub(ourMinerData.TimeReceived) 663 if (ourHeight-peerHeight) > 1 || elapsedTime > (time.Second*10) { 664 utils.Logf("P2PClient", "Peer %s broadcasted a stale block (%d ms late, mainchain height %d, expected >= %d), ignoring it", c.AddressPort.String(), elapsedTime.Milliseconds(), peerHeight, ourHeight) 665 } 666 } else { 667 c.Ban(DefaultBanTime, fmt.Errorf("broadcasted an unreasonably stale block (mainchain height %d, expected >= %d)", peerHeight, ourHeight)) 668 return 669 } 670 } else if peerHeight > ourHeight { 671 if peerHeight >= (ourHeight + 2) { 672 utils.Logf("P2PClient", "Peer %s is ahead on mainchain (mainchain height %d, your height %d). Is monerod stuck or lagging?", c.AddressPort.String(), peerHeight, ourHeight) 673 } 674 } else { 675 utils.Logf("P2PClient", "Peer %s is mining on an alternative mainchain tip (mainchain height %d, previous_id = %s)", c.AddressPort.String(), peerHeight, block.Main.PreviousId) 676 } 677 } 678 679 if c.Owner.SideChain().BlockSeen(block) { 680 //utils.Logf("P2PClient", "Peer %s block id = %s, height = %d (nonce %d, extra_nonce %d) was received before, skipping it", c.AddressPort.String(), types.HashFromBytes(block.CoinbaseExtra(sidechain.SideIdentifierHash)), block.Side.Height, block.Main.Nonce, block.ExtraNonce()) 681 break 682 } 683 684 block.WantBroadcast.Store(true) 685 if missingBlocks, err, ban := c.Owner.SideChain().AddPoolBlockExternal(block); err != nil { 686 if ban { 687 c.Ban(DefaultBanTime, err) 688 } else { 689 utils.Logf("P2PClient", "Peer %s error adding block id = %s, height = %d, main height = %d, timestamp = %d", c.AddressPort.String(), tipHash, block.Side.Height, block.Main.Coinbase.GenHeight, block.Main.Timestamp) 690 } 691 return 692 } else { 693 for _, id := range missingBlocks { 694 c.SendMissingBlockRequest(id) 695 } 696 } 697 } 698 case MessagePeerListRequest: 699 connectedPeerList := c.Owner.Clients() 700 701 entriesToSend := make([]netip.AddrPort, 0, PeerListResponseMaxPeers) 702 703 // Send every 4th peer on average, selected at random 704 peersToSendTarget := min(PeerListResponseMaxPeers, max(len(connectedPeerList)/4, 1)) 705 n := 0 706 for _, peer := range connectedPeerList { 707 if peer.AddressPort.Addr().IsLoopback() || peer.AddressPort.Addr().IsPrivate() || !peer.IsGood() || peer.AddressPort.Addr().Compare(c.AddressPort.Addr()) == 0 { 708 continue 709 } 710 711 n++ 712 713 // Use https://en.wikipedia.org/wiki/Reservoir_sampling algorithm 714 if len(entriesToSend) < peersToSendTarget { 715 entriesToSend = append(entriesToSend, peer.AddressPort) 716 } 717 718 k := unsafeRandom.IntN(n) 719 if k < peersToSendTarget { 720 entriesToSend[k] = peer.AddressPort 721 } 722 } 723 724 // Check whether to send version to target or not 725 if c.LastIncomingPeerListRequestTime.IsZero() && c.Owner.VersionInformation().SupportsFeature(p2pooltypes.FeaturePeerInformationExchange) && c.VersionInformation.SupportsFeature(p2pooltypes.FeaturePeerInformationReceive) { 726 //first, send version / protocol information 727 if len(entriesToSend) == 0 { 728 entriesToSend = append(entriesToSend, c.Owner.VersionInformation().ToAddrPort()) 729 } else { 730 entriesToSend[0] = c.Owner.VersionInformation().ToAddrPort() 731 } 732 } 733 734 lastLen := len(entriesToSend) 735 736 if lastLen < PeerListResponseMaxPeers { 737 //improvement from normal p2pool: pad response with other peers from peer list, not connected 738 peerList := c.Owner.PeerList() 739 for i := lastLen; i < PeerListResponseMaxPeers && len(peerList) > 0; i++ { 740 k := unsafeRandom.IntN(len(peerList)) % len(peerList) 741 peer := peerList[k] 742 if !slices.ContainsFunc(entriesToSend, func(addrPort netip.AddrPort) bool { 743 return addrPort.Addr().Compare(peer.AddressPort.Addr()) == 0 744 }) { 745 entriesToSend = append(entriesToSend, peer.AddressPort) 746 } 747 } 748 } 749 750 var hasIpv6 bool 751 for _, e := range entriesToSend { 752 if e.Addr().Is6() { 753 hasIpv6 = true 754 break 755 } 756 } 757 758 //include one ipv6, if existent 759 if !hasIpv6 { 760 peerList := c.Owner.PeerList() 761 unsafeRandom.Shuffle(len(peerList), func(i, j int) { 762 peerList[i] = peerList[j] 763 }) 764 for _, p := range c.Owner.PeerList() { 765 if p.AddressPort.Addr().Is4In6() || p.AddressPort.Addr().Is6() { 766 if len(entriesToSend) < PeerListResponseMaxPeers { 767 entriesToSend = append(entriesToSend, p.AddressPort) 768 } else { 769 entriesToSend[len(entriesToSend)-1] = p.AddressPort 770 } 771 break 772 } 773 } 774 } 775 776 c.LastIncomingPeerListRequestTime = time.Now() 777 778 c.SendPeerListResponse(entriesToSend) 779 case MessagePeerListResponse: 780 if numPeers, err := c.ReadByte(); err != nil { 781 c.Ban(DefaultBanTime, err) 782 return 783 } else if numPeers > PeerListResponseMaxPeers { 784 c.Ban(DefaultBanTime, fmt.Errorf("too many peers on PEER_LIST_RESPONSE num_peers = %d", numPeers)) 785 return 786 } else { 787 firstPeerResponse := c.PingDuration.Swap(uint64(max(time.Now().Sub(time.UnixMicro(int64(c.LastPeerListRequestTimestamp.Load()))), 0))) == 0 788 var rawIp [16]byte 789 var port uint16 790 791 if firstPeerResponse { 792 utils.Logf("P2PClient", "Peer %s initial PEER_LIST_RESPONSE: num_peers %d", c.AddressPort.String(), numPeers) 793 } 794 for i := uint8(0); i < numPeers; i++ { 795 if isV6, err := c.ReadByte(); err != nil { 796 c.Ban(DefaultBanTime, err) 797 return 798 } else { 799 if _, err = c.Read(rawIp[:]); err != nil { 800 c.Ban(DefaultBanTime, err) 801 return 802 } else if err = binary.Read(c, binary.LittleEndian, &port); err != nil { 803 c.Ban(DefaultBanTime, err) 804 return 805 } 806 807 if isV6 == 0 { 808 if rawIp[12] == 0 || rawIp[12] >= 224 { 809 // Ignore 0.0.0.0/8 (special-purpose range for "this network") and 224.0.0.0/3 (IP multicast and reserved ranges) 810 811 // Check for protocol version message 812 if binary.LittleEndian.Uint32(rawIp[12:]) == 0xFFFFFFFF && port == 0xFFFF { 813 c.VersionInformation.Protocol = p2pooltypes.ProtocolVersion(binary.LittleEndian.Uint32(rawIp[0:])) 814 c.VersionInformation.SoftwareVersion = p2pooltypes.SoftwareVersion(binary.LittleEndian.Uint32(rawIp[4:])) 815 c.VersionInformation.SoftwareId = p2pooltypes.SoftwareId(binary.LittleEndian.Uint32(rawIp[8:])) 816 utils.Logf("P2PClient", "Peer %s version information: %s", c.AddressPort.String(), c.VersionInformation.String()) 817 818 c.afterInitialProtocolExchange() 819 } 820 continue 821 } 822 823 copy(rawIp[:], make([]byte, 10)) 824 rawIp[10], rawIp[11] = 0xFF, 0xFF 825 826 } 827 828 c.Owner.AddToPeerList(netip.AddrPortFrom(netip.AddrFrom16(rawIp).Unmap(), port)) 829 } 830 } 831 } 832 case MessageBlockNotify: 833 c.LastBlockRequestTimestamp.Store(uint64(time.Now().Unix())) 834 835 var templateId types.Hash 836 if err := binary.Read(c, binary.LittleEndian, &templateId); err != nil { 837 c.Ban(DefaultBanTime, err) 838 return 839 } 840 841 c.BroadcastedHashes.Push(templateId) 842 843 // If we don't know about this block, request it from this peer. The peer can do it to speed up our initial sync, for example. 844 if tip := c.Owner.SideChain().GetPoolBlockByTemplateId(templateId); tip == nil { 845 //TODO: prevent sending duplicate requests 846 if c.SendBlockRequestWithBound(templateId, 25) { 847 848 } 849 } else { 850 if lastTip := c.LastKnownTip.Load(); lastTip == nil || lastTip.Side.Height <= tip.Side.Height { 851 c.LastKnownTip.Store(tip) 852 } 853 } 854 855 case MessageInternal: 856 internalMessageId, err := binary.ReadUvarint(c) 857 if err != nil { 858 c.Ban(DefaultBanTime, err) 859 return 860 } 861 messageSize, err := binary.ReadUvarint(c) 862 if err != nil { 863 c.Ban(DefaultBanTime, err) 864 return 865 } 866 reader := io.LimitReader(c, int64(messageSize)) 867 868 _ = reader 869 870 switch InternalMessageId(internalMessageId) { 871 default: 872 c.Ban(DefaultBanTime, fmt.Errorf("unknown InternalMessageId %d", internalMessageId)) 873 return 874 } 875 default: 876 c.Ban(DefaultBanTime, fmt.Errorf("unknown MessageId %d", messageId)) 877 return 878 } 879 880 c.LastActiveTimestamp.Store(uint64(time.Now().Unix())) 881 } 882 } 883 884 func (c *Client) afterInitialProtocolExchange() { 885 //TODO: use notify to send fast sync data 886 } 887 888 func (c *Client) sendHandshakeChallenge() { 889 if _, err := rand.Read(c.handshakeChallenge[:]); err != nil { 890 utils.Logf("P2PServer", "Unable to generate handshake challenge for %s", c.AddressPort.String()) 891 c.Close() 892 return 893 } 894 895 var buf [HandshakeChallengeSize + int(unsafe.Sizeof(uint64(0)))]byte 896 copy(buf[:], c.handshakeChallenge[:]) 897 binary.LittleEndian.PutUint64(buf[HandshakeChallengeSize:], c.Owner.PeerId()) 898 899 c.SendMessage(&ClientMessage{ 900 MessageId: MessageHandshakeChallenge, 901 Buffer: buf[:], 902 }) 903 } 904 905 func (c *Client) sendHandshakeSolution(challenge HandshakeChallenge) { 906 stop := &c.Closed 907 if c.IsIncomingConnection { 908 stop = &atomic.Bool{} 909 stop.Store(true) 910 } 911 912 //TODO: consensus MergeMiningId 913 if solution, hash, ok := FindChallengeSolution(challenge, c.Owner.HandshakeConsensusId(), stop); ok || c.IsIncomingConnection { 914 915 var buf [HandshakeChallengeSize + types.HashSize]byte 916 copy(buf[:], hash[:]) 917 binary.LittleEndian.PutUint64(buf[types.HashSize:], solution) 918 919 c.SendMessage(&ClientMessage{ 920 MessageId: MessageHandshakeSolution, 921 Buffer: buf[:], 922 }) 923 c.SentHandshakeSolution.Store(true) 924 } 925 } 926 927 // Read reads from underlying connection, on error it will Close 928 func (c *Client) Read(buf []byte) (n int, err error) { 929 if n, err = c.Connection.Read(buf); err != nil { 930 c.Close() 931 } 932 return 933 } 934 935 type ClientMessage struct { 936 MessageId MessageId 937 Buffer []byte 938 } 939 940 func (c *Client) SendMessage(message *ClientMessage) { 941 if !c.Closed.Load() { 942 bufLen := len(message.Buffer) + 1 943 if bufLen > MaxBufferSize { 944 utils.Logf("P2PClient", "Peer %s tried to send more than %d bytes, sent %d, disconnecting", c.AddressPort, MaxBufferSize, len(message.Buffer)+1) 945 c.Close() 946 return 947 } 948 949 buf := getBuffer(bufLen) 950 defer returnBuffer(buf) 951 buf[0] = byte(message.MessageId) 952 copy(buf[1:], message.Buffer) 953 //c.sendLock.Lock() 954 //defer c.sendLock.Unlock() 955 if err := c.Connection.SetWriteDeadline(time.Now().Add(time.Second * 5)); err != nil { 956 c.Close() 957 } else if _, err = c.Connection.Write(buf[:bufLen]); err != nil { 958 c.Close() 959 } 960 //_, _ = c.Write(message.Buffer) 961 } 962 } 963 964 // ReadByte reads from underlying connection, on error it will Close 965 func (c *Client) ReadByte() (b byte, err error) { 966 var buf [1]byte 967 if _, err = c.Connection.Read(buf[:]); err != nil && c.Closed.Load() { 968 c.Close() 969 } 970 return buf[0], err 971 } 972 973 func (c *Client) Close() bool { 974 if c.Closed.Swap(true) { 975 return false 976 } 977 978 if !c.HandshakeComplete.Load() { 979 c.Ban(DefaultBanTime, errors.New("disconnected before finishing handshake")) 980 } 981 982 func() { 983 c.Owner.clientsLock.Lock() 984 defer c.Owner.clientsLock.Unlock() 985 if c.Owner.fastestPeer == c { 986 c.Owner.fastestPeer = nil 987 } 988 if i := slices.Index(c.Owner.clients, c); i != -1 { 989 c.Owner.clients = slices.Delete(c.Owner.clients, i, i+1) 990 if c.IsIncomingConnection { 991 c.Owner.NumIncomingConnections.Add(-1) 992 } else { 993 c.Owner.NumOutgoingConnections.Add(-1) 994 c.Owner.PendingOutgoingConnections.Replace(c.AddressPort.Addr().String(), "") 995 } 996 } 997 }() 998 999 _ = c.Connection.Close() 1000 close(c.closeChannel) 1001 1002 utils.Logf("P2PClient", "Peer %s connection closed", c.AddressPort.String()) 1003 return true 1004 }