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