Initial commit

This commit is contained in:
DataHoarder 2024-04-03 20:17:23 +02:00
commit 98b25e0428
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
8 changed files with 1959 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.idea
p2pool.cache
p2pool_peers.txt
default.pgo.tmp

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2024 WeebDataHoarder, go-p2pool Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

892
api.go Normal file
View file

@ -0,0 +1,892 @@
package main
import (
"bytes"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/randomx"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/p2p"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
p2pooltypes "git.gammaspectra.live/P2Pool/consensus/v3/p2pool/types"
"git.gammaspectra.live/P2Pool/consensus/v3/types"
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
cmdutils "git.gammaspectra.live/P2Pool/observer-cmd-utils/httputils"
"github.com/gorilla/mux"
"io"
"math"
"net/http"
"net/netip"
"strconv"
"sync"
"time"
)
func getServerMux(instance *P2Pool) *mux.Router {
serveMux := mux.NewRouter()
archiveCache := instance.AddressableCache()
// ================================= Peering section =================================
serveMux.HandleFunc("/server/peers", func(writer http.ResponseWriter, request *http.Request) {
clients := instance.Server().Clients()
result := make([]p2pooltypes.P2PoolServerPeerResult, 0, len(clients))
for _, c := range clients {
if !c.IsGood() {
continue
}
result = append(result, p2pooltypes.P2PoolServerPeerResult{
Incoming: c.IsIncomingConnection,
Address: c.AddressPort.Addr().String(),
SoftwareId: c.VersionInformation.SoftwareId.String(),
SoftwareVersion: c.VersionInformation.SoftwareVersion.String(),
ProtocolVersion: c.VersionInformation.Protocol.String(),
ConnectionTime: uint64(c.ConnectionTime.Unix()),
ListenPort: c.ListenPort.Load(),
Latency: uint64(time.Duration(c.PingDuration.Load()).Milliseconds()),
PeerId: c.PeerId.Load(),
})
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
})
serveMux.HandleFunc("/server/connection_check/{addrPort:.+}", func(writer http.ResponseWriter, request *http.Request) {
addrPort, err := netip.ParseAddrPort(mux.Vars(request)["addrPort"])
if err != nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusBadRequest)
buf, _ := utils.MarshalJSON(struct {
Error string `json:"error"`
}{
Error: err.Error(),
})
_, _ = writer.Write(buf)
return
}
var client *p2p.Client
var alreadyConnected bool
isBanned, banEntry := instance.Server().IsBanned(addrPort.Addr())
for _, c := range instance.Server().Clients() {
if c.AddressPort.Addr().Compare(addrPort.Addr()) == 0 && uint16(c.ListenPort.Load()) == addrPort.Port() {
client = c
alreadyConnected = true
break
}
}
if client == nil {
if client, err = instance.Server().DirectConnect(addrPort); err != nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusBadRequest)
buf, _ := utils.MarshalJSON(struct {
Error string `json:"error"`
}{
Error: err.Error(),
})
_, _ = writer.Write(buf)
return
}
}
if client == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusBadRequest)
buf, _ := utils.MarshalJSON(struct {
Error string `json:"error"`
}{
Error: "could not find client",
})
_, _ = writer.Write(buf)
return
}
for i := 0; i < 6; i++ {
if client.Closed.Load() || (client.IsGood() && client.PingDuration.Load() > 0 && client.LastKnownTip.Load() != nil) {
break
}
time.Sleep(time.Second * 1)
}
banError := ""
errorStr := ""
if err := client.BanError(); err != nil {
errorStr = err.Error()
}
if banEntry != nil && banEntry.Error != nil {
banError = banEntry.Error.Error()
}
info := p2pooltypes.P2PoolConnectionCheckInformation[*sidechain.PoolBlock]{
Address: client.AddressPort.Addr().Unmap().String(),
Port: client.AddressPort.Port(),
ListenPort: uint16(client.ListenPort.Load()),
PeerId: client.PeerId.Load(),
SoftwareId: client.VersionInformation.SoftwareId.String(),
SoftwareVersion: client.VersionInformation.SoftwareVersion.String(),
ProtocolVersion: client.VersionInformation.Protocol.String(),
ConnectionTime: uint64(client.ConnectionTime.Unix()),
Latency: uint64(time.Duration(client.PingDuration.Load()).Milliseconds()),
Incoming: client.IsIncomingConnection,
BroadcastTime: client.LastBroadcastTimestamp.Load(),
BroadcastHeight: client.BroadcastMaxHeight.Load(),
Tip: client.LastKnownTip.Load(),
Closed: client.Closed.Load(),
AlreadyConnected: alreadyConnected,
HandshakeComplete: client.HandshakeComplete.Load(),
LastActive: client.LastActiveTimestamp.Load(),
Banned: isBanned,
Error: errorStr,
BanError: banError,
}
if isBanned {
client.Close()
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, info)
})
serveMux.HandleFunc("/server/peerlist", func(writer http.ResponseWriter, request *http.Request) {
peers := instance.Server().PeerList()
var result []byte
for _, c := range peers {
result = append(result, []byte(c.AddressPort.String()+"\n")...)
}
writer.Header().Set("Content-Type", "text/plain")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write(result)
})
serveMux.HandleFunc("/server/status", func(writer http.ResponseWriter, request *http.Request) {
result := p2pooltypes.P2PoolServerStatusResult{
PeerId: instance.Server().PeerId(),
SoftwareId: instance.Server().VersionInformation().SoftwareId.String(),
SoftwareVersion: instance.Server().VersionInformation().SoftwareVersion.String(),
ProtocolVersion: instance.Server().VersionInformation().Protocol.String(),
ListenPort: instance.Server().ListenPort(),
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
})
// ================================= MainChain section =================================
serveMux.HandleFunc("/mainchain/header_by_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
result := instance.GetMinimalBlockHeaderByHeight(height)
if result == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
}
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/mainchain/difficulty_by_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
result := instance.GetDifficultyByHeight(height)
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/mainchain/header_by_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
if id, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
result := instance.GetMinimalBlockHeaderByHash(id)
if result == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
}
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/mainchain/miner_data", func(writer http.ResponseWriter, request *http.Request) {
result := instance.GetMinerDataTip()
if result == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
}
})
serveMux.HandleFunc("/mainchain/tip", func(writer http.ResponseWriter, request *http.Request) {
minerData := instance.GetMinerDataTip()
if minerData == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
return
}
result := instance.GetMinimalBlockHeaderByHeight(minerData.Height - 1)
if result == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
}
})
// ================================= SideChain section =================================
serveMux.HandleFunc("/sidechain/consensus", func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, instance.Consensus())
})
serveMux.HandleFunc("/sidechain/blocks_by_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
result := make([]p2pooltypes.P2PoolBinaryBlockResult, 0, 1)
for _, b := range instance.SideChain().GetPoolBlocksByHeight(height) {
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("[]"))
}
})
serveMux.HandleFunc("/sidechain/block_by_template_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
if templateId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
var result p2pooltypes.P2PoolBinaryBlockResult
if b := instance.SideChain().GetPoolBlockByTemplateId(templateId); b != nil {
result.Version = int(b.ShareVersion())
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Error = err.Error()
} else {
result.Blob = blob
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("[]"))
}
})
serveMux.HandleFunc("/sidechain/state/short", func(writer http.ResponseWriter, request *http.Request) {
tip := instance.SideChain().GetChainTip()
if tip != nil {
tipId := instance.SideChain().GetChainTip().SideTemplateId(instance.Consensus())
chain, uncles := instance.SideChain().GetPoolBlocksFromTip(tipId)
result := p2pooltypes.P2PoolSideChainStateResult{
TipHeight: tip.Side.Height,
TipId: tipId,
Chain: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(chain)),
Uncles: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(uncles)),
}
for _, b := range chain {
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
for _, b := range uncles {
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/sidechain/state/tip", func(writer http.ResponseWriter, request *http.Request) {
tip := instance.SideChain().GetChainTip()
if tip != nil {
tipId := instance.SideChain().GetChainTip().SideTemplateId(instance.Consensus())
chain, uncles := instance.SideChain().GetPoolBlocksFromTip(tipId)
result := p2pooltypes.P2PoolSideChainStateResult{
TipHeight: tip.Side.Height,
TipId: tipId,
Chain: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(chain)),
Uncles: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(uncles)),
}
for _, b := range chain {
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
for _, b := range uncles {
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/sidechain/state/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
if templateId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
tip := instance.SideChain().GetPoolBlockByTemplateId(templateId)
if tip != nil {
tipId := tip.SideTemplateId(instance.Consensus())
chain, uncles := instance.SideChain().GetPoolBlocksFromTip(tipId)
result := p2pooltypes.P2PoolSideChainStateResult{
TipHeight: tip.Side.Height,
TipId: tipId,
Chain: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(chain)),
Uncles: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(uncles)),
}
for _, b := range chain {
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
for _, b := range uncles {
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("{}"))
}
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/sidechain/tip", func(writer http.ResponseWriter, request *http.Request) {
var result p2pooltypes.P2PoolBinaryBlockResult
if b := instance.SideChain().GetChainTip(); b != nil {
result.Version = int(b.ShareVersion())
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Error = err.Error()
} else {
result.Blob = blob
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
})
serveMux.HandleFunc("/sidechain/status", func(writer http.ResponseWriter, request *http.Request) {
result := p2pooltypes.P2PoolSideChainStatusResult{
Synchronized: instance.SideChain().PreCalcFinished(),
Blocks: instance.SideChain().GetPoolBlockCount(),
}
tip := instance.SideChain().GetHighestKnownTip()
if tip != nil {
result.Height = tip.Side.Height
result.Id = tip.SideTemplateId(instance.Consensus())
result.Difficulty = tip.Side.Difficulty
result.CumulativeDifficulty = tip.Side.CumulativeDifficulty
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
})
preAllocatedSharesPool := sidechain.NewPreAllocatedSharesPool(instance.Consensus().ChainWindowSize * 2)
// ================================= Archive section =================================
if archiveCache != nil {
serveMux.HandleFunc("/archive/window_from_template_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
if templateId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
tpls := archiveCache.LoadByTemplateId(templateId)
if len(tpls) > 0 {
tip := tpls[0]
mainDifficulty := instance.GetDifficultyByHeight(randomx.SeedHeight(tip.Main.Coinbase.GenHeight))
expectedBlocks := int(mainDifficulty.Mul64(2).Div(tip.Side.Difficulty).Lo) + 20
if expectedBlocks < 100 {
expectedBlocks = 100
}
if expectedBlocks > int(instance.Consensus().ChainWindowSize) {
expectedBlocks = int(instance.Consensus().ChainWindowSize)
}
if tip.ShareVersion() == sidechain.ShareVersion_V1 {
expectedBlocks = int(instance.Consensus().ChainWindowSize)
}
var windowLock sync.RWMutex
window := make(sidechain.UniquePoolBlockSlice, 0, expectedBlocks)
window = append(window, tip)
getByTemplateId := func(id types.Hash) *sidechain.PoolBlock {
if b := func() *sidechain.PoolBlock {
windowLock.RLock()
defer windowLock.RUnlock()
if b := window.Get(id); b != nil {
return b
}
return nil
}(); b != nil {
return b
}
windowLock.Lock()
defer windowLock.Unlock()
if bs := archiveCache.LoadByTemplateId(id); len(bs) > 0 {
window = append(window, bs[0])
return bs[0]
}
return nil
}
preAllocatedShares := preAllocatedSharesPool.Get()
defer preAllocatedSharesPool.Put(preAllocatedShares)
if _, err = tip.PreProcessBlock(instance.Consensus(), instance.SideChain().DerivationCache(), preAllocatedShares, instance.GetDifficultyByHeight, getByTemplateId); err == nil {
result := p2pooltypes.P2PoolSideChainStateResult{
TipHeight: tip.Side.Height,
TipId: tip.SideTemplateId(instance.Consensus()),
Chain: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, expectedBlocks),
Uncles: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, expectedBlocks/5),
}
var topError error
topError = sidechain.IterateBlocksInPPLNSWindow(tip, instance.Consensus(), instance.GetDifficultyByHeight, getByTemplateId, nil, func(e sidechain.PoolBlockWindowSlot) {
if _, err = e.Block.PreProcessBlock(instance.Consensus(), instance.SideChain().DerivationCache(), preAllocatedShares, instance.GetDifficultyByHeight, getByTemplateId); err != nil {
topError = err
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(e.Block.ShareVersion()),
Error: err.Error(),
})
} else if blob, err := e.Block.AppendBinaryFlags(make([]byte, 0, e.Block.BufferLength()), false, false); err != nil {
topError = err
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(e.Block.ShareVersion()),
Error: err.Error(),
})
} else {
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(e.Block.ShareVersion()),
Blob: blob,
})
}
for _, u := range e.Uncles {
if _, err = u.PreProcessBlock(instance.Consensus(), instance.SideChain().DerivationCache(), preAllocatedShares, instance.GetDifficultyByHeight, getByTemplateId); err != nil {
topError = err
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(u.ShareVersion()),
Error: err.Error(),
})
} else if blob, err := u.AppendBinaryFlags(make([]byte, 0, u.BufferLength()), false, false); err != nil {
topError = err
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(u.ShareVersion()),
Error: err.Error(),
})
} else {
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(u.ShareVersion()),
Blob: blob,
})
}
}
})
if topError == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
return
}
}
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
_, _ = writer.Write([]byte("{}"))
})
serveMux.HandleFunc("/archive/store_alternate", func(writer http.ResponseWriter, request *http.Request) {
if buf, err := io.ReadAll(request.Body); err != nil {
return
} else {
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(instance.Consensus(), instance.SideChain().DerivationCache(), buf); err != nil {
return
}
if b.NeedsPreProcess() {
return
}
templateId := b.SideTemplateId(instance.Consensus())
if bytes.Compare(b.CoinbaseExtra(sidechain.SideTemplateId), templateId[:]) != 0 {
return
}
if archiveCache.LoadByMainId(b.MainId()) != nil {
return
}
existingBlock := archiveCache.LoadByTemplateId(templateId)
if len(existingBlock) == 0 {
return
}
tempData, _ := existingBlock[0].MarshalBinary()
newBlock := &sidechain.PoolBlock{}
if err = newBlock.UnmarshalBinary(instance.Consensus(), instance.SideChain().DerivationCache(), tempData); err != nil {
return
}
//set extra nonce and nonce
newBlock.Main.Coinbase.Extra[1] = b.Main.Coinbase.Extra[1]
newBlock.Main.Nonce = b.Main.Nonce
newBlock.Depth.Store(math.MaxUint64)
if !newBlock.IsProofHigherThanDifficulty(instance.Consensus().GetHasher(), func(height uint64) (hash types.Hash) {
seedHeight := randomx.SeedHeight(height)
if h := instance.GetMinimalBlockHeaderByHeight(seedHeight); h != nil {
return h.Id
} else {
return types.ZeroHash
}
}) {
return
}
//finally store alternate
archiveCache.Store(newBlock)
}
})
serveMux.HandleFunc("/archive/blocks_by_template_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
if templateId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
result := make([]p2pooltypes.P2PoolBinaryBlockResult, 0, 1)
for _, b := range archiveCache.LoadByTemplateId(templateId) {
if err := archiveCache.ProcessBlock(b); err != nil {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("[]"))
}
})
serveMux.HandleFunc("/archive/blocks_by_side_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
result := make([]p2pooltypes.P2PoolBinaryBlockResult, 0, 1)
for _, b := range archiveCache.LoadBySideChainHeight(height) {
if err := archiveCache.ProcessBlock(b); err != nil {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("[]"))
}
})
serveMux.HandleFunc("/archive/light_blocks_by_main_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
if blocks := archiveCache.LoadByMainChainHeight(height); len(blocks) > 0 {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, blocks)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
if blocks == nil {
blocks = make(sidechain.UniquePoolBlockSlice, 0)
}
_ = cmdutils.EncodeJson(request, writer, blocks)
}
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/archive/light_blocks_by_side_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
if blocks := archiveCache.LoadBySideChainHeight(height); len(blocks) > 0 {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, blocks)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
if blocks == nil {
blocks = make(sidechain.UniquePoolBlockSlice, 0)
}
_ = cmdutils.EncodeJson(request, writer, blocks)
}
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/archive/light_blocks_by_template_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
if mainId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
if blocks := archiveCache.LoadByTemplateId(mainId); len(blocks) > 0 {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, blocks)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
if blocks == nil {
blocks = make(sidechain.UniquePoolBlockSlice, 0)
}
_ = cmdutils.EncodeJson(request, writer, blocks)
}
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/archive/light_block_by_main_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
params := request.URL.Query()
var templateIdHint types.Hash
if params.Has("templateIdHint") {
if h, err := types.HashFromString(params.Get("templateIdHint")); err == nil {
templateIdHint = h
}
}
if mainId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
var b *sidechain.PoolBlock
if templateIdHint != types.ZeroHash {
// Fast lookup on sidechain
if b = instance.SideChain().GetPoolBlockByTemplateId(templateIdHint); b == nil || b.MainId() != mainId {
b = nil
}
}
// Fallback
if b == nil {
b = archiveCache.LoadByMainId(mainId)
}
if b != nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, b)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
_, _ = writer.Write([]byte("{}"))
}
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte("{}"))
}
})
serveMux.HandleFunc("/archive/block_by_main_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
params := request.URL.Query()
var templateIdHint types.Hash
if params.Has("templateIdHint") {
if h, err := types.HashFromString(params.Get("templateIdHint")); err == nil {
templateIdHint = h
}
}
if mainId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
var result p2pooltypes.P2PoolBinaryBlockResult
var b *sidechain.PoolBlock
if templateIdHint != types.ZeroHash {
// Fast lookup on sidechain
if b = instance.SideChain().GetPoolBlockByTemplateId(templateIdHint); b == nil || b.MainId() != mainId {
b = nil
}
}
// Fallback
if b == nil {
b = archiveCache.LoadByMainId(mainId)
}
if b != nil {
result.Version = int(b.ShareVersion())
if err := archiveCache.ProcessBlock(b); err != nil {
result.Error = err.Error()
} else {
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result.Error = err.Error()
} else {
result.Blob = blob
}
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte(""))
}
})
serveMux.HandleFunc("/archive/blocks_by_main_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
result := make([]p2pooltypes.P2PoolBinaryBlockResult, 0, 1)
for _, b := range archiveCache.LoadByMainChainHeight(height) {
if err := archiveCache.ProcessBlock(b); err != nil {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Error: err.Error(),
})
} else {
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
Version: int(b.ShareVersion()),
Blob: blob,
})
}
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = cmdutils.EncodeJson(request, writer, result)
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("[]"))
}
})
}
return serveMux
}

