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  }