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  }