BIN
default.pgo Normal file

Binary file not shown.

37
go.mod Normal file
View file

@ -0,0 +1,37 @@
module git.gammaspectra.live/P2Pool/go-p2pool
go 1.22
require (
git.gammaspectra.live/P2Pool/consensus/v3 v3.0.1
git.gammaspectra.live/P2Pool/observer-cache-archive v0.0.0-20240403175902-82f1b2cbe6d9
git.gammaspectra.live/P2Pool/observer-cmd-utils v0.0.0-20240403175747-82cff4d3bd01
github.com/gorilla/mux v1.8.1
)
require (
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/dolthub/maphash v0.1.0 // indirect
github.com/dolthub/swiss v0.2.1 // indirect
github.com/floatdrop/lru v1.3.0 // indirect
github.com/go-zeromq/goczmq/v4 v4.2.2 // indirect
github.com/go-zeromq/zmq4 v0.16.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/holiman/uint256 v1.2.4 // indirect
github.com/jxskiss/base62 v1.1.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
go.etcd.io/bbolt v1.3.7 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
)
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887

62
go.sum Normal file
View file

@ -0,0 +1,62 @@
git.gammaspectra.live/P2Pool/consensus/v3 v3.0.1 h1:ibdNiP5uw6yfsnqld7yYTkcnlr+EL6ifEaTP6fUkh3s=
git.gammaspectra.live/P2Pool/consensus/v3 v3.0.1/go.mod h1:wgvMwy3c+rztPojdRaRUH6Hly+DRHy6Kv9KRIos3FxU=
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
git.gammaspectra.live/P2Pool/observer-cache-archive v0.0.0-20240403175902-82f1b2cbe6d9 h1:xxFQ7gJB4a0wTZv8InZADAF65e4WKAlBCt2JgmQq//w=
git.gammaspectra.live/P2Pool/observer-cache-archive v0.0.0-20240403175902-82f1b2cbe6d9/go.mod h1:N3CQRDaVmZkTnRYbyNsmB9HGVcRnCSpAAKo6UNtxmFo=
git.gammaspectra.live/P2Pool/observer-cmd-utils v0.0.0-20240403175747-82cff4d3bd01 h1:K4N+NZ0XdJQqeQwxgkgyISv7+/1Afy2RgMbyykJbARk=
git.gammaspectra.live/P2Pool/observer-cmd-utils v0.0.0-20240403175747-82cff4d3bd01/go.mod h1:BPFII6OaDqWYVp5uX9SFsUMQA6gd63KsH3x+kFWG6is=
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
github.com/go-zeromq/goczmq/v4 v4.2.2 h1:HAJN+i+3NW55ijMJJhk7oWxHKXgAuSBkoFfvr8bYj4U=
github.com/go-zeromq/goczmq/v4 v4.2.2/go.mod h1:Sm/lxrfxP/Oxqs0tnHD6WAhwkWrx+S+1MRrKzcxoaYE=
github.com/go-zeromq/zmq4 v0.16.0 h1:D6oIPWSdkY/4DJu4tBUmo28P3WRq4F4Ji4/iQ/fJHc0=
github.com/go-zeromq/zmq4 v0.16.0/go.mod h1:8c3aXloJBRPba1AqWMJK4vypniM+yC+JKqi8KpRaDFc=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=

