2022-11-01 11:22:00 +00:00
package p2pool
import (
2023-03-05 14:06:49 +00:00
"context"
"encoding/binary"
2022-11-06 20:11:03 +00:00
"errors"
2022-11-03 11:32:07 +00:00
"fmt"
2023-03-05 14:06:49 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client/zmq"
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
2023-03-10 15:45:22 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache"
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/legacy"
2023-03-05 14:06:49 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/mainchain"
2022-11-03 11:32:07 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/p2p"
2022-11-01 11:22:00 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
2023-03-09 15:18:42 +00:00
p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
2023-03-05 14:06:49 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"log"
2022-12-04 13:57:07 +00:00
"strconv"
2023-03-05 14:06:49 +00:00
"sync/atomic"
"time"
2022-11-01 11:22:00 +00:00
)
type P2Pool struct {
2022-11-03 11:32:07 +00:00
consensus * sidechain . Consensus
sidechain * sidechain . SideChain
2023-03-05 14:06:49 +00:00
mainchain * mainchain . MainChain
2023-03-10 15:45:22 +00:00
cache cache . HeapCache
2022-11-03 11:32:07 +00:00
server * p2p . Server
2023-03-05 14:06:49 +00:00
ctx context . Context
ctxCancel context . CancelFunc
rpcClient * client . Client
zmqClient * zmq . Client
started atomic . Bool
2022-11-01 11:22:00 +00:00
}
2022-11-06 20:11:03 +00:00
func ( p * P2Pool ) GetBlob ( key [ ] byte ) ( blob [ ] byte , err error ) {
return nil , errors . New ( "not found" )
}
func ( p * P2Pool ) SetBlob ( key , blob [ ] byte ) ( err error ) {
return nil
}
func ( p * P2Pool ) RemoveBlob ( key [ ] byte ) ( err error ) {
return nil
}
2023-03-05 14:06:49 +00:00
func ( p * P2Pool ) Close ( ) {
p . ctxCancel ( )
_ = p . zmqClient . Close ( )
}
func NewP2Pool ( consensus * sidechain . Consensus , settings map [ string ] string ) ( * P2Pool , error ) {
2022-11-03 11:32:07 +00:00
pool := & P2Pool {
consensus : consensus ,
}
var err error
2023-03-05 14:06:49 +00:00
pool . ctx , pool . ctxCancel = context . WithCancel ( context . Background ( ) )
2022-11-03 11:32:07 +00:00
listenAddress := fmt . Sprintf ( "0.0.0.0:%d" , pool . Consensus ( ) . DefaultPort ( ) )
2023-03-05 14:06:49 +00:00
if pool . rpcClient , err = client . NewClient ( settings [ "rpc-url" ] ) ; err != nil {
return nil , err
}
pool . zmqClient = zmq . NewClient ( settings [ "zmq-url" ] , zmq . TopicFullChainMain , zmq . TopicFullMinerData )
2022-11-03 11:32:07 +00:00
pool . sidechain = sidechain . NewSideChain ( pool )
2023-03-05 14:06:49 +00:00
pool . mainchain = mainchain . NewMainChain ( pool . sidechain , pool )
if pool . mainchain == nil {
return nil , errors . New ( "could not create MainChain" )
}
2023-03-10 15:45:22 +00:00
if cachePath , ok := settings [ "cache" ] ; ok {
if pool . cache , err = legacy . NewCache ( cachePath ) ; err != nil {
return nil , fmt . Errorf ( "could not create cache: %w" , err )
}
}
2022-11-03 11:32:07 +00:00
if addr , ok := settings [ "listen" ] ; ok {
listenAddress = addr
}
2022-12-04 13:57:07 +00:00
maxOutgoingPeers := uint64 ( 10 )
if outgoingPeers , ok := settings [ "out-peers" ] ; ok {
maxOutgoingPeers , _ = strconv . ParseUint ( outgoingPeers , 10 , 0 )
}
2022-12-11 13:46:37 +00:00
maxIncomingPeers := uint64 ( 450 )
if incomingPeers , ok := settings [ "in-peers" ] ; ok {
maxIncomingPeers , _ = strconv . ParseUint ( incomingPeers , 10 , 0 )
}
externalListenPort := uint64 ( 0 )
if externalPort , ok := settings [ "external-port" ] ; ok {
externalListenPort , _ = strconv . ParseUint ( externalPort , 10 , 0 )
}
2023-03-09 15:18:42 +00:00
if pool . server , err = p2p . NewServer ( pool , listenAddress , uint16 ( externalListenPort ) , uint32 ( maxOutgoingPeers ) , uint32 ( maxIncomingPeers ) , pool . ctx ) ; err != nil {
2023-03-05 14:06:49 +00:00
return nil , err
2022-11-03 11:32:07 +00:00
}
2022-11-01 11:22:00 +00:00
2023-03-05 14:06:49 +00:00
return pool , nil
}
func ( p * P2Pool ) GetChainMainByHash ( hash types . Hash ) * sidechain . ChainMain {
return p . mainchain . GetChainMainByHash ( hash )
}
func ( p * P2Pool ) GetChainMainByHeight ( height uint64 ) * sidechain . ChainMain {
return p . mainchain . GetChainMainByHeight ( height )
}
func ( p * P2Pool ) GetChainMainTip ( ) * sidechain . ChainMain {
return p . mainchain . GetChainMainTip ( )
}
2023-03-09 15:18:42 +00:00
func ( p * P2Pool ) GetMinerDataTip ( ) * p2pooltypes . MinerData {
return p . mainchain . GetMinerDataTip ( )
}
2023-03-05 14:06:49 +00:00
// GetMinimalBlockHeaderByHeight Only Id / Height / Timestamp are assured
func ( p * P2Pool ) GetMinimalBlockHeaderByHeight ( height uint64 ) * block . Header {
2023-03-09 15:18:42 +00:00
lowerThanTip := height <= p . mainchain . GetChainMainTip ( ) . Height
2023-03-05 14:06:49 +00:00
if chainMain := p . mainchain . GetChainMainByHeight ( height ) ; chainMain != nil && chainMain . Id != types . ZeroHash {
return & block . Header {
Timestamp : chainMain . Timestamp ,
Height : chainMain . Height ,
Reward : chainMain . Reward ,
Difficulty : chainMain . Difficulty ,
Id : chainMain . Id ,
}
2023-03-09 15:18:42 +00:00
} else if lowerThanTip {
2023-03-05 14:06:49 +00:00
if header , err := p . ClientRPC ( ) . GetBlockHeaderByHeight ( height , p . ctx ) ; err != nil {
return nil
} else {
prevHash , _ := types . HashFromString ( header . BlockHeader . PrevHash )
h , _ := types . HashFromString ( header . BlockHeader . Hash )
blockHeader := & block . Header {
MajorVersion : uint8 ( header . BlockHeader . MajorVersion ) ,
MinorVersion : uint8 ( header . BlockHeader . MinorVersion ) ,
Timestamp : uint64 ( header . BlockHeader . Timestamp ) ,
PreviousId : prevHash ,
Height : header . BlockHeader . Height ,
Nonce : uint32 ( header . BlockHeader . Nonce ) ,
Reward : header . BlockHeader . Reward ,
Id : h ,
Difficulty : types . DifficultyFrom64 ( header . BlockHeader . Difficulty ) ,
}
//cache it. next block found will clean it up
p . mainchain . HandleMainHeader ( blockHeader )
return blockHeader
}
}
2023-03-09 15:18:42 +00:00
return nil
2023-03-05 14:06:49 +00:00
}
func ( p * P2Pool ) GetDifficultyByHeight ( height uint64 ) types . Difficulty {
2023-03-09 15:18:42 +00:00
lowerThanTip := height <= p . mainchain . GetChainMainTip ( ) . Height
2023-03-05 14:06:49 +00:00
if chainMain := p . mainchain . GetChainMainByHeight ( height ) ; chainMain != nil && chainMain . Difficulty != types . ZeroDifficulty {
return chainMain . Difficulty
2023-03-09 15:18:42 +00:00
} else if lowerThanTip {
2023-03-05 14:06:49 +00:00
//TODO cache
if header , err := p . ClientRPC ( ) . GetBlockHeaderByHeight ( height , p . ctx ) ; err != nil {
return types . ZeroDifficulty
} else {
prevHash , _ := types . HashFromString ( header . BlockHeader . PrevHash )
h , _ := types . HashFromString ( header . BlockHeader . Hash )
blockHeader := & block . Header {
MajorVersion : uint8 ( header . BlockHeader . MajorVersion ) ,
MinorVersion : uint8 ( header . BlockHeader . MinorVersion ) ,
Timestamp : uint64 ( header . BlockHeader . Timestamp ) ,
PreviousId : prevHash ,
Height : header . BlockHeader . Height ,
Nonce : uint32 ( header . BlockHeader . Nonce ) ,
Reward : header . BlockHeader . Reward ,
Id : h ,
Difficulty : types . DifficultyFrom64 ( header . BlockHeader . Difficulty ) ,
}
//cache it. next block found will clean it up
p . mainchain . HandleMainHeader ( blockHeader )
return blockHeader . Difficulty
}
}
2023-03-09 15:18:42 +00:00
return types . ZeroDifficulty
2023-03-05 14:06:49 +00:00
}
// GetMinimalBlockHeaderByHash Only Id / Height / Timestamp are assured
func ( p * P2Pool ) GetMinimalBlockHeaderByHash ( hash types . Hash ) * block . Header {
if chainMain := p . mainchain . GetChainMainByHash ( hash ) ; chainMain != nil && chainMain . Id != types . ZeroHash {
return & block . Header {
Timestamp : chainMain . Timestamp ,
Height : chainMain . Height ,
Reward : chainMain . Reward ,
Difficulty : chainMain . Difficulty ,
Id : chainMain . Id ,
}
} else {
if header , err := p . ClientRPC ( ) . GetBlockHeaderByHash ( hash , p . ctx ) ; err != nil {
return nil
} else {
prevHash , _ := types . HashFromString ( header . BlockHeader . PrevHash )
h , _ := types . HashFromString ( header . BlockHeader . Hash )
blockHeader := & block . Header {
MajorVersion : uint8 ( header . BlockHeader . MajorVersion ) ,
MinorVersion : uint8 ( header . BlockHeader . MinorVersion ) ,
Timestamp : uint64 ( header . BlockHeader . Timestamp ) ,
PreviousId : prevHash ,
Height : header . BlockHeader . Height ,
Nonce : uint32 ( header . BlockHeader . Nonce ) ,
Reward : header . BlockHeader . Reward ,
Id : h ,
Difficulty : types . DifficultyFrom64 ( header . BlockHeader . Difficulty ) ,
}
//cache it. next block found will clean it up
p . mainchain . HandleMainHeader ( blockHeader )
return blockHeader
}
}
}
func ( p * P2Pool ) ClientRPC ( ) * client . Client {
return p . rpcClient
}
func ( p * P2Pool ) ClientZMQ ( ) * zmq . Client {
return p . zmqClient
}
func ( p * P2Pool ) Context ( ) context . Context {
return p . ctx
2022-11-01 11:22:00 +00:00
}
2022-11-03 11:32:07 +00:00
2023-03-09 15:18:42 +00:00
func ( p * P2Pool ) SideChain ( ) * sidechain . SideChain {
return p . sidechain
}
func ( p * P2Pool ) MainChain ( ) * mainchain . MainChain {
return p . mainchain
}
2022-11-03 11:32:07 +00:00
func ( p * P2Pool ) Server ( ) * p2p . Server {
return p . server
}
2023-03-05 14:06:49 +00:00
func ( p * P2Pool ) Run ( ) ( err error ) {
if err = p . getInfo ( ) ; err != nil {
return err
}
if err = p . getVersion ( ) ; err != nil {
return err
}
if err = p . getInfo ( ) ; err != nil {
return err
}
if err = p . getMinerData ( ) ; err != nil {
return err
}
2023-03-09 15:18:42 +00:00
go func ( ) {
if p . mainchain . Listen ( ) != nil {
p . Close ( )
}
} ( )
2023-03-05 14:06:49 +00:00
2023-03-10 15:45:22 +00:00
//TODO: move peer list loading here
if p . cache != nil {
p . cache . LoadAll ( p . Server ( ) )
}
2023-03-05 14:06:49 +00:00
p . started . Store ( true )
if err = p . Server ( ) . Listen ( ) ; err != nil {
return err
}
return nil
}
func ( p * P2Pool ) getInfo ( ) error {
if info , err := p . ClientRPC ( ) . GetInfo ( ) ; err != nil {
return err
} else {
if info . BusySyncing {
log . Printf ( "[P2Pool] monerod is busy syncing, trying again in 5 seconds" )
time . Sleep ( time . Second * 5 )
return p . getInfo ( )
} else if ! info . Synchronized {
log . Printf ( "[P2Pool] monerod is not synchronized, trying again in 5 seconds" )
time . Sleep ( time . Second * 5 )
return p . getInfo ( )
}
networkType := sidechain . NetworkInvalid
if info . Mainnet {
networkType = sidechain . NetworkMainnet
} else if info . Testnet {
networkType = sidechain . NetworkTestnet
} else if info . Stagenet {
networkType = sidechain . NetworkStagenet
}
if p . sidechain . Consensus ( ) . NetworkType != networkType {
return fmt . Errorf ( "monerod is on %d, but you're mining to a %d sidechain" , networkType , p . sidechain . Consensus ( ) . NetworkType )
}
}
return nil
}
const RequiredMajor = 3
const RequiredMinor = 10
const RequiredMoneroVersion = ( RequiredMajor << 16 ) | RequiredMinor
func ( p * P2Pool ) getVersion ( ) error {
if version , err := p . ClientRPC ( ) . GetVersion ( ) ; err != nil {
return err
} else {
if version . Version < RequiredMoneroVersion {
return fmt . Errorf ( "monerod RPC v%d.%d is incompatible, update to RPC >= v%d.%d (Monero v0.18.0.0 or newer)" , version . Version >> 16 , version . Version & 0xffff , RequiredMajor , RequiredMinor )
}
}
return nil
}
func ( p * P2Pool ) getMinerData ( ) error {
if minerData , err := p . ClientRPC ( ) . GetMinerData ( ) ; err != nil {
return err
} else {
prevId , _ := types . HashFromString ( minerData . PrevId )
seedHash , _ := types . HashFromString ( minerData . SeedHash )
diff , _ := types . DifficultyFromString ( minerData . Difficulty )
2023-03-09 15:18:42 +00:00
p . mainchain . HandleMinerData ( & p2pooltypes . MinerData {
2023-03-05 14:06:49 +00:00
MajorVersion : minerData . MajorVersion ,
Height : minerData . Height ,
PrevId : prevId ,
SeedHash : seedHash ,
Difficulty : diff ,
MedianWeight : minerData . MedianWeight ,
AlreadyGeneratedCoins : minerData . AlreadyGeneratedCoins ,
MedianTimestamp : minerData . MedianTimestamp ,
TimeReceived : time . Now ( ) ,
} )
return p . mainchain . DownloadBlockHeaders ( minerData . Height )
}
}
2022-11-03 11:32:07 +00:00
func ( p * P2Pool ) Consensus ( ) * sidechain . Consensus {
return p . consensus
}
2023-03-05 14:06:49 +00:00
func ( p * P2Pool ) UpdateBlockFound ( data * sidechain . ChainMain , block * sidechain . PoolBlock ) {
log . Printf ( "[P2Pool] BLOCK FOUND: main chain block at height %d, id %s was mined by this p2pool" , data . Height , data . Id )
//TODO
}
func ( p * P2Pool ) SubmitBlock ( b * block . Block ) {
//TODO: do not submit multiple times
go func ( ) {
if blob , err := b . MarshalBinary ( ) ; err == nil {
var templateId types . Hash
var extraNonce uint32
if t := b . Coinbase . Extra . GetTag ( transaction . TxExtraTagMergeMining ) ; t != nil {
templateId = types . HashFromBytes ( t . Data )
}
if t := b . Coinbase . Extra . GetTag ( transaction . TxExtraTagNonce ) ; t != nil {
extraNonce = binary . LittleEndian . Uint32 ( t . Data )
}
log . Printf ( "[P2Pool] submit_block: height = %d, template id = %s, nonce = %d, extra_nonce = %d, blob = %d bytes" , b . Coinbase . GenHeight , templateId . String ( ) , b . Nonce , extraNonce , len ( blob ) )
if result , err := p . ClientRPC ( ) . SubmitBlock ( blob ) ; err != nil {
log . Printf ( "[P2Pool] submit_block: daemon returned error: %s, height = %d, template id = %s, nonce = %d, extra_nonce = %d" , err , b . Coinbase . GenHeight , templateId . String ( ) , b . Nonce , extraNonce )
} else {
if result . Status == "OK" {
log . Printf ( "[P2Pool] submit_block: BLOCK ACCEPTED at height = %d, template id = %s" , b . Coinbase . GenHeight , templateId . String ( ) )
} else {
log . Printf ( "[P2Pool] submit_block: daemon sent unrecognizable reply: %s, height = %d, template id = %s, nonce = %d, extra_nonce = %d" , result . Status , b . Coinbase . GenHeight , templateId . String ( ) , b . Nonce , extraNonce )
}
}
}
} ( )
}
2023-03-10 15:45:22 +00:00
func ( p * P2Pool ) Store ( block * sidechain . PoolBlock ) {
if p . cache != nil {
p . cache . Store ( block )
}
}
func ( p * P2Pool ) ClearCachedBlocks ( ) {
p . server . ClearCachedBlocks ( )
}
2023-03-05 14:06:49 +00:00
func ( p * P2Pool ) Started ( ) bool {
return p . started . Load ( )
}
2022-11-03 11:32:07 +00:00
func ( p * P2Pool ) Broadcast ( block * sidechain . PoolBlock ) {
2023-03-09 15:18:42 +00:00
minerData := p . GetMinerDataTip ( )
if ( block . Main . Coinbase . GenHeight ) + 2 < minerData . Height {
log . Printf ( "[P2Pool] Trying to broadcast a stale block %s (mainchain height %d, current height is %d)" , block . SideTemplateId ( p . consensus ) , block . Main . Coinbase . GenHeight , minerData . Height )
return
}
if block . Main . Coinbase . GenHeight > ( minerData . Height + 2 ) {
log . Printf ( "[P2Pool] Trying to broadcast a block %s ahead on mainchain (mainchain height %d, current height is %d)" , block . SideTemplateId ( p . consensus ) , block . Main . Coinbase . GenHeight , minerData . Height )
return
}
2022-11-03 11:32:07 +00:00
p . server . Broadcast ( block )
}