2022-11-01 11:22:00 +00:00
package p2p
import (
2022-11-07 18:38:29 +00:00
"bufio"
2022-11-01 11:22:00 +00:00
"crypto/rand"
"encoding/binary"
2022-11-07 14:58:02 +00:00
"encoding/hex"
"errors"
"fmt"
2022-11-03 11:32:07 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
2022-11-01 11:22:00 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
2022-11-06 16:01:36 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
2022-11-01 11:22:00 +00:00
"golang.org/x/exp/slices"
2022-11-07 18:38:29 +00:00
"io"
2022-11-01 11:22:00 +00:00
"log"
2022-12-02 13:34:18 +00:00
unsafeRandom "math/rand"
2022-11-01 11:22:00 +00:00
"net"
"net/netip"
2022-12-09 12:13:00 +00:00
"sync"
2022-11-01 11:22:00 +00:00
"sync/atomic"
"time"
2022-11-03 11:32:07 +00:00
"unsafe"
2022-11-01 11:22:00 +00:00
)
2022-11-03 11:32:07 +00:00
const DefaultBanTime = time . Second * 600
2022-11-06 16:01:36 +00:00
const PeerListResponseMaxPeers = 16
2022-11-03 11:32:07 +00:00
2022-12-09 12:13:00 +00:00
// MaxBlockTemplateSize Max P2P message size (128 KB) minus BLOCK_RESPONSE header (5 bytes)
const MaxBlockTemplateSize = 128 * 1024 - ( 1 - 4 )
2022-11-01 11:22:00 +00:00
type Client struct {
2022-12-11 12:31:43 +00:00
// Peer general static-ish information
2022-12-11 16:27:46 +00:00
PeerId atomic . Uint64
2022-12-11 12:31:43 +00:00
VersionInformation PeerVersionInformation
IsIncomingConnection bool
ConnectionTime time . Time
ListenPort atomic . Uint32
AddressPort netip . AddrPort
// Peer general dynamic-ish information
BroadcastMaxHeight atomic . Uint64
PingDuration atomic . Uint64
// Internal values
Owner * Server
Connection * net . TCPConn
Closed atomic . Bool
LastBroadcast time . Time
LastBlockRequest time . Time
LastIncomingPeerListRequestTime time . Time
LastActiveTimestamp atomic . Uint64
LastPeerListRequestTimestamp atomic . Uint64
NextOutgoingPeerListRequestTimestamp atomic . Uint64
//State properties
HandshakeComplete atomic . Bool
2022-11-03 11:32:07 +00:00
2022-12-04 00:46:22 +00:00
BroadcastedHashes * utils . CircularBuffer [ types . Hash ]
2022-12-09 12:13:00 +00:00
RequestedHashes * utils . CircularBuffer [ types . Hash ]
2022-12-03 22:43:18 +00:00
2022-12-18 14:53:32 +00:00
blockPendingRequests chan types . Hash
chainTipBlockRequest atomic . Bool
2022-11-03 11:32:07 +00:00
2022-11-07 14:58:02 +00:00
expectedMessage MessageId
2022-11-01 11:22:00 +00:00
2022-11-07 14:58:02 +00:00
handshakeChallenge HandshakeChallenge
2022-11-06 10:58:19 +00:00
2022-11-07 14:58:02 +00:00
closeChannel chan struct { }
2022-12-09 12:13:00 +00:00
sendLock sync . Mutex
2022-11-01 11:22:00 +00:00
}
2022-12-09 12:13:00 +00:00
func NewClient ( owner * Server , conn * net . TCPConn ) * Client {
2022-11-01 11:22:00 +00:00
c := & Client {
2022-12-18 14:53:32 +00:00
Owner : owner ,
Connection : conn ,
ConnectionTime : time . Now ( ) ,
AddressPort : netip . MustParseAddrPort ( conn . RemoteAddr ( ) . String ( ) ) ,
expectedMessage : MessageHandshakeChallenge ,
closeChannel : make ( chan struct { } ) ,
BroadcastedHashes : utils . NewCircularBuffer [ types . Hash ] ( 8 ) ,
RequestedHashes : utils . NewCircularBuffer [ types . Hash ] ( 16 ) ,
blockPendingRequests : make ( chan types . Hash , 100 ) , //allow max 100 pending block requests at the same time
2022-11-01 11:22:00 +00:00
}
2022-12-11 12:31:43 +00:00
c . LastActiveTimestamp . Store ( uint64 ( time . Now ( ) . Unix ( ) ) )
2022-11-01 11:22:00 +00:00
return c
}
2022-11-07 14:58:02 +00:00
func ( c * Client ) Ban ( duration time . Duration , err error ) {
c . Owner . Ban ( c . AddressPort . Addr ( ) , duration , err )
2022-11-03 11:32:07 +00:00
c . Close ( )
}
func ( c * Client ) OnAfterHandshake ( ) {
c . SendListenPort ( )
c . SendBlockRequest ( types . ZeroHash )
2022-12-14 18:31:59 +00:00
c . LastBroadcast = time . Now ( )
2022-11-03 11:32:07 +00:00
}
2022-12-18 14:53:32 +00:00
func ( c * Client ) getNextBlockRequest ( ) ( id types . Hash , ok bool ) {
select {
case id = <- c . blockPendingRequests :
return id , true
default :
return types . ZeroHash , false
}
}
2022-11-03 11:32:07 +00:00
func ( c * Client ) SendListenPort ( ) {
2022-12-11 13:46:37 +00:00
if c . Owner . externalListenPort != 0 {
c . SendMessage ( & ClientMessage {
MessageId : MessageListenPort ,
Buffer : binary . LittleEndian . AppendUint32 ( nil , uint32 ( c . Owner . externalListenPort ) ) ,
} )
} else {
c . SendMessage ( & ClientMessage {
MessageId : MessageListenPort ,
Buffer : binary . LittleEndian . AppendUint32 ( nil , uint32 ( c . Owner . listenAddress . Port ( ) ) ) ,
} )
}
2022-11-03 11:32:07 +00:00
}
2022-12-04 14:15:33 +00:00
func ( c * Client ) SendMissingBlockRequest ( hash types . Hash ) {
2022-12-09 12:13:00 +00:00
if hash == types . ZeroHash {
return
}
2023-03-10 15:45:22 +00:00
if b := c . Owner . GetCachedBlock ( hash ) ; b != nil {
log . Printf ( "[P2PClient] Using cached block for id = %s" , hash . String ( ) )
if missingBlocks , err := c . Owner . SideChain ( ) . AddPoolBlockExternal ( b ) ; err == nil {
for _ , id := range missingBlocks {
c . SendMissingBlockRequest ( id )
}
return
}
}
2022-12-09 12:13:00 +00:00
// do not re-request hashes that have been requested
if ! c . RequestedHashes . PushUnique ( hash ) {
return
}
// If the initial sync is not finished yet, try to ask the fastest peer too
if ! c . Owner . SideChain ( ) . PreCalcFinished ( ) {
2022-12-04 14:15:33 +00:00
fastest := c . Owner . GetFastestClient ( )
if fastest != nil && c != fastest && ! c . Owner . SideChain ( ) . PreCalcFinished ( ) {
//send towards the fastest peer as well
2022-12-09 12:13:00 +00:00
fastest . SendMissingBlockRequest ( hash )
2022-12-04 14:15:33 +00:00
}
2022-12-09 12:13:00 +00:00
}
2022-12-04 14:15:33 +00:00
c . SendBlockRequest ( hash )
}
2022-12-12 15:58:45 +00:00
func ( c * Client ) SendUniqueBlockRequest ( hash types . Hash ) {
if hash == types . ZeroHash {
return
}
// do not re-request hashes that have been requested
if ! c . RequestedHashes . PushUnique ( hash ) {
return
}
c . SendBlockRequest ( hash )
}
2022-12-18 14:53:32 +00:00
func ( c * Client ) SendBlockRequest ( id types . Hash ) {
2022-11-06 10:58:19 +00:00
2022-11-06 16:01:36 +00:00
c . SendMessage ( & ClientMessage {
MessageId : MessageBlockRequest ,
2022-12-18 14:53:32 +00:00
Buffer : id [ : ] ,
2022-11-06 16:01:36 +00:00
} )
2022-11-03 11:32:07 +00:00
2022-12-18 14:53:32 +00:00
c . blockPendingRequests <- id
if id == types . ZeroHash {
c . chainTipBlockRequest . Store ( true )
2022-11-03 11:32:07 +00:00
}
}
func ( c * Client ) SendBlockResponse ( block * sidechain . PoolBlock ) {
if block != nil {
blockData , _ := block . MarshalBinary ( )
2022-11-06 16:01:36 +00:00
c . SendMessage ( & ClientMessage {
MessageId : MessageBlockResponse ,
2022-11-07 22:59:52 +00:00
Buffer : append ( binary . LittleEndian . AppendUint32 ( make ( [ ] byte , 0 , len ( blockData ) + 4 ) , uint32 ( len ( blockData ) ) ) , blockData ... ) ,
2022-11-06 16:01:36 +00:00
} )
2022-11-03 11:32:07 +00:00
} else {
2022-11-06 16:01:36 +00:00
c . SendMessage ( & ClientMessage {
MessageId : MessageBlockResponse ,
2022-11-07 22:59:52 +00:00
Buffer : binary . LittleEndian . AppendUint32 ( nil , 0 ) ,
2022-11-06 16:01:36 +00:00
} )
2022-11-06 10:58:19 +00:00
}
}
2022-11-03 11:32:07 +00:00
func ( c * Client ) SendPeerListRequest ( ) {
2022-12-11 12:31:43 +00:00
c . NextOutgoingPeerListRequestTimestamp . Store ( uint64 ( time . Now ( ) . Unix ( ) ) + 60 + ( unsafeRandom . Uint64 ( ) % 61 ) )
2022-11-06 16:01:36 +00:00
c . SendMessage ( & ClientMessage {
MessageId : MessagePeerListRequest ,
} )
2022-12-11 12:31:43 +00:00
c . LastPeerListRequestTimestamp . Store ( uint64 ( time . Now ( ) . UnixMicro ( ) ) )
2023-03-10 15:45:22 +00:00
//log.Printf("[P2PClient] Sending PEER_LIST_REQUEST to %s", c.AddressPort.String())
2022-11-06 16:01:36 +00:00
}
func ( c * Client ) SendPeerListResponse ( list [ ] netip . AddrPort ) {
if len ( list ) > PeerListResponseMaxPeers {
return
}
2022-11-07 22:59:52 +00:00
buf := make ( [ ] byte , 0 , 1 + len ( list ) * ( 1 + 16 + 2 ) )
2022-11-06 16:01:36 +00:00
buf = append ( buf , byte ( len ( list ) ) )
for i := range list {
2023-03-05 14:06:49 +00:00
//TODO: check ipv4 gets sent properly
2022-12-02 12:15:53 +00:00
if list [ i ] . Addr ( ) . Is6 ( ) && ! IsPeerVersionInformation ( list [ i ] ) {
2022-11-06 16:01:36 +00:00
buf = append ( buf , 1 )
} else {
buf = append ( buf , 0 )
}
ip := list [ i ] . Addr ( ) . As16 ( )
buf = append ( buf , ip [ : ] ... )
buf = binary . LittleEndian . AppendUint16 ( buf , list [ i ] . Port ( ) )
}
c . SendMessage ( & ClientMessage {
MessageId : MessagePeerListResponse ,
2022-11-07 22:59:52 +00:00
Buffer : buf ,
2022-11-06 16:01:36 +00:00
} )
2022-11-03 11:32:07 +00:00
}
2022-12-02 13:34:18 +00:00
func ( c * Client ) IsGood ( ) bool {
return c . HandshakeComplete . Load ( ) && c . ListenPort . Load ( ) > 0
}
2022-11-01 11:22:00 +00:00
func ( c * Client ) OnConnection ( ) {
2022-12-11 12:31:43 +00:00
c . LastActiveTimestamp . Store ( uint64 ( time . Now ( ) . Unix ( ) ) )
2022-11-01 11:22:00 +00:00
c . sendHandshakeChallenge ( )
2022-11-03 11:32:07 +00:00
2022-12-09 12:13:00 +00:00
var messageIdBuf [ 1 ] byte
var messageId MessageId
2022-11-03 11:32:07 +00:00
for ! c . Closed . Load ( ) {
2022-12-09 12:13:00 +00:00
if _ , err := io . ReadFull ( c , messageIdBuf [ : ] ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
}
2022-12-09 12:13:00 +00:00
messageId = MessageId ( messageIdBuf [ 0 ] )
2022-11-07 18:38:29 +00:00
if ! c . HandshakeComplete . Load ( ) && messageId != c . expectedMessage {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , fmt . Errorf ( "unexpected pre-handshake message: got %d, expected %d" , messageId , c . expectedMessage ) )
2022-11-03 11:32:07 +00:00
return
}
switch messageId {
case MessageHandshakeChallenge :
2022-11-07 18:38:29 +00:00
if c . HandshakeComplete . Load ( ) {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , errors . New ( "got HANDSHAKE_CHALLENGE but handshake is complete" ) )
2022-11-03 11:32:07 +00:00
return
}
var challenge HandshakeChallenge
var peerId uint64
if err := binary . Read ( c , binary . LittleEndian , & challenge ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
}
if err := binary . Read ( c , binary . LittleEndian , & peerId ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
}
if peerId == c . Owner . PeerId ( ) {
//tried to connect to self
c . Close ( )
return
}
2022-12-11 16:27:46 +00:00
c . PeerId . Store ( peerId )
2022-11-03 11:32:07 +00:00
if func ( ) bool {
c . Owner . clientsLock . RLock ( )
defer c . Owner . clientsLock . RUnlock ( )
for _ , client := range c . Owner . clients {
2022-12-11 16:27:46 +00:00
if client != c && client . PeerId . Load ( ) == peerId {
2022-11-03 11:32:07 +00:00
return true
}
}
return false
} ( ) {
//same peer
c . Close ( )
return
}
c . sendHandshakeSolution ( challenge )
2022-11-07 14:58:02 +00:00
c . expectedMessage = MessageHandshakeSolution
2022-11-03 11:32:07 +00:00
c . OnAfterHandshake ( )
case MessageHandshakeSolution :
2022-11-07 18:38:29 +00:00
if c . HandshakeComplete . Load ( ) {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , errors . New ( "got HANDSHAKE_SOLUTION but handshake is complete" ) )
2022-11-03 11:32:07 +00:00
return
}
var challengeHash types . Hash
var solution uint64
if err := binary . Read ( c , binary . LittleEndian , & challengeHash ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
}
if err := binary . Read ( c , binary . LittleEndian , & solution ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
}
if c . IsIncomingConnection {
2022-11-07 14:58:02 +00:00
if hash , ok := CalculateChallengeHash ( c . handshakeChallenge , c . Owner . Consensus ( ) . Id ( ) , solution ) ; ! ok {
2022-11-03 11:32:07 +00:00
//not enough PoW
2022-11-07 14:58:02 +00:00
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 ( ) ) )
2022-11-03 11:32:07 +00:00
return
} else if hash != challengeHash {
//wrong hash
2022-11-07 14:58:02 +00:00
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 ( ) ) )
2022-11-03 11:32:07 +00:00
return
}
} else {
2022-11-07 14:58:02 +00:00
if hash , _ := CalculateChallengeHash ( c . handshakeChallenge , c . Owner . Consensus ( ) . Id ( ) , solution ) ; hash != challengeHash {
2022-11-03 11:32:07 +00:00
//wrong hash
2022-11-07 14:58:02 +00:00
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 ( ) ) )
2022-11-03 11:32:07 +00:00
return
}
}
2022-11-07 18:38:29 +00:00
c . HandshakeComplete . Store ( true )
2022-11-03 11:32:07 +00:00
case MessageListenPort :
2022-12-02 13:34:18 +00:00
if c . ListenPort . Load ( ) != 0 {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , errors . New ( "got LISTEN_PORT but we already received it" ) )
2022-11-03 11:32:07 +00:00
return
}
2022-12-02 13:34:18 +00:00
var listenPort uint32
if err := binary . Read ( c , binary . LittleEndian , & listenPort ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
}
2022-12-02 13:34:18 +00:00
if listenPort == 0 || listenPort >= 65536 {
c . Ban ( DefaultBanTime , fmt . Errorf ( "listen port out of range: %d" , listenPort ) )
2022-11-03 11:32:07 +00:00
return
}
2022-12-02 13:34:18 +00:00
c . ListenPort . Store ( listenPort )
2022-11-03 11:32:07 +00:00
case MessageBlockRequest :
c . LastBlockRequest = time . Now ( )
var templateId types . Hash
if err := binary . Read ( c , binary . LittleEndian , & templateId ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
}
2022-12-09 12:13:00 +00:00
var block * sidechain . PoolBlock
//if empty, return chain tip
if templateId == types . ZeroHash {
//todo: don't return stale
block = c . Owner . SideChain ( ) . GetChainTip ( )
} else {
block = c . Owner . SideChain ( ) . GetPoolBlockByTemplateId ( templateId )
}
2022-11-03 11:32:07 +00:00
2022-12-09 12:13:00 +00:00
c . SendBlockResponse ( block )
2022-11-03 11:32:07 +00:00
case MessageBlockResponse :
2022-12-09 12:13:00 +00:00
block := & sidechain . PoolBlock {
LocalTimestamp : uint64 ( time . Now ( ) . Unix ( ) ) ,
2023-03-07 17:57:06 +00:00
NetworkType : c . Owner . Consensus ( ) . NetworkType ,
2022-12-09 12:13:00 +00:00
}
2022-11-03 11:32:07 +00:00
2022-12-18 14:53:32 +00:00
expectedBlockId , ok := c . getNextBlockRequest ( )
if ! ok {
2022-12-14 18:31:59 +00:00
c . Ban ( DefaultBanTime , errors . New ( "unexpected BLOCK_RESPONSE" ) )
return
}
2022-11-03 11:32:07 +00:00
var blockSize uint32
if err := binary . Read ( c , binary . LittleEndian , & blockSize ) ; err != nil {
//TODO warn
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
2022-11-06 10:58:19 +00:00
} else if blockSize == 0 {
//NOT found
//TODO log
2022-11-03 11:32:07 +00:00
} else {
2022-11-07 18:38:29 +00:00
if err = block . FromReader ( bufio . NewReader ( io . LimitReader ( c , int64 ( blockSize ) ) ) ) ; err != nil {
2022-11-03 11:32:07 +00:00
//TODO warn
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
2022-11-06 10:58:19 +00:00
} else {
2022-12-18 14:53:32 +00:00
isChainTipBlockRequest := false
if c . chainTipBlockRequest . Swap ( false ) {
isChainTipBlockRequest = true
2022-11-06 16:01:36 +00:00
//TODO: stale block
2022-12-09 12:13:00 +00:00
log . Printf ( "[P2PClient] Peer %s tip is at id = %s, height = %d, main height = %d" , c . AddressPort . String ( ) , types . HashFromBytes ( block . CoinbaseExtra ( sidechain . SideTemplateId ) ) , block . Side . Height , block . Main . Coinbase . GenHeight )
2022-12-18 14:53:32 +00:00
if expectedBlockId != types . ZeroHash {
c . Ban ( DefaultBanTime , fmt . Errorf ( "expected block id = %s, got %s" , expectedBlockId , types . ZeroHash . String ( ) ) )
2023-03-09 15:18:42 +00:00
return
2022-12-18 14:53:32 +00:00
}
2023-03-10 15:45:22 +00:00
peerHeight := block . Main . Coinbase . GenHeight
ourHeight := c . Owner . MainChain ( ) . GetMinerDataTip ( ) . Height
if ( peerHeight + 2 ) < ourHeight {
c . Ban ( DefaultBanTime , fmt . Errorf ( "mining on top of a stale block (mainchain peer height %d, expected >= %d)" , peerHeight , ourHeight ) )
return
}
2022-12-18 14:53:32 +00:00
2022-11-06 16:01:36 +00:00
c . SendPeerListRequest ( )
2022-11-06 10:58:19 +00:00
}
2022-12-09 12:13:00 +00:00
if missingBlocks , err := c . Owner . SideChain ( ) . AddPoolBlockExternal ( block ) ; err != nil {
//TODO warn
c . Ban ( DefaultBanTime , err )
return
} else {
2022-12-18 14:53:32 +00:00
if ! isChainTipBlockRequest && expectedBlockId != block . SideTemplateId ( c . Owner . SideChain ( ) . Consensus ( ) ) {
c . Ban ( DefaultBanTime , fmt . Errorf ( "expected block id = %s, got %s" , expectedBlockId . String ( ) , block . SideTemplateId ( c . Owner . SideChain ( ) . Consensus ( ) ) . String ( ) ) )
return
}
2022-12-09 12:13:00 +00:00
for _ , id := range missingBlocks {
c . SendMissingBlockRequest ( id )
2022-11-06 16:01:36 +00:00
}
2022-12-09 12:13:00 +00:00
}
2022-11-06 16:01:36 +00:00
}
2022-11-03 11:32:07 +00:00
}
2022-12-03 22:43:18 +00:00
case MessageBlockBroadcast , MessageBlockBroadcastCompact :
2022-12-09 12:13:00 +00:00
block := & sidechain . PoolBlock {
LocalTimestamp : uint64 ( time . Now ( ) . Unix ( ) ) ,
2023-03-07 17:57:06 +00:00
NetworkType : c . Owner . Consensus ( ) . NetworkType ,
2022-12-09 12:13:00 +00:00
}
2022-11-03 11:32:07 +00:00
var blockSize uint32
if err := binary . Read ( c , binary . LittleEndian , & blockSize ) ; err != nil {
//TODO warn
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-03 11:32:07 +00:00
return
2022-11-06 10:58:19 +00:00
} else if blockSize == 0 {
//NOT found
//TODO log
2022-12-03 22:43:18 +00:00
} else if messageId == MessageBlockBroadcastCompact {
if err = block . FromCompactReader ( bufio . NewReader ( io . LimitReader ( c , int64 ( blockSize ) ) ) ) ; err != nil {
//TODO warn
c . Ban ( DefaultBanTime , err )
return
}
} else {
if err = block . FromReader ( bufio . NewReader ( io . LimitReader ( c , int64 ( blockSize ) ) ) ) ; err != nil {
//TODO warn
c . Ban ( DefaultBanTime , err )
return
}
2022-11-03 11:32:07 +00:00
}
2022-12-03 22:43:18 +00:00
2022-12-11 12:31:43 +00:00
//Atomic max, not necessary as no external writers exist
topHeight := utils . Max ( c . BroadcastMaxHeight . Load ( ) , block . Side . Height )
for {
if oldHeight := c . BroadcastMaxHeight . Swap ( topHeight ) ; oldHeight <= topHeight {
break
} else {
topHeight = oldHeight
}
}
2022-12-04 00:46:22 +00:00
c . BroadcastedHashes . Push ( types . HashFromBytes ( block . CoinbaseExtra ( sidechain . SideTemplateId ) ) )
2022-12-03 22:43:18 +00:00
2022-11-06 16:01:36 +00:00
c . LastBroadcast = time . Now ( )
2022-12-09 12:13:00 +00:00
if missingBlocks , err := c . Owner . SideChain ( ) . PreprocessBlock ( block ) ; err != nil {
for _ , id := range missingBlocks {
c . SendMissingBlockRequest ( id )
}
//TODO: ban here, but sort blocks properly, maybe a queue to re-try?
return
} else {
2023-03-09 15:18:42 +00:00
ourMinerData := c . Owner . MainChain ( ) . GetMinerDataTip ( )
if block . Main . PreviousId != ourMinerData . PrevId {
// This peer is mining on top of a different Monero block, investigate it
peerHeight := block . Main . Coinbase . GenHeight
ourHeight := ourMinerData . Height
if peerHeight < ourHeight {
if ( ourHeight - peerHeight ) < 5 {
elapsedTime := time . Now ( ) . Sub ( ourMinerData . TimeReceived )
if ( ourHeight - peerHeight ) > 1 || elapsedTime > ( time . Second * 10 ) {
log . Printf ( "[P2PClient] Peer %s broadcasted a stale block (%d ms late, mainchain height %d, expected >= %d), ignoring it" , c . AddressPort . String ( ) , elapsedTime . Milliseconds ( ) , peerHeight , ourHeight )
}
} else {
c . Ban ( DefaultBanTime , fmt . Errorf ( "broadcasted an unreasonably stale block (mainchain height %d, expected >= %d)" , peerHeight , ourHeight ) )
return
}
} else if peerHeight > ourHeight {
if peerHeight >= ( ourHeight + 2 ) {
log . Printf ( "[P2PClient] Peer %s is ahead on mainchain (mainchain height %d, your height %d). Is monerod stuck or lagging?" , c . AddressPort . String ( ) , peerHeight , ourHeight )
}
} else {
log . Printf ( "[P2PClient] Peer %s is mining on an alternative mainchain tip (mainchain height %d, previous_id = %s)" , c . AddressPort . String ( ) , peerHeight , block . Main . PreviousId )
}
}
2022-12-09 12:13:00 +00:00
block . WantBroadcast . Store ( true )
if missingBlocks , err = c . Owner . SideChain ( ) . AddPoolBlockExternal ( block ) ; err != nil {
//TODO warn
c . Ban ( DefaultBanTime , err )
2022-11-06 10:58:19 +00:00
return
} else {
2022-12-09 12:13:00 +00:00
for _ , id := range missingBlocks {
c . SendMissingBlockRequest ( id )
2022-11-06 10:58:19 +00:00
}
}
2022-12-09 12:13:00 +00:00
}
2022-11-03 11:32:07 +00:00
case MessagePeerListRequest :
2022-12-02 13:34:18 +00:00
connectedPeerList := c . Owner . Clients ( )
entriesToSend := make ( [ ] netip . AddrPort , 0 , PeerListResponseMaxPeers )
// Send every 4th peer on average, selected at random
2022-12-06 08:59:57 +00:00
peersToSendTarget := utils . Min ( PeerListResponseMaxPeers , utils . Max ( len ( entriesToSend ) / 4 , 1 ) )
2022-12-02 13:34:18 +00:00
n := 0
for _ , peer := range connectedPeerList {
if peer . AddressPort . Addr ( ) . IsLoopback ( ) || ! peer . IsGood ( ) || peer . AddressPort . Addr ( ) . Compare ( c . AddressPort . Addr ( ) ) == 0 {
continue
}
n ++
// Use https://en.wikipedia.org/wiki/Reservoir_sampling algorithm
if len ( entriesToSend ) < peersToSendTarget {
entriesToSend = append ( entriesToSend , peer . AddressPort )
}
k := unsafeRandom . Intn ( n )
if k < peersToSendTarget {
entriesToSend [ k ] = peer . AddressPort
}
}
2022-12-02 11:33:37 +00:00
if c . LastIncomingPeerListRequestTime . IsZero ( ) {
//first, send version / protocol information
2022-12-02 13:34:18 +00:00
if len ( entriesToSend ) == 0 {
entriesToSend = append ( entriesToSend , c . Owner . versionInformation . ToAddrPort ( ) )
} else {
entriesToSend [ 0 ] = c . Owner . versionInformation . ToAddrPort ( )
}
2022-12-02 11:33:37 +00:00
}
2022-12-02 13:34:18 +00:00
c . LastIncomingPeerListRequestTime = time . Now ( )
c . SendPeerListResponse ( entriesToSend )
2022-11-03 11:32:07 +00:00
case MessagePeerListResponse :
2022-11-06 16:01:36 +00:00
if numPeers , err := c . ReadByte ( ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-06 16:01:36 +00:00
return
} else if numPeers > PeerListResponseMaxPeers {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , fmt . Errorf ( "too many peers on PEER_LIST_RESPONSE num_peers = %d" , numPeers ) )
2022-11-06 16:01:36 +00:00
return
} else {
2022-12-11 12:31:43 +00:00
c . PingDuration . Store ( uint64 ( utils . Max ( time . Now ( ) . Sub ( time . UnixMicro ( int64 ( c . LastPeerListRequestTimestamp . Load ( ) ) ) ) , 0 ) ) )
2022-11-06 16:01:36 +00:00
var rawIp [ 16 ] byte
var port uint16
for i := uint8 ( 0 ) ; i < numPeers ; i ++ {
if isV6 , err := c . ReadByte ( ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-06 16:01:36 +00:00
return
} else {
if _ , err = c . Read ( rawIp [ : ] ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-06 16:01:36 +00:00
return
} else if err = binary . Read ( c , binary . LittleEndian , & port ) ; err != nil {
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , err )
2022-11-06 16:01:36 +00:00
return
}
2022-12-02 11:33:37 +00:00
2022-11-08 10:46:35 +00:00
if isV6 == 0 {
2022-12-02 11:33:37 +00:00
if rawIp [ 12 ] == 0 || rawIp [ 12 ] >= 224 {
// Ignore 0.0.0.0/8 (special-purpose range for "this network") and 224.0.0.0/3 (IP multicast and reserved ranges)
// Check for protocol version message
if binary . LittleEndian . Uint32 ( rawIp [ 12 : ] ) == 0xFFFFFFFF && port == 0xFFFF {
c . VersionInformation . Protocol = ProtocolVersion ( binary . LittleEndian . Uint32 ( rawIp [ 0 : ] ) )
2022-12-11 12:31:43 +00:00
c . VersionInformation . SoftwareVersion = SoftwareVersion ( binary . LittleEndian . Uint32 ( rawIp [ 4 : ] ) )
c . VersionInformation . SoftwareId = SoftwareId ( binary . LittleEndian . Uint32 ( rawIp [ 8 : ] ) )
2022-12-02 11:33:37 +00:00
log . Printf ( "peer %s is %s" , c . AddressPort . String ( ) , c . VersionInformation . String ( ) )
}
continue
}
2022-11-06 16:01:36 +00:00
copy ( rawIp [ : ] , make ( [ ] byte , 10 ) )
rawIp [ 10 ] , rawIp [ 11 ] = 0xFF , 0xFF
}
2022-11-08 10:46:35 +00:00
c . Owner . AddToPeerList ( netip . AddrPortFrom ( netip . AddrFrom16 ( rawIp ) . Unmap ( ) , port ) )
2022-11-06 16:01:36 +00:00
}
}
}
2022-11-03 11:32:07 +00:00
default :
2022-11-07 14:58:02 +00:00
c . Ban ( DefaultBanTime , fmt . Errorf ( "unknown MessageId %d" , messageId ) )
2022-11-06 16:01:36 +00:00
return
2022-11-03 11:32:07 +00:00
}
2022-12-11 12:31:43 +00:00
c . LastActiveTimestamp . Store ( uint64 ( time . Now ( ) . Unix ( ) ) )
2022-11-03 11:32:07 +00:00
}
2022-11-01 11:22:00 +00:00
}
func ( c * Client ) sendHandshakeChallenge ( ) {
2022-11-07 14:58:02 +00:00
if _ , err := rand . Read ( c . handshakeChallenge [ : ] ) ; err != nil {
2022-11-01 11:22:00 +00:00
log . Printf ( "[P2PServer] Unable to generate handshake challenge for %s" , c . AddressPort . String ( ) )
c . Close ( )
return
}
2022-11-06 16:01:36 +00:00
var buf [ HandshakeChallengeSize + int ( unsafe . Sizeof ( uint64 ( 0 ) ) ) ] byte
2022-11-07 14:58:02 +00:00
copy ( buf [ : ] , c . handshakeChallenge [ : ] )
2022-11-06 16:01:36 +00:00
binary . LittleEndian . PutUint64 ( buf [ HandshakeChallengeSize : ] , c . Owner . PeerId ( ) )
2022-11-01 11:22:00 +00:00
2022-11-06 16:01:36 +00:00
c . SendMessage ( & ClientMessage {
MessageId : MessageHandshakeChallenge ,
2022-11-07 22:59:52 +00:00
Buffer : buf [ : ] ,
2022-11-06 16:01:36 +00:00
} )
2022-11-01 11:22:00 +00:00
}
func ( c * Client ) sendHandshakeSolution ( challenge HandshakeChallenge ) {
stop := & c . Closed
if c . IsIncomingConnection {
stop = & atomic . Bool { }
stop . Store ( true )
}
2022-11-03 11:32:07 +00:00
if solution , hash , ok := FindChallengeSolution ( challenge , c . Owner . Consensus ( ) . Id ( ) , stop ) ; ok {
2022-11-01 11:22:00 +00:00
2022-11-06 16:01:36 +00:00
var buf [ HandshakeChallengeSize + types . HashSize ] byte
copy ( buf [ : ] , hash [ : ] )
binary . LittleEndian . PutUint64 ( buf [ types . HashSize : ] , solution )
c . SendMessage ( & ClientMessage {
MessageId : MessageHandshakeSolution ,
2022-11-07 22:59:52 +00:00
Buffer : buf [ : ] ,
2022-11-06 16:01:36 +00:00
} )
2022-11-01 11:22:00 +00:00
}
}
2022-11-03 11:32:07 +00:00
// Read reads from underlying connection, on error it will Close
func ( c * Client ) Read ( buf [ ] byte ) ( n int , err error ) {
2022-12-09 12:13:00 +00:00
if n , err = c . Connection . Read ( buf ) ; err != nil {
2022-11-03 11:32:07 +00:00
c . Close ( )
}
return
}
2022-11-06 16:01:36 +00:00
type ClientMessage struct {
MessageId MessageId
2022-11-07 22:59:52 +00:00
Buffer [ ] byte
2022-11-06 16:01:36 +00:00
}
func ( c * Client ) SendMessage ( message * ClientMessage ) {
2022-11-07 14:58:02 +00:00
if ! c . Closed . Load ( ) {
2022-12-09 12:13:00 +00:00
//c.sendLock.Lock()
//defer c.sendLock.Unlock()
2022-12-19 11:02:42 +00:00
if err := c . Connection . SetWriteDeadline ( time . Now ( ) . Add ( time . Second * 5 ) ) ; err != nil {
c . Close ( )
} else if _ , err = c . Connection . Write ( append ( [ ] byte { byte ( message . MessageId ) } , message . Buffer ... ) ) ; err != nil {
2022-12-11 12:02:15 +00:00
c . Close ( )
}
2022-12-09 12:13:00 +00:00
//_, _ = c.Write(message.Buffer)
2022-11-07 14:58:02 +00:00
}
2022-11-06 16:01:36 +00:00
}
2022-11-03 11:32:07 +00:00
// ReadByte reads from underlying connection, on error it will Close
func ( c * Client ) ReadByte ( ) ( b byte , err error ) {
var buf [ 1 ] byte
if _ , err = c . Connection . Read ( buf [ : ] ) ; err != nil && c . Closed . Load ( ) {
c . Close ( )
}
return buf [ 0 ] , err
}
2022-11-01 11:22:00 +00:00
func ( c * Client ) Close ( ) {
if c . Closed . Swap ( true ) {
return
}
2022-12-09 12:13:00 +00:00
c . Owner . clientsLock . Lock ( )
defer c . Owner . clientsLock . Unlock ( )
if i := slices . Index ( c . Owner . clients , c ) ; i != - 1 {
2022-12-06 08:59:57 +00:00
c . Owner . clients = slices . Delete ( c . Owner . clients , i , i + 1 )
2022-11-07 14:58:02 +00:00
if c . IsIncomingConnection {
c . Owner . NumIncomingConnections . Add ( - 1 )
} else {
c . Owner . NumOutgoingConnections . Add ( - 1 )
}
2022-11-01 11:22:00 +00:00
}
2022-11-03 11:32:07 +00:00
_ = c . Connection . Close ( )
2022-11-07 14:58:02 +00:00
close ( c . closeChannel )
2022-11-01 11:22:00 +00:00
}