318
main.go Normal file
View file

@ -0,0 +1,318 @@
package main
import (
"bufio"
"flag"
"fmt"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/p2p"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/types"
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
"net"
"net/http"
_ "net/http/pprof"
"net/netip"
"os"
"os/signal"
"runtime"
"runtime/debug"
"slices"
"strconv"
"strings"
"sync"
"syscall"
"time"
)
func main() {
currentConsensus := sidechain.ConsensusDefault
//monerod related
moneroHost := flag.String("host", "127.0.0.1", "IP address of your Monero node")
moneroRpcPort := flag.Uint("rpc-port", 18081, "monerod RPC API port number")
moneroZmqPort := flag.Uint("zmq-port", 18083, "monerod ZMQ pub port number")
// consensus related
consensusConfigFile := flag.String("consensus-config", "", "Name of the p2pool consensus config file")
useMiniSidechain := flag.Bool("mini", false, "Connect to p2pool-mini sidechain. Note that it will also change default p2p port.")
//p2p peering related
p2pListen := flag.String("p2p", fmt.Sprintf("0.0.0.0:%d", currentConsensus.DefaultPort()), "IP:port for p2p server to listen on.")
p2pExternalPort := flag.Uint64("p2p-external-port", 0, "Port number that your router uses for mapping to your local p2p port. Use it if you are behind a NAT and still want to accept incoming connections")
outPeers := flag.Uint64("out-peers", 10, "Maximum number of outgoing connections for p2p server (any value between 10 and 450)")
inPeers := flag.Uint64("in-peers", 10, "Maximum number of incoming connections for p2p server (any value between 10 and 450)")
addPeers := flag.String("addpeers", "", "Comma-separated list of IP:port of other p2pool nodes to connect to")
addSelf := flag.Bool("add-self-peer", false, "Adds itself to the peer list regularly, based on found local interfaces for IPv4/IPv6")
peerList := flag.String("peer-list", "p2pool_peers.txt", "Either a path or an URL to obtain peer lists from. If it is a path, new peers will be saved to this path. Set to empty to disable")
//other settings
lightMode := flag.Bool("light-mode", false, "Don't allocate RandomX dataset, saves 2GB of RAM")
noDns := flag.Bool("no-dns", false, "Disable DNS queries, use only IP addresses to connect to peers (seed node DNS will be unavailable too)")
memoryLimitInGiB := flag.Uint64("memory-limit", 0, "Memory limit for go managed sections in GiB, set 0 to disable")
apiBind := flag.String("api-bind", "", "Bind to this address to serve blocks, and other utility methods. If -archive is specified, serve archived blocks.")
createArchive := flag.String("archive", "", "If specified, create an archive store of sidechain blocks on this path.")
blockCache := flag.String("block-cache", "p2pool.cache", "Block cache for faster startups. Set to empty to disable")
//testing settings
doDebug := flag.Bool("debug", false, "Log more details. Default false")
ipv6Only := flag.Bool("ipv6-only", false, "Use only IPv6. Default false")
debugListen := flag.String("debug-listen", "", "Provide a bind address and port to expose a pprof HTTP API on it.")
//TODO extend verbosity to debug flag
flag.Parse()
if *doDebug {
utils.LogFile = true
}
if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil {
utils.Logf("P2Pool", "Consensus Software %s %s (go version %s)", types.CurrentSoftwareId, types.CurrentSoftwareVersion, buildInfo.GoVersion)
} else {
utils.Logf("P2Pool", "Consensus Software %s %s (go version %s)", types.CurrentSoftwareId, types.CurrentSoftwareVersion, runtime.Version())
}
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
debug.SetTraceback("all")
if *memoryLimitInGiB != 0 {
debug.SetMemoryLimit(int64(*memoryLimitInGiB) * 1024 * 1024 * 1024)
}
settings := make(map[string]string)
settings["listen"] = *p2pListen
changeConsensus := func(newConsensus *sidechain.Consensus) {
oldListen := netip.MustParseAddrPort(settings["listen"])
// if default exists, change port to match
if settings["listen"] == fmt.Sprintf("%s:%d", oldListen.Addr().String(), currentConsensus.DefaultPort()) {
settings["listen"] = fmt.Sprintf("%s:%d", oldListen.Addr().String(), newConsensus.DefaultPort())
}
currentConsensus = newConsensus
}
settings["rpc-url"] = fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort)
settings["zmq-url"] = fmt.Sprintf("tcp://%s:%d", *moneroHost, *moneroZmqPort)
if *useMiniSidechain {
changeConsensus(sidechain.ConsensusMini)
}
if *consensusConfigFile != "" {
consensusData, err := os.ReadFile(*consensusConfigFile)
if err != nil {
utils.Panic(err)
}
if newConsensus, err := sidechain.NewConsensusFromJSON(consensusData); err != nil {
utils.Panic(err)
} else {
changeConsensus(newConsensus)
}
}
settings["out-peers"] = strconv.FormatUint(*outPeers, 10)
settings["in-peers"] = strconv.FormatUint(*inPeers, 10)
settings["external-port"] = strconv.FormatUint(*p2pExternalPort, 10)
if *ipv6Only {
settings["ipv6-only"] = "true"
}
if *blockCache != "" {
settings["cache"] = *blockCache
}
if *createArchive != "" {
settings["archive"] = *createArchive
}
if !*lightMode {
settings["full-mode"] = "true"
}
if instance, err := NewP2Pool(currentConsensus, settings); err != nil {
utils.Fatalf("Could not start p2pool: %s", err)
} else {
defer instance.Close(nil)
if *apiBind != "" {
serveMux := getServerMux(instance)
server := &http.Server{
Addr: *apiBind,
ReadTimeout: time.Second * 2,
Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if request.Method != "GET" && request.Method != "HEAD" && request.Method != "POST" {
writer.WriteHeader(http.StatusForbidden)
return
}
utils.Logf("API", "Handling %s %s", request.Method, request.URL.String())
serveMux.ServeHTTP(writer, request)
}),
}
go func() {
//TODO: context/wait
if err := server.ListenAndServe(); err != nil {
utils.Panic(err)
}
}()
}
if *debugListen != "" {
go func() {
if err := http.ListenAndServe(*debugListen, nil); err != nil {
utils.Panic(err)
}
}()
}
var connectList []netip.AddrPort
for _, peerAddr := range strings.Split(*addPeers, ",") {
if peerAddr == "" {
continue
}
//TODO: dns resolution of hosts
if addrPort, err := netip.ParseAddrPort(peerAddr); err != nil {
utils.Panic(err)
} else {
instance.Server().AddToPeerList(addrPort)
connectList = append(connectList, addrPort)
}
}
if !*noDns && currentConsensus.SeedNode() != "" {
utils.Logf("P2Pool", "Loading seed peers from %s", currentConsensus.SeedNode())
records, _ := net.LookupTXT(currentConsensus.SeedNode())
if len(records) > 0 {
for _, r := range records {
for _, e := range strings.Split(r, ",") {
if seedNodeAddr, err := netip.ParseAddrPort(e); err == nil {
instance.Server().AddToPeerList(seedNodeAddr)
}
}
}
} else {
ips, _ := net.LookupIP(currentConsensus.SeedNode())
for _, seedNodeIp := range ips {
seedNodeAddr := netip.AddrPortFrom(netip.MustParseAddr(seedNodeIp.String()), currentConsensus.DefaultPort())
instance.Server().AddToPeerList(seedNodeAddr)
}
}
}
if *peerList != "" {
utils.Logf("P2Pool", "Loading peers from %s", *peerList)
if len(*peerList) > 4 && (*peerList)[:4] == "http" {
func() {
r, err := http.DefaultClient.Get(*peerList)
if err == nil {
defer r.Body.Close()
scanner := bufio.NewScanner(r.Body)
for scanner.Scan() {
if addrPort, err := netip.ParseAddrPort(strings.TrimSpace(scanner.Text())); err == nil {
instance.Server().AddToPeerList(addrPort)
}
}
}
}()
} else {
func() {
f, err := os.Open(*peerList)
if err == nil {
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if addrPort, err := netip.ParseAddrPort(strings.TrimSpace(scanner.Text())); err == nil {
instance.Server().AddToPeerList(addrPort)
}
}
}
}()
go func() {
contents := make([]byte, 0, 4096)
for range utils.ContextTick(instance.Context(), time.Minute*1) {
contents = contents[:0]
peerListEntries := instance.Server().PeerList()
slices.SortFunc(peerListEntries, func(a, b *p2p.PeerListEntry) int {
return a.AddressPort.Compare(b.AddressPort)
})
for _, addrPort := range peerListEntries {
contents = append(contents, []byte(addrPort.AddressPort.String())...)
contents = append(contents, '\n')
}
if err := os.WriteFile(*peerList, contents, 0644); err != nil {
utils.Errorf("P2Pool", "error writing peer list %s: %s", *peerList, err.Error())
break
}
}
}()
}
}
if *addSelf {
if addrs, err := utils.GetOutboundIPv6(); err != nil {
utils.Errorf("P2Pool", "Could not get interface ipv6 addresses: %s", err)
} else {
for _, addr := range addrs {
if addr.Is6() {
//use own port directly?
instance.Server().AddToPeerList(netip.AddrPortFrom(addr, instance.Server().ListenPort()))
} else {
instance.Server().AddToPeerList(netip.AddrPortFrom(addr, instance.Server().ExternalListenPort()))
}
}
}
}
go func() {
for !instance.Started() {
time.Sleep(time.Second * 1)
}
var wg sync.WaitGroup
for _, addrPort := range connectList {
wg.Add(1)
go func(addrPort netip.AddrPort) {
defer wg.Done()
if err := instance.Server().Connect(addrPort); err != nil {
utils.Errorf("error connecting to initial peer %s: %s", addrPort.String(), err.Error())
}
}(addrPort)
}
wg.Wait()
instance.Server().UpdateClientConnections()
}()
sigHandler := make(chan os.Signal, 1)
signal.Notify(sigHandler, syscall.SIGINT)
go func() {
for s := range sigHandler {
if s == syscall.SIGKILL || s == syscall.SIGINT {
instance.Close(nil)
}
}
}()
if err := instance.Run(); err != nil {
instance.Close(err)
if instance.Started() {
instance.WaitUntilClosed()
}
if closeError := instance.CloseError(); closeError != nil {
utils.Panic(err)
} else {
os.Exit(0)
}
}
}
}

627
p2pool.go Normal file
View file

@ -0,0 +1,627 @@
package main
import (
"context"
"encoding/binary"
"errors"
"fmt"
"git.gammaspectra.live/P2Pool/consensus/v3/monero"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/block"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/zmq"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/randomx"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/transaction"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/cache"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/cache/legacy"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/mainchain"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/mempool"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/p2p"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
p2pooltypes "git.gammaspectra.live/P2Pool/consensus/v3/p2pool/types"
"git.gammaspectra.live/P2Pool/consensus/v3/types"
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
archive "git.gammaspectra.live/P2Pool/observer-cache-archive"
"slices"
"strconv"
"sync"
"sync/atomic"
"time"
)
type EventListener struct {
ListenerId uint64
Tip func(tip *sidechain.PoolBlock)
Broadcast func(b *sidechain.PoolBlock)
Found func(data *sidechain.ChainMain, b *sidechain.PoolBlock)
MainData func(data *sidechain.ChainMain)
MinerData func(data *p2pooltypes.MinerData)
MempoolData func(data mempool.Mempool)
}
type P2Pool struct {
consensus *sidechain.Consensus
sidechain *sidechain.SideChain
mainchain *mainchain.MainChain
archive cache.AddressableCache
cache cache.HeapCache
server *p2p.Server
ctx context.Context
ctxCancel context.CancelFunc
rpcClient *client.Client
zmqClient *zmq.Client
recentSubmittedBlocks *utils.CircularBuffer[types.Hash]
listenersLock sync.RWMutex
listeners []EventListener
nextListenerId uint64
started atomic.Bool
closeError error
closed chan struct{}
}
// AddListener Registers listener to several events produced centrally.
// Note that you should process events called as fast as possible, or spawn a new goroutine to not block
func (p *P2Pool) AddListener(tip func(tip *sidechain.PoolBlock), broadcast func(b *sidechain.PoolBlock), found func(data *sidechain.ChainMain, b *sidechain.PoolBlock), mainData func(data *sidechain.ChainMain), minerData func(data *p2pooltypes.MinerData), mempoolData func(data mempool.Mempool)) (listenerId uint64) {
p.listenersLock.Lock()
p.listenersLock.Unlock()
listenerId = p.nextListenerId
p.nextListenerId++
p.listeners = append(p.listeners, EventListener{
ListenerId: listenerId,
Tip: tip,
Broadcast: broadcast,
Found: found,
MainData: mainData,
MinerData: minerData,
MempoolData: mempoolData,
})
return listenerId
}
func (p *P2Pool) RemoveListener(listenerId uint64) bool {
p.listenersLock.Lock()
p.listenersLock.Unlock()
if i := slices.IndexFunc(p.listeners, func(listener EventListener) bool {
return listener.ListenerId == listenerId
}); i != -1 {
p.listeners = slices.Delete(p.listeners, i, i+1)
return true
}
return false
}
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
}
func (p *P2Pool) AddressableCache() cache.AddressableCache {
return p.archive
}
func (p *P2Pool) Cache() cache.Cache {
return p.cache
}
func (p *P2Pool) CloseError() error {
return p.closeError
}
func (p *P2Pool) WaitUntilClosed() {
<-p.closed
}
func (p *P2Pool) Close(err error) {
started := p.started.Swap(false)
if started {
p.closeError = err
}
p.ctxCancel()
_ = p.zmqClient.Close()
if p.server != nil {
p.server.Close()
}
if p.cache != nil {
p.cache.Close()
}
if p.archive != nil {
p.archive.Close()
}
if started && err != nil {
close(p.closed)
utils.Panicf("[P2Pool] Closed due to error %s", err)
} else if started {
close(p.closed)
utils.Logf("P2Pool", "Closed")
} else if err != nil {
utils.Logf("P2Pool", "Received extra error during closing %s", err)
}
}
func NewP2Pool(consensus *sidechain.Consensus, settings map[string]string) (*P2Pool, error) {
if settings["full-mode"] == "true" {
if err := consensus.InitHasher(2, randomx.FlagSecure, randomx.FlagFullMemory); err != nil {
return nil, err
}
} else {
if err := consensus.InitHasher(2, randomx.FlagSecure); err != nil {
return nil, err
}
}
pool := &P2Pool{
consensus: consensus,
recentSubmittedBlocks: utils.NewCircularBuffer[types.Hash](8),
closed: make(chan struct{}),
}
var err error
pool.ctx, pool.ctxCancel = context.WithCancel(context.Background())
listenAddress := fmt.Sprintf("0.0.0.0:%d", pool.Consensus().DefaultPort())
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, zmq.TopicMinimalTxPoolAdd)
pool.sidechain = sidechain.NewSideChain(pool)
pool.mainchain = mainchain.NewMainChain(pool.sidechain, pool)
if pool.mainchain == nil {
return nil, errors.New("could not create MainChain")
}
if archivePath, ok := settings["archive"]; ok {
if pool.archive, err = archive.NewCache(archivePath, pool.consensus, pool.GetDifficultyByHeight); err != nil {
return nil, fmt.Errorf("could not create cache: %w", err)
}
}
if cachePath, ok := settings["cache"]; ok {
if pool.cache, err = legacy.NewCache(consensus, cachePath); err != nil {
return nil, fmt.Errorf("could not create cache: %w", err)
}
}
if addr, ok := settings["listen"]; ok {
listenAddress = addr
}
maxOutgoingPeers := uint64(10)
if outgoingPeers, ok := settings["out-peers"]; ok {
maxOutgoingPeers, _ = strconv.ParseUint(outgoingPeers, 10, 0)
}
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)
}
useIPv4, useIPv6 := true, true
if b, ok := settings["ipv6-only"]; ok && b == "true" {
useIPv4 = false
useIPv6 = true
}
if pool.server, err = p2p.NewServer(pool, listenAddress, uint16(externalListenPort), uint32(maxOutgoingPeers), uint32(maxIncomingPeers), useIPv4, useIPv6, pool.ctx); err != nil {
return nil, err
}
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()
}
func (p *P2Pool) GetMinerDataTip() *p2pooltypes.MinerData {
return p.mainchain.GetMinerDataTip()
}
// GetMinimalBlockHeaderByHeight Only Id / Height / Timestamp are assured
func (p *P2Pool) GetMinimalBlockHeaderByHeight(height uint64) *block.Header {
lowerThanTip := height <= p.mainchain.GetChainMainTip().Height
if chainMain := p.mainchain.GetChainMainByHeight(height); chainMain != nil && chainMain.Id != types.ZeroHash {
prev := p.mainchain.GetChainMainByHeight(height - 1)
if prev != nil {
return &block.Header{
Timestamp: chainMain.Timestamp,
Height: chainMain.Height,
Reward: chainMain.Reward,
Difficulty: chainMain.Difficulty,
Id: chainMain.Id,
PreviousId: prev.Id,
}
} else {
return &block.Header{
Timestamp: chainMain.Timestamp,
Height: chainMain.Height,
Reward: chainMain.Reward,
Difficulty: chainMain.Difficulty,
Id: chainMain.Id,
}
}
} else if lowerThanTip {
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),
}
return blockHeader
}
}
return nil
}
func (p *P2Pool) GetDifficultyByHeight(height uint64) types.Difficulty {
lowerThanTip := height <= p.mainchain.GetChainMainTip().Height
if chainMain := p.mainchain.GetChainMainByHeight(height); chainMain != nil && chainMain.Difficulty != types.ZeroDifficulty {
return chainMain.Difficulty
} else if lowerThanTip {
//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),
}
return blockHeader.Difficulty
}
}
return types.ZeroDifficulty
}
// 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 || header == nil {
return nil
} else {
prevHash, _ := types.HashFromString(header.PrevHash)
h, _ := types.HashFromString(header.Hash)
blockHeader := &block.Header{
MajorVersion: uint8(header.MajorVersion),
MinorVersion: uint8(header.MinorVersion),
Timestamp: uint64(header.Timestamp),
PreviousId: prevHash,
Height: header.Height,
Nonce: uint32(header.Nonce),
Reward: header.Reward,
Id: h,
Difficulty: types.DifficultyFrom64(header.Difficulty),
}
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
}
func (p *P2Pool) SideChain() *sidechain.SideChain {
return p.sidechain
}
func (p *P2Pool) MainChain() *mainchain.MainChain {
return p.mainchain
}
func (p *P2Pool) Server() *p2p.Server {
return p.server
}
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
}
go func() {
//TODO: redo listen
if err := p.mainchain.Listen(); err != nil {
p.Close(err)
}
}()
//TODO: move peer list loading here
if p.cache != nil {
p.cache.LoadAll(p.Server())
}
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 {
utils.Logf("P2Pool", "monerod is busy syncing, trying again in 5 seconds")
time.Sleep(time.Second * 5)
return p.getInfo()
} else if !info.Synchronized {
utils.Logf("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
}
func (p *P2Pool) getVersion() error {
if version, err := p.ClientRPC().GetVersion(); err != nil {
return err
} else {
if version.Version < monero.RequiredMoneroVersion {
return fmt.Errorf("monerod RPC v%d.%d is incompatible, update to RPC >= v%d.%d (Monero %s or newer)", version.Version>>16, version.Version&0xffff, monero.RequiredMajor, monero.RequiredMinor, monero.RequiredMoneroString)
}
}
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)
data := &p2pooltypes.MinerData{
MajorVersion: minerData.MajorVersion,
Height: minerData.Height,
PrevId: prevId,
SeedHash: seedHash,
Difficulty: diff,
MedianWeight: minerData.MedianWeight,
AlreadyGeneratedCoins: minerData.AlreadyGeneratedCoins,
MedianTimestamp: minerData.MedianTimestamp,
TimeReceived: time.Now(),
}
data.TxBacklog = make(mempool.Mempool, len(minerData.TxBacklog))
for i, e := range minerData.TxBacklog {
txId, _ := types.HashFromString(e.Id)
data.TxBacklog[i] = &mempool.MempoolEntry{
Id: txId,
BlobSize: e.BlobSize,
Weight: e.Weight,
Fee: e.Fee,
}
}
p.mainchain.HandleMinerData(data)
return p.mainchain.DownloadBlockHeaders(minerData.Height)
}
}
func (p *P2Pool) Consensus() *sidechain.Consensus {
return p.consensus
}
func (p *P2Pool) UpdateMainData(data *sidechain.ChainMain) {
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for i := range p.listeners {
if p.listeners[i].MainData != nil {
p.listeners[i].MainData(data)
}
}
}
func (p *P2Pool) UpdateMempoolData(data mempool.Mempool) {
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for i := range p.listeners {
if p.listeners[i].MempoolData != nil {
p.listeners[i].MempoolData(data)
}
}
}
func (p *P2Pool) UpdateMinerData(data *p2pooltypes.MinerData) {
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for i := range p.listeners {
if p.listeners[i].MinerData != nil {
p.listeners[i].MinerData(data)
}
}
}
func (p *P2Pool) UpdateBlockFound(data *sidechain.ChainMain, block *sidechain.PoolBlock) {
utils.Logf("P2Pool", "BLOCK FOUND: main chain block at height %d, id %s was mined by this p2pool", data.Height, data.Id)
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for i := range p.listeners {
if p.listeners[i].Found != nil {
p.listeners[i].Found(data, block)
}
}
}
func (p *P2Pool) SubmitBlock(b *block.Block) {
go func() {
//do not submit multiple monero blocks to monerod
if !p.recentSubmittedBlocks.PushUnique(b.Id()) {
return
}
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)
}
utils.Logf("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 {
utils.Logf("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" {
utils.Logf("P2Pool", "submit_block: BLOCK ACCEPTED at height = %d, template id = %s", b.Coinbase.GenHeight, templateId.String())
} else {
utils.Logf("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)
}
}
}
}()
}
func (p *P2Pool) Store(block *sidechain.PoolBlock) {
if p.cache != nil {
p.cache.Store(block)
}
if p.archive != nil {
p.archive.Store(block)
}
}
func (p *P2Pool) ClearCachedBlocks() {
p.server.ClearCachedBlocks()
}
func (p *P2Pool) Started() bool {
return p.started.Load()
}
func (p *P2Pool) UpdateTip(tip *sidechain.PoolBlock) {
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for i := range p.listeners {
if p.listeners[i].Tip != nil {
p.listeners[i].Tip(tip)
}
}
}
func (p *P2Pool) Broadcast(block *sidechain.PoolBlock) {
minerData := p.GetMinerDataTip()
if (block.Main.Coinbase.GenHeight)+2 < minerData.Height {
utils.Logf("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) {
utils.Logf("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
}
p.server.Broadcast(block)
p.listenersLock.RLock()
defer p.listenersLock.RUnlock()
for i := range p.listeners {
if p.listeners[i].Broadcast != nil {
p.listeners[i].Broadcast(block)
}
}
}