Implemented ZMQ fetch of block data, proper main chain handling, refactored client RPC access, updated dependencies
This commit is contained in:
parent
ff946d7431
commit
92b74f667f
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/exp/maps"
|
||||
|
@ -32,9 +33,10 @@ func setHeightDifficulty(height uint64, difficulty types.Difficulty) {
|
|||
difficultyCache[height] = difficulty
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
func cacheHeightDifficulty(height uint64) {
|
||||
if _, ok := getHeightDifficulty(height); !ok {
|
||||
if header, err := client.GetDefaultClient().GetBlockHeaderByHeight(height); err != nil {
|
||||
if header, err := client.GetDefaultClient().GetBlockHeaderByHeight(height, context.Background()); err != nil {
|
||||
if template, err := client.GetDefaultClient().GetBlockTemplate(types.DonationAddress); err != nil {
|
||||
setHeightDifficulty(uint64(template.Height), types.DifficultyFrom64(uint64(template.Difficulty)))
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
|
@ -23,6 +22,7 @@ func main() {
|
|||
|
||||
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")
|
||||
p2pListen := flag.String("p2p", fmt.Sprintf("0.0.0.0:%d", currentConsensus.DefaultPort()), "IP:port for p2p server to listen on.")
|
||||
//TODO: zmq
|
||||
addPeers := flag.String("addpeers", "", "Comma-separated list of IP:port of other p2pool nodes to connect to")
|
||||
|
@ -40,11 +40,20 @@ func main() {
|
|||
|
||||
settings := make(map[string]string)
|
||||
settings["listen"] = *p2pListen
|
||||
if *useMiniSidechain {
|
||||
if settings["listen"] == fmt.Sprintf("0.0.0.0:%d", currentConsensus.DefaultPort()) {
|
||||
settings["listen"] = fmt.Sprintf("0.0.0.0:%d", sidechain.ConsensusMini.DefaultPort())
|
||||
|
||||
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 = sidechain.ConsensusMini
|
||||
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 != "" {
|
||||
|
@ -52,24 +61,24 @@ func main() {
|
|||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
var newConsensus sidechain.Consensus
|
||||
if err = json.Unmarshal(consensusData, &newConsensus); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
if settings["listen"] == fmt.Sprintf("0.0.0.0:%d", currentConsensus.DefaultPort()) {
|
||||
settings["listen"] = fmt.Sprintf("0.0.0.0:%d", newConsensus.DefaultPort())
|
||||
if newConsensus, err := sidechain.NewConsensusFromJSON(consensusData); err != nil {
|
||||
log.Panic(err)
|
||||
} else {
|
||||
changeConsensus(newConsensus)
|
||||
}
|
||||
currentConsensus = &newConsensus
|
||||
}
|
||||
|
||||
settings["out-peers"] = strconv.FormatUint(*outPeers, 10)
|
||||
settings["in-peers"] = strconv.FormatUint(*inPeers, 10)
|
||||
settings["external-port"] = strconv.FormatUint(*p2pExternalPort, 10)
|
||||
|
||||
if p2pool := p2pool2.NewP2Pool(currentConsensus, settings); p2pool == nil {
|
||||
log.Fatal("Could not start p2pool")
|
||||
if p2pool, err := p2pool2.NewP2Pool(currentConsensus, settings); err != nil {
|
||||
log.Fatalf("Could not start p2pool: %s", err)
|
||||
} else {
|
||||
defer p2pool.Close()
|
||||
|
||||
var connectList []netip.AddrPort
|
||||
for _, peerAddr := range strings.Split(*addPeers, ",") {
|
||||
if peerAddr == "" {
|
||||
continue
|
||||
|
@ -79,11 +88,7 @@ func main() {
|
|||
log.Panic(err)
|
||||
} else {
|
||||
p2pool.Server().AddToPeerList(addrPort)
|
||||
go func() {
|
||||
if err := p2pool.Server().Connect(addrPort); err != nil {
|
||||
log.Printf("error connecting to peer %s: %s", addrPort.String(), err.Error())
|
||||
}
|
||||
}()
|
||||
connectList = append(connectList, addrPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,11 +98,7 @@ func main() {
|
|||
for _, seedNodeIp := range ips {
|
||||
seedNodeAddr := netip.MustParseAddrPort(fmt.Sprintf("%s:%d", seedNodeIp.String(), currentConsensus.DefaultPort()))
|
||||
p2pool.Server().AddToPeerList(seedNodeAddr)
|
||||
go func() {
|
||||
if err := p2pool.Server().Connect(seedNodeAddr); err != nil {
|
||||
log.Printf("error connecting to seed node peer %s: %s", seedNodeAddr.String(), err.Error())
|
||||
}
|
||||
}()
|
||||
//connectList = append(connectList, seedNodeAddr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +148,21 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
if err := p2pool.Server().Listen(); err != nil {
|
||||
go func() {
|
||||
for !p2pool.Started() {
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
|
||||
for _, addrPort := range connectList {
|
||||
go func(addrPort netip.AddrPort) {
|
||||
if err := p2pool.Server().Connect(addrPort); err != nil {
|
||||
log.Printf("error connecting to peer %s: %s", addrPort.String(), err.Error())
|
||||
}
|
||||
}(addrPort)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := p2pool.Run(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,15 @@ package database
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/exp/slices"
|
||||
|
@ -29,8 +32,8 @@ type BlockInterface interface {
|
|||
}
|
||||
|
||||
type BlockCoinbase struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Reward uint64 `json:"reward"`
|
||||
Id types.Hash `json:"id"`
|
||||
Reward uint64 `json:"reward"`
|
||||
PrivateKey crypto.PrivateKeyBytes `json:"private_key"`
|
||||
//Payouts extra JSON field, do not use
|
||||
Payouts []*JSONCoinbaseOutput `json:"payouts,omitempty"`
|
||||
|
@ -117,6 +120,24 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.PoolBlock, knownUncles [
|
|||
return nil, nil, errors.New("could not get or create miner")
|
||||
}
|
||||
|
||||
getSeedByHeight := func(height uint64) (hash types.Hash) {
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
if h, err := client.GetDefaultClient().GetBlockHeaderByHeight(seedHeight, context.Background()); err != nil {
|
||||
return types.ZeroHash
|
||||
} else {
|
||||
hash, _ := types.HashFromString(h.BlockHeader.Hash)
|
||||
return hash
|
||||
}
|
||||
}
|
||||
|
||||
getDifficultyByHeight := func(height uint64) types.Difficulty {
|
||||
if h, err := client.GetDefaultClient().GetBlockHeaderByHeight(height, context.Background()); err != nil {
|
||||
return types.ZeroDifficulty
|
||||
} else {
|
||||
return types.DifficultyFrom64(h.BlockHeader.Difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
block = &Block{
|
||||
Id: types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId)),
|
||||
Height: b.Side.Height,
|
||||
|
@ -134,18 +155,18 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.PoolBlock, knownUncles [
|
|||
Difficulty: b.Side.Difficulty,
|
||||
Timestamp: b.Main.Timestamp,
|
||||
MinerId: miner.Id(),
|
||||
PowHash: b.PowHash(),
|
||||
PowHash: b.PowHash(getSeedByHeight),
|
||||
Main: BlockMainData{
|
||||
Id: b.MainId(),
|
||||
Height: b.Main.Coinbase.GenHeight,
|
||||
Found: b.IsProofHigherThanMainDifficulty(),
|
||||
Found: b.IsProofHigherThanMainDifficulty(getDifficultyByHeight, getSeedByHeight),
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: b.Main.PreviousId,
|
||||
Difficulty: b.MainDifficulty(),
|
||||
Difficulty: b.MainDifficulty(getDifficultyByHeight),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -176,18 +197,18 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.PoolBlock, knownUncles [
|
|||
Difficulty: uncle.Side.Difficulty,
|
||||
Timestamp: uncle.Main.Timestamp,
|
||||
MinerId: uncleMiner.Id(),
|
||||
PowHash: uncle.PowHash(),
|
||||
PowHash: uncle.PowHash(getSeedByHeight),
|
||||
Main: BlockMainData{
|
||||
Id: uncle.MainId(),
|
||||
Height: uncle.Main.Coinbase.GenHeight,
|
||||
Found: uncle.IsProofHigherThanMainDifficulty(),
|
||||
Found: uncle.IsProofHigherThanMainDifficulty(getDifficultyByHeight, getSeedByHeight),
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: uncle.Main.PreviousId,
|
||||
Difficulty: uncle.MainDifficulty(),
|
||||
Difficulty: uncle.MainDifficulty(getDifficultyByHeight),
|
||||
},
|
||||
},
|
||||
ParentId: block.Id,
|
||||
|
@ -202,61 +223,61 @@ func NewBlockFromBinaryBlock(db *Database, b *sidechain.PoolBlock, knownUncles [
|
|||
}
|
||||
|
||||
type JsonBlock2 struct {
|
||||
MinerMainId types.Hash `json:"miner_main_id"`
|
||||
CoinbaseReward uint64 `json:"coinbase_reward,string"`
|
||||
CoinbaseId types.Hash `json:"coinbase_id"`
|
||||
Version uint64 `json:"version,string"`
|
||||
Diff types.Difficulty `json:"diff"`
|
||||
Wallet *address.Address `json:"wallet"`
|
||||
MinerMainDiff types.Difficulty `json:"miner_main_diff"`
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height,string"`
|
||||
PowHash types.Hash `json:"pow_hash"`
|
||||
MainId types.Hash `json:"main_id"`
|
||||
MainHeight uint64 `json:"main_height,string"`
|
||||
Ts uint64 `json:"ts,string"`
|
||||
PrevId types.Hash `json:"prev_id"`
|
||||
CoinbasePriv crypto.PrivateKeyBytes `json:"coinbase_priv"`
|
||||
Lts uint64 `json:"lts,string"`
|
||||
MainFound string `json:"main_found,omitempty"`
|
||||
MinerMainId types.Hash `json:"miner_main_id"`
|
||||
CoinbaseReward uint64 `json:"coinbase_reward,string"`
|
||||
CoinbaseId types.Hash `json:"coinbase_id"`
|
||||
Version uint64 `json:"version,string"`
|
||||
Diff types.Difficulty `json:"diff"`
|
||||
Wallet *address.Address `json:"wallet"`
|
||||
MinerMainDiff types.Difficulty `json:"miner_main_diff"`
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height,string"`
|
||||
PowHash types.Hash `json:"pow_hash"`
|
||||
MainId types.Hash `json:"main_id"`
|
||||
MainHeight uint64 `json:"main_height,string"`
|
||||
Ts uint64 `json:"ts,string"`
|
||||
PrevId types.Hash `json:"prev_id"`
|
||||
CoinbasePriv crypto.PrivateKeyBytes `json:"coinbase_priv"`
|
||||
Lts uint64 `json:"lts,string"`
|
||||
MainFound string `json:"main_found,omitempty"`
|
||||
|
||||
Uncles []struct {
|
||||
MinerMainId types.Hash `json:"miner_main_id"`
|
||||
CoinbaseReward uint64 `json:"coinbase_reward,string"`
|
||||
CoinbaseId types.Hash `json:"coinbase_id"`
|
||||
Version uint64 `json:"version,string"`
|
||||
Diff types.Difficulty `json:"diff"`
|
||||
Wallet *address.Address `json:"wallet"`
|
||||
MinerMainDiff types.Difficulty `json:"miner_main_diff"`
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height,string"`
|
||||
PowHash types.Hash `json:"pow_hash"`
|
||||
MainId types.Hash `json:"main_id"`
|
||||
MainHeight uint64 `json:"main_height,string"`
|
||||
Ts uint64 `json:"ts,string"`
|
||||
PrevId types.Hash `json:"prev_id"`
|
||||
CoinbasePriv crypto.PrivateKeyBytes `json:"coinbase_priv"`
|
||||
Lts uint64 `json:"lts,string"`
|
||||
MainFound string `json:"main_found,omitempty"`
|
||||
MinerMainId types.Hash `json:"miner_main_id"`
|
||||
CoinbaseReward uint64 `json:"coinbase_reward,string"`
|
||||
CoinbaseId types.Hash `json:"coinbase_id"`
|
||||
Version uint64 `json:"version,string"`
|
||||
Diff types.Difficulty `json:"diff"`
|
||||
Wallet *address.Address `json:"wallet"`
|
||||
MinerMainDiff types.Difficulty `json:"miner_main_diff"`
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height,string"`
|
||||
PowHash types.Hash `json:"pow_hash"`
|
||||
MainId types.Hash `json:"main_id"`
|
||||
MainHeight uint64 `json:"main_height,string"`
|
||||
Ts uint64 `json:"ts,string"`
|
||||
PrevId types.Hash `json:"prev_id"`
|
||||
CoinbasePriv crypto.PrivateKeyBytes `json:"coinbase_priv"`
|
||||
Lts uint64 `json:"lts,string"`
|
||||
MainFound string `json:"main_found,omitempty"`
|
||||
} `json:"uncles,omitempty"`
|
||||
}
|
||||
|
||||
type JsonBlock1 struct {
|
||||
Wallet *address.Address `json:"wallet"`
|
||||
Height uint64 `json:"height,string"`
|
||||
MHeight uint64 `json:"mheight,string"`
|
||||
PrevId types.Hash `json:"prev_id"`
|
||||
Ts uint64 `json:"ts,string"`
|
||||
PowHash types.Hash `json:"pow_hash"`
|
||||
Id types.Hash `json:"id"`
|
||||
PrevHash types.Hash `json:"prev_hash"`
|
||||
Diff uint64 `json:"diff,string"`
|
||||
TxCoinbase types.Hash `json:"tx_coinbase"`
|
||||
Lts uint64 `json:"lts,string"`
|
||||
MHash types.Hash `json:"mhash"`
|
||||
TxPriv crypto.PrivateKeyBytes `json:"tx_priv"`
|
||||
TxPub crypto.PublicKeyBytes `json:"tx_pub"`
|
||||
BlockFound string `json:"main_found,omitempty"`
|
||||
Wallet *address.Address `json:"wallet"`
|
||||
Height uint64 `json:"height,string"`
|
||||
MHeight uint64 `json:"mheight,string"`
|
||||
PrevId types.Hash `json:"prev_id"`
|
||||
Ts uint64 `json:"ts,string"`
|
||||
PowHash types.Hash `json:"pow_hash"`
|
||||
Id types.Hash `json:"id"`
|
||||
PrevHash types.Hash `json:"prev_hash"`
|
||||
Diff uint64 `json:"diff,string"`
|
||||
TxCoinbase types.Hash `json:"tx_coinbase"`
|
||||
Lts uint64 `json:"lts,string"`
|
||||
MHash types.Hash `json:"mhash"`
|
||||
TxPriv crypto.PrivateKeyBytes `json:"tx_priv"`
|
||||
TxPub crypto.PublicKeyBytes `json:"tx_pub"`
|
||||
BlockFound string `json:"main_found,omitempty"`
|
||||
|
||||
Uncles []struct {
|
||||
Diff uint64 `json:"diff,string"`
|
||||
|
|
|
@ -43,8 +43,6 @@ func (m *Miner) MoneroAddress() *address.Address {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
type outputResult struct {
|
||||
Miner *Miner
|
||||
Output *transaction.Output
|
||||
|
@ -53,7 +51,10 @@ type outputResult struct {
|
|||
func MatchOutputs(c *transaction.CoinbaseTransaction, miners []*Miner, privateKey crypto.PrivateKey) (result []outputResult) {
|
||||
addresses := make(map[address.PackedAddress]*Miner, len(miners))
|
||||
|
||||
outputs := c.Outputs
|
||||
outputs := make([]*transaction.Output, len(c.Outputs))
|
||||
for i, o := range c.Outputs {
|
||||
outputs[i] = &o
|
||||
}
|
||||
|
||||
for _, m := range miners {
|
||||
addresses[*m.MoneroAddress().ToPackedAddress()] = m
|
||||
|
|
16
go.mod
16
go.mod
|
@ -4,7 +4,7 @@ go 1.19
|
|||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20221005074023-b6ca970f3050
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230305004001-d8d87587c8a8
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752
|
||||
|
@ -18,9 +18,9 @@ require (
|
|||
github.com/lib/pq v1.10.7
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tyler-sommer/stick v1.0.4
|
||||
golang.org/x/crypto v0.5.0
|
||||
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4
|
||||
golang.org/x/net v0.5.0
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017
|
||||
golang.org/x/net v0.8.0
|
||||
lukechampine.com/uint128 v1.2.0
|
||||
)
|
||||
|
||||
|
@ -30,8 +30,8 @@ require (
|
|||
github.com/go-zeromq/goczmq/v4 v4.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
32
go.sum
32
go.sum
|
@ -1,7 +1,7 @@
|
|||
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550 h1:Mqu6Q2e//30TWeP5bM9Th5KEzWdFAFd80Y2ZXN9fmeE=
|
||||
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20221005074023-b6ca970f3050 h1:OoSrxnnLqy/HIsZy/KcR52WHaJiB4RiTMaHlMfY/740=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20221005074023-b6ca970f3050/go.mod h1:FsZfI1sMcXZr55H0aKJ1LhMdPpW5YuyCubu5ilvQ0fM=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230305004001-d8d87587c8a8 h1:FFhYIN01+QhQ53VH1RA08nH318kFYEkBmEdz0iMm0kA=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230305004001-d8d87587c8a8/go.mod h1:E6nns07xjNbnF+3LKZAjQ9zwpJGLivti8WCKfm+/OII=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823 h1:HxIuImZsB15Ix59K5VznoHzZ1ut5hW0AAqiDJpOd2+g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48 h1:ExrYG0RSrx/I4McPWgUF4B8R2OkblMrMki2ia8vG6Bw=
|
||||
|
@ -54,10 +54,10 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4 h1:CNkDRtCj8otM5CFz5jYvbr8ioXX8flVsLfDWEj0M5kk=
|
||||
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017 h1:3Ea9SZLCB0aRIhSEjM+iaGIlzzeDJdpi579El/YIhEE=
|
||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
|
@ -68,14 +68,14 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -84,13 +84,13 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
|
@ -104,7 +104,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
|
|
|
@ -3,7 +3,7 @@ package block
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"errors"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
|
@ -294,21 +294,24 @@ func (b *Block) TxTreeHash() (rootHash types.Hash) {
|
|||
return
|
||||
}
|
||||
|
||||
func (b *Block) Difficulty() types.Difficulty {
|
||||
func (b *Block) Difficulty(f GetDifficultyByHeightFunc) types.Difficulty {
|
||||
//cached by sidechain.Share
|
||||
d, _ := client.GetDefaultClient().GetDifficultyByHeight(b.Coinbase.GenHeight)
|
||||
return d
|
||||
return f(b.Coinbase.GenHeight)
|
||||
}
|
||||
|
||||
func (b *Block) PowHash() types.Hash {
|
||||
func (b *Block) PowHash(f GetSeedByHeightFunc) types.Hash {
|
||||
//cached by sidechain.Share
|
||||
h, _ := HashBlob(b.Coinbase.GenHeight, b.HashingBlob())
|
||||
h, _ := b.PowHashWithError(f)
|
||||
return h
|
||||
}
|
||||
|
||||
func (b *Block) PowHashWithError() (types.Hash, error) {
|
||||
func (b *Block) PowHashWithError(f GetSeedByHeightFunc) (types.Hash, error) {
|
||||
//not cached
|
||||
return HashBlob(b.Coinbase.GenHeight, b.HashingBlob())
|
||||
if seed := f(b.Coinbase.GenHeight); seed == types.ZeroHash {
|
||||
return types.ZeroHash, errors.New("could not get seed")
|
||||
} else {
|
||||
return hasher.Hash(seed[:], b.HashingBlob())
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Block) Id() types.Hash {
|
||||
|
|
10
monero/block/func.go
Normal file
10
monero/block/func.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package block
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
)
|
||||
|
||||
type GetDifficultyByHeightFunc func(height uint64) types.Difficulty
|
||||
type GetSeedByHeightFunc func(height uint64) (hash types.Hash)
|
||||
type GetBlockHeaderByHashFunc func(hash types.Hash) *Header
|
||||
type GetBlockHeaderByHeightFunc func(height uint64) *Header
|
|
@ -1,91 +0,0 @@
|
|||
package block
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/floatdrop/lru"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func HashBlob(height uint64, blob []byte) (hash types.Hash, err error) {
|
||||
|
||||
if seed, err := client.GetDefaultClient().GetSeedByHeight(height); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
return hasher.Hash(seed[:], blob)
|
||||
}
|
||||
}
|
||||
|
||||
var blockHeaderByHash = lru.New[types.Hash, *Header](128)
|
||||
var blockHeaderByHashLock sync.Mutex
|
||||
|
||||
func GetBlockHeaderByHeight(height uint64) *Header {
|
||||
//TODO: cache
|
||||
if header, err := client.GetDefaultClient().GetBlockHeaderByHeight(height); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeader.PrevHash)
|
||||
h, _ := types.HashFromString(header.BlockHeader.Hash)
|
||||
return &Header{
|
||||
MajorVersion: uint8(header.BlockHeader.MajorVersion),
|
||||
MinorVersion: uint8(header.BlockHeader.MinorVersion),
|
||||
Timestamp: uint64(header.BlockHeader.Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.BlockHeader.Difficulty,
|
||||
Nonce: uint32(header.BlockHeader.Nonce),
|
||||
Reward: header.BlockHeader.Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(header.BlockHeader.Difficulty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetBlockHeaderByHash(hash types.Hash) *Header {
|
||||
blockHeaderByHashLock.Lock()
|
||||
defer blockHeaderByHashLock.Unlock()
|
||||
if h := blockHeaderByHash.Get(hash); h == nil {
|
||||
if header, err := client.GetDefaultClient().GetBlockHeaderByHash(hash); err != nil || len(header.BlockHeaders) != 1 {
|
||||
return nil
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeaders[0].PrevHash)
|
||||
blockHash, _ := types.HashFromString(header.BlockHeaders[0].Hash)
|
||||
blockHeader := &Header{
|
||||
MajorVersion: uint8(header.BlockHeaders[0].MajorVersion),
|
||||
MinorVersion: uint8(header.BlockHeaders[0].MinorVersion),
|
||||
Timestamp: uint64(header.BlockHeaders[0].Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.BlockHeaders[0].Height,
|
||||
Nonce: uint32(header.BlockHeaders[0].Nonce),
|
||||
Reward: header.BlockHeaders[0].Reward,
|
||||
Id: blockHash,
|
||||
Difficulty: types.DifficultyFrom64(header.BlockHeaders[0].Difficulty),
|
||||
}
|
||||
|
||||
blockHeaderByHash.Set(hash, blockHeader)
|
||||
|
||||
return blockHeader
|
||||
}
|
||||
} else {
|
||||
return *h
|
||||
}
|
||||
}
|
||||
|
||||
func GetLastBlockHeader() *Header {
|
||||
if header, err := client.GetDefaultClient().GetLastBlockHeader(); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeader.PrevHash)
|
||||
h, _ := types.HashFromString(header.BlockHeader.Hash)
|
||||
return &Header{
|
||||
MajorVersion: uint8(header.BlockHeader.MajorVersion),
|
||||
MinorVersion: uint8(header.BlockHeader.MinorVersion),
|
||||
Timestamp: uint64(header.BlockHeader.Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.BlockHeader.Difficulty,
|
||||
Nonce: uint32(header.BlockHeader.Nonce),
|
||||
Reward: header.BlockHeader.Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(header.BlockHeader.Difficulty),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ import (
|
|||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"github.com/floatdrop/lru"
|
||||
|
@ -54,15 +53,11 @@ func GetDefaultClient() *Client {
|
|||
}
|
||||
}
|
||||
|
||||
// Client TODO: ratelimit
|
||||
// Client
|
||||
type Client struct {
|
||||
c *rpc.Client
|
||||
d *daemon.Client
|
||||
|
||||
difficultyCache *lru.LRU[uint64, types.Difficulty]
|
||||
|
||||
seedCache *lru.LRU[uint64, types.Hash]
|
||||
|
||||
coinbaseTransactionCache *lru.LRU[types.Hash, *transaction.CoinbaseTransaction]
|
||||
|
||||
throttler <-chan time.Time
|
||||
|
@ -76,10 +71,8 @@ func NewClient(address string) (*Client, error) {
|
|||
return &Client{
|
||||
c: c,
|
||||
d: daemon.NewClient(c),
|
||||
difficultyCache: lru.New[uint64, types.Difficulty](1024),
|
||||
seedCache: lru.New[uint64, types.Hash](1024),
|
||||
coinbaseTransactionCache: lru.New[types.Hash, *transaction.CoinbaseTransaction](1024),
|
||||
throttler: time.Tick(time.Second / 2),
|
||||
throttler: time.Tick(time.Second / 8),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -252,63 +245,27 @@ func (c *Client) GetOuts(inputs ...uint64) ([]Output, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockIdByHeight(height uint64) (types.Hash, error) {
|
||||
if r, err := c.GetBlockHeaderByHeight(height); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
if h, err := types.HashFromString(r.BlockHeader.Hash); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
return h, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) AddSeedByHeightToCache(seedHeight uint64, seed types.Hash) {
|
||||
c.seedCache.Set(seedHeight, seed)
|
||||
}
|
||||
|
||||
func (c *Client) GetSeedByHeight(height uint64) (types.Hash, error) {
|
||||
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
|
||||
if seed := c.seedCache.Get(seedHeight); seed == nil {
|
||||
if seed, err := c.GetBlockIdByHeight(seedHeight); err != nil {
|
||||
return types.ZeroHash, err
|
||||
} else {
|
||||
c.AddSeedByHeightToCache(seedHeight, seed)
|
||||
return seed, nil
|
||||
}
|
||||
} else {
|
||||
return *seed, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetDifficultyByHeight(height uint64) (types.Difficulty, error) {
|
||||
if difficulty := c.difficultyCache.Get(height); difficulty == nil {
|
||||
if header, err := c.GetBlockHeaderByHeight(height); err != nil {
|
||||
if template, err := c.GetBlockTemplate(types.DonationAddress); err != nil {
|
||||
return types.ZeroDifficulty, err
|
||||
} else if uint64(template.Height) == height {
|
||||
difficulty := types.DifficultyFrom64(uint64(template.Difficulty))
|
||||
c.difficultyCache.Set(height, difficulty)
|
||||
return difficulty, nil
|
||||
} else {
|
||||
return types.ZeroDifficulty, errors.New("height not found and is not next template")
|
||||
}
|
||||
} else {
|
||||
difficulty := types.DifficultyFrom64(uint64(header.BlockHeader.Difficulty))
|
||||
c.difficultyCache.Set(height, difficulty)
|
||||
return difficulty, nil
|
||||
}
|
||||
} else {
|
||||
return *difficulty, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockHeaderByHash(hash types.Hash) (*daemon.GetBlockHeaderByHashResult, error) {
|
||||
func (c *Client) GetVersion() (*daemon.GetVersionResult, error) {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetBlockHeaderByHash(context.Background(), []string{hash.String()}); err != nil {
|
||||
if result, err := c.d.GetVersion(context.Background()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetInfo() (*daemon.GetInfoResult, error) {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetInfo(context.Background()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockHeaderByHash(hash types.Hash, ctx context.Context) (*daemon.GetBlockHeaderByHashResult, error) {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetBlockHeaderByHash(ctx, []string{hash.String()}); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
|
@ -324,9 +281,43 @@ func (c *Client) GetLastBlockHeader() (*daemon.GetLastBlockHeaderResult, error)
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockHeaderByHeight(height uint64) (*daemon.GetBlockHeaderByHeightResult, error) {
|
||||
func (c *Client) GetBlockHeaderByHeight(height uint64, ctx context.Context) (*daemon.GetBlockHeaderByHeightResult, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-c.throttler:
|
||||
if result, err := c.d.GetBlockHeaderByHeight(ctx, height); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockHeadersRangeResult(start, end uint64, ctx context.Context) (*daemon.GetBlockHeadersRangeResult, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-c.throttler:
|
||||
if result, err := c.d.GetBlockHeadersRange(ctx, start, end); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SubmitBlock(blob []byte) (*daemon.SubmitBlockResult, error) {
|
||||
if result, err := c.d.SubmitBlock(context.Background(), blob); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetMinerData() (*daemon.GetMinerDataResult, error) {
|
||||
<-c.throttler
|
||||
if result, err := c.d.GetBlockHeaderByHeight(context.Background(), height); err != nil {
|
||||
if result, err := c.d.GetMinerData(context.Background()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
|
|
|
@ -41,9 +41,13 @@ type FullChainMain struct {
|
|||
} `json:"inputs"`
|
||||
Outputs []struct {
|
||||
Amount uint64 `json:"amount"`
|
||||
ToKey struct {
|
||||
ToKey *struct {
|
||||
Key crypto.PublicKeyBytes `json:"key"`
|
||||
} `json:"to_key"`
|
||||
ToTaggedKey *struct {
|
||||
Key crypto.PublicKeyBytes `json:"key"`
|
||||
ViewTag string `json:"view_tag"`
|
||||
} `json:"to_tagged_key"`
|
||||
} `json:"outputs"`
|
||||
Extra string `json:"extra"`
|
||||
Signatures []interface{} `json:"signatures"`
|
||||
|
|
444
p2pool/mainchain/mainchain.go
Normal file
444
p2pool/mainchain/mainchain.go
Normal file
|
@ -0,0 +1,444 @@
|
|||
package mainchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
mainblock "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/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"golang.org/x/exp/slices"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const TimestampWindow = 60
|
||||
const BlockHeadersRequired = 720
|
||||
|
||||
type MainChain struct {
|
||||
p2pool P2PoolInterface
|
||||
lock sync.RWMutex
|
||||
sidechain *sidechain.SideChain
|
||||
|
||||
highest uint64
|
||||
mainchainByHeight map[uint64]*sidechain.ChainMain
|
||||
mainchainByHash map[types.Hash]*sidechain.ChainMain
|
||||
|
||||
tip atomic.Pointer[sidechain.ChainMain]
|
||||
tipMinerData atomic.Pointer[MinerData]
|
||||
|
||||
medianTimestamp atomic.Uint64
|
||||
}
|
||||
|
||||
type P2PoolInterface interface {
|
||||
ClientRPC() *client.Client
|
||||
ClientZMQ() *zmq.Client
|
||||
Context() context.Context
|
||||
Started() bool
|
||||
UpdateBlockFound(data *sidechain.ChainMain, block *sidechain.PoolBlock)
|
||||
}
|
||||
|
||||
func NewMainChain(s *sidechain.SideChain, p2pool P2PoolInterface) *MainChain {
|
||||
m := &MainChain{
|
||||
sidechain: s,
|
||||
p2pool: p2pool,
|
||||
mainchainByHeight: make(map[uint64]*sidechain.ChainMain),
|
||||
mainchainByHash: make(map[types.Hash]*sidechain.ChainMain),
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *MainChain) Listen() error {
|
||||
ctx := c.p2pool.Context()
|
||||
s, err := c.p2pool.ClientZMQ().Listen(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-s.ErrC:
|
||||
//TODO: retry connection
|
||||
return err
|
||||
case fullChainMain := <-s.FullChainMainC:
|
||||
log.Print(fullChainMain)
|
||||
case fullMinerData := <-s.FullMinerDataC:
|
||||
log.Print(fullMinerData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MainChain) getTimestamps(timestamps []uint64) bool {
|
||||
_ = timestamps[TimestampWindow-1]
|
||||
if len(c.mainchainByHeight) <= TimestampWindow {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < TimestampWindow; i++ {
|
||||
h, ok := c.mainchainByHeight[c.highest-uint64(i)]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
timestamps[i] = h.Timestamp
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *MainChain) updateMedianTimestamp() {
|
||||
var timestamps [TimestampWindow]uint64
|
||||
if !c.getTimestamps(timestamps[:]) {
|
||||
c.medianTimestamp.Store(0)
|
||||
return
|
||||
}
|
||||
|
||||
slices.Sort(timestamps[:])
|
||||
|
||||
// Shift it +1 block compared to Monero's code because we don't have the latest block yet when we receive new miner data
|
||||
ts := (timestamps[TimestampWindow/2] + timestamps[TimestampWindow/2+1]) / 2
|
||||
log.Printf("[MainChain] Median timestamp updated to %d", ts)
|
||||
c.medianTimestamp.Store(ts)
|
||||
}
|
||||
|
||||
func (c *MainChain) HandleMainHeader(mainHeader *mainblock.Header) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
mainData := &sidechain.ChainMain{
|
||||
Difficulty: mainHeader.Difficulty,
|
||||
Height: mainHeader.Height,
|
||||
Timestamp: mainHeader.Timestamp,
|
||||
Reward: mainHeader.Reward,
|
||||
Id: mainHeader.Id,
|
||||
}
|
||||
c.mainchainByHeight[mainHeader.Height] = mainData
|
||||
c.mainchainByHash[mainHeader.Id] = mainData
|
||||
|
||||
if mainData.Height > c.highest {
|
||||
c.highest = mainData.Height
|
||||
}
|
||||
|
||||
log.Printf("[MainChain] new main chain block: height = %d, id = %s, timestamp = %d, reward = %s", mainData.Height, mainData.Id.String(), mainData.Timestamp, utils.XMRUnits(mainData.Reward))
|
||||
|
||||
c.updateMedianTimestamp()
|
||||
}
|
||||
|
||||
func (c *MainChain) HandleMainBlock(b *mainblock.Block) {
|
||||
mainData := &sidechain.ChainMain{
|
||||
Difficulty: types.ZeroDifficulty,
|
||||
Height: b.Coinbase.GenHeight,
|
||||
Timestamp: b.Timestamp,
|
||||
Reward: b.Coinbase.TotalReward,
|
||||
Id: b.Id(),
|
||||
}
|
||||
|
||||
func() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if h, ok := c.mainchainByHeight[mainData.Height]; ok {
|
||||
mainData.Difficulty = h.Difficulty
|
||||
} else {
|
||||
return
|
||||
}
|
||||
c.mainchainByHash[mainData.Id] = mainData
|
||||
c.mainchainByHeight[mainData.Height] = mainData
|
||||
|
||||
if mainData.Height > c.highest {
|
||||
c.highest = mainData.Height
|
||||
}
|
||||
|
||||
log.Printf("[MainChain] new main chain block: height = %d, id = %s, timestamp = %d, reward = %s", mainData.Height, mainData.Id.String(), mainData.Timestamp, utils.XMRUnits(mainData.Reward))
|
||||
|
||||
c.updateMedianTimestamp()
|
||||
}()
|
||||
|
||||
extraMergeMiningTag := b.Coinbase.Extra.GetTag(transaction.TxExtraTagMergeMining)
|
||||
if extraMergeMiningTag == nil {
|
||||
return
|
||||
}
|
||||
sidechainHashData := extraMergeMiningTag.Data
|
||||
if len(sidechainHashData) != types.HashSize {
|
||||
return
|
||||
}
|
||||
|
||||
sidechainId := types.HashFromBytes(sidechainHashData)
|
||||
|
||||
if block := c.sidechain.GetPoolBlockByTemplateId(sidechainId); block != nil {
|
||||
c.p2pool.UpdateBlockFound(mainData, block)
|
||||
} else {
|
||||
c.sidechain.WatchMainChainBlock(mainData, sidechainId)
|
||||
}
|
||||
|
||||
c.updateTip()
|
||||
}
|
||||
|
||||
// HandleChainMain
|
||||
// deprecated
|
||||
func (c *MainChain) HandleChainMain(mainData *sidechain.ChainMain, extra []byte) {
|
||||
func() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if h, ok := c.mainchainByHeight[mainData.Height]; ok {
|
||||
h.Height = mainData.Height
|
||||
h.Timestamp = mainData.Timestamp
|
||||
h.Reward = mainData.Reward
|
||||
mainData.Id = h.Id
|
||||
mainData.Difficulty = h.Difficulty
|
||||
c.mainchainByHash[h.Id] = h
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if mainData.Height > c.highest {
|
||||
c.highest = mainData.Height
|
||||
}
|
||||
|
||||
log.Printf("[MainChain] new main chain block: height = %d, id = %s, timestamp = %d, reward = %s", mainData.Height, mainData.Id.String(), mainData.Timestamp, utils.XMRUnits(mainData.Reward))
|
||||
|
||||
c.updateMedianTimestamp()
|
||||
}()
|
||||
|
||||
var tags transaction.ExtraTags
|
||||
if err := tags.UnmarshalBinary(extra); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
extraMergeMiningTag := tags.GetTag(transaction.TxExtraTagMergeMining)
|
||||
if extraMergeMiningTag == nil {
|
||||
return
|
||||
}
|
||||
sidechainHashData := extraMergeMiningTag.Data
|
||||
if len(sidechainHashData) != types.HashSize {
|
||||
return
|
||||
}
|
||||
|
||||
sidechainId := types.HashFromBytes(sidechainHashData)
|
||||
|
||||
if block := c.sidechain.GetPoolBlockByTemplateId(sidechainId); block != nil {
|
||||
c.p2pool.UpdateBlockFound(mainData, block)
|
||||
} else {
|
||||
c.sidechain.WatchMainChainBlock(mainData, sidechainId)
|
||||
}
|
||||
|
||||
c.updateTip()
|
||||
}
|
||||
|
||||
func (c *MainChain) GetChainMainByHeight(height uint64) *sidechain.ChainMain {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.mainchainByHeight[height]
|
||||
}
|
||||
|
||||
func (c *MainChain) GetChainMainByHash(hash types.Hash) *sidechain.ChainMain {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.mainchainByHash[hash]
|
||||
}
|
||||
|
||||
func (c *MainChain) GetChainMainTip() *sidechain.ChainMain {
|
||||
return c.tip.Load()
|
||||
}
|
||||
|
||||
func (c *MainChain) updateTip() {
|
||||
if minerData := c.tipMinerData.Load(); minerData != nil {
|
||||
if d := c.GetChainMainByHash(minerData.PrevId); d != nil {
|
||||
c.tip.Store(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MainChain) Cleanup() {
|
||||
if tip := c.GetChainMainTip(); tip != nil {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.cleanup(tip.Height)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MainChain) cleanup(height uint64) {
|
||||
// Expects m_mainchainLock to be already locked here
|
||||
// Deletes everything older than 720 blocks, except for the 3 latest RandomX seed heights
|
||||
|
||||
const PruneDistance = BlockHeadersRequired
|
||||
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
|
||||
seedHeights := []uint64{seedHeight, seedHeight - randomx.SeedHashEpochBlocks, seedHeight - randomx.SeedHashEpochBlocks*2}
|
||||
|
||||
for h, m := range c.mainchainByHeight {
|
||||
if (h + PruneDistance) >= height {
|
||||
continue
|
||||
}
|
||||
|
||||
if !slices.Contains(seedHeights, h) {
|
||||
delete(c.mainchainByHash, m.Id)
|
||||
delete(c.mainchainByHeight, h)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *MainChain) DownloadBlockHeaders(currentHeight uint64) error {
|
||||
seedHeight := randomx.SeedHeight(currentHeight)
|
||||
|
||||
var prevSeedHeight uint64
|
||||
|
||||
if seedHeight > randomx.SeedHashEpochBlocks {
|
||||
prevSeedHeight = seedHeight - randomx.SeedHashEpochBlocks
|
||||
}
|
||||
|
||||
// First download 2 RandomX seeds
|
||||
|
||||
for _, h := range []uint64{prevSeedHeight, seedHeight} {
|
||||
if err := c.getBlockHeader(h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var startHeight uint64
|
||||
if currentHeight > BlockHeadersRequired {
|
||||
startHeight = currentHeight - BlockHeadersRequired
|
||||
}
|
||||
|
||||
if rangeResult, err := c.p2pool.ClientRPC().GetBlockHeadersRangeResult(startHeight, currentHeight-1, c.p2pool.Context()); err != nil {
|
||||
return fmt.Errorf("couldn't download block headers range for height %d to %d: %s", startHeight, currentHeight-1, err)
|
||||
} else {
|
||||
for _, header := range rangeResult.Headers {
|
||||
prevHash, _ := types.HashFromString(header.PrevHash)
|
||||
h, _ := types.HashFromString(header.Hash)
|
||||
c.HandleMainHeader(&mainblock.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),
|
||||
})
|
||||
}
|
||||
log.Printf("[MainChain] Downloaded headers for range %d to %d", startHeight, currentHeight-1)
|
||||
}
|
||||
|
||||
c.updateMedianTimestamp()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MainChain) HandleMinerData(minerData *MinerData) {
|
||||
var missingHeights []uint64
|
||||
func() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
mainData := &sidechain.ChainMain{
|
||||
Difficulty: minerData.Difficulty,
|
||||
Height: minerData.Height,
|
||||
}
|
||||
|
||||
if existingMainData, ok := c.mainchainByHeight[mainData.Height]; !ok {
|
||||
c.mainchainByHeight[mainData.Height] = mainData
|
||||
} else {
|
||||
existingMainData.Difficulty = mainData.Difficulty
|
||||
mainData = existingMainData
|
||||
}
|
||||
|
||||
prevMainData := &sidechain.ChainMain{
|
||||
Height: minerData.Height - 1,
|
||||
Id: minerData.PrevId,
|
||||
}
|
||||
|
||||
if existingPrevMainData, ok := c.mainchainByHeight[prevMainData.Height]; !ok {
|
||||
c.mainchainByHeight[prevMainData.Height] = prevMainData
|
||||
} else {
|
||||
existingPrevMainData.Id = prevMainData.Id
|
||||
|
||||
// timestamp and reward is unknown here
|
||||
existingPrevMainData.Timestamp = 0
|
||||
existingPrevMainData.Reward = 0
|
||||
|
||||
prevMainData = existingPrevMainData
|
||||
}
|
||||
|
||||
c.mainchainByHash[prevMainData.Id] = prevMainData
|
||||
|
||||
c.cleanup(minerData.Height)
|
||||
|
||||
minerData.TimeReceived = time.Now()
|
||||
c.tipMinerData.Store(minerData)
|
||||
|
||||
c.updateMedianTimestamp()
|
||||
|
||||
log.Printf("[MainChain] new miner data: major_version = %d, height = %d, prev_id = %s, seed_hash = %s, difficulty = %s", minerData.MajorVersion, minerData.Height, minerData.PrevId.String(), minerData.SeedHash.String(), minerData.Difficulty.StringNumeric())
|
||||
|
||||
if c.p2pool.Started() {
|
||||
for h := minerData.Height; h > 0 && (h+BlockHeadersRequired) > minerData.Height; h-- {
|
||||
if d, ok := c.mainchainByHeight[h]; !ok || d.Difficulty.Equals(types.ZeroDifficulty) {
|
||||
log.Printf("[MainChain] Main chain data for height = %d is missing, requesting from monerod again", h)
|
||||
missingHeights = append(missingHeights, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, h := range missingHeights {
|
||||
wg.Add(1)
|
||||
go func(height uint64) {
|
||||
wg.Done()
|
||||
if err := c.getBlockHeader(height); err != nil {
|
||||
log.Printf("[MainChain] %s", err)
|
||||
}
|
||||
}(h)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
c.updateTip()
|
||||
|
||||
}
|
||||
|
||||
func (c *MainChain) getBlockHeader(height uint64) error {
|
||||
if header, err := c.p2pool.ClientRPC().GetBlockHeaderByHeight(height, c.p2pool.Context()); err != nil {
|
||||
return fmt.Errorf("couldn't download block header for height %d: %s", height, err)
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeader.PrevHash)
|
||||
h, _ := types.HashFromString(header.BlockHeader.Hash)
|
||||
c.HandleMainHeader(&mainblock.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 nil
|
||||
}
|
||||
|
||||
type MinerData struct {
|
||||
MajorVersion uint8
|
||||
Height uint64
|
||||
PrevId types.Hash
|
||||
SeedHash types.Hash
|
||||
Difficulty types.Difficulty
|
||||
MedianWeight uint64
|
||||
AlreadyGeneratedCoins uint64
|
||||
MedianTimestamp uint64
|
||||
//TxBacklog any
|
||||
TimeReceived time.Time
|
||||
}
|
|
@ -204,6 +204,7 @@ func (c *Client) SendPeerListResponse(list []netip.AddrPort) {
|
|||
buf := make([]byte, 0, 1+len(list)*(1+16+2))
|
||||
buf = append(buf, byte(len(list)))
|
||||
for i := range list {
|
||||
//TODO: check ipv4 gets sent properly
|
||||
if list[i].Addr().Is6() && !IsPeerVersionInformation(list[i]) {
|
||||
buf = append(buf, 1)
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
@ -42,9 +43,11 @@ type Server struct {
|
|||
|
||||
clientsLock sync.RWMutex
|
||||
clients []*Client
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewServer(sidechain *sidechain.SideChain, listenAddress string, externalListenPort uint16, maxOutgoingPeers, maxIncomingPeers uint32) (*Server, error) {
|
||||
func NewServer(sidechain *sidechain.SideChain, listenAddress string, externalListenPort uint16, maxOutgoingPeers, maxIncomingPeers uint32, ctx context.Context) (*Server, error) {
|
||||
peerId := make([]byte, int(unsafe.Sizeof(uint64(0))))
|
||||
_, err := rand.Read(peerId)
|
||||
if err != nil {
|
||||
|
@ -64,6 +67,7 @@ func NewServer(sidechain *sidechain.SideChain, listenAddress string, externalLis
|
|||
MaxOutgoingPeers: utils.Min(utils.Max(maxOutgoingPeers, 10), 450),
|
||||
MaxIncomingPeers: utils.Min(utils.Max(maxIncomingPeers, 10), 450),
|
||||
versionInformation: PeerVersionInformation{SoftwareId: SoftwareIdGoObserver, SoftwareVersion: CurrentSoftwareVersion, Protocol: SupportedProtocolVersion},
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
s.PendingOutgoingConnections = utils.NewCircularBuffer[string](int(s.MaxOutgoingPeers))
|
||||
|
@ -161,7 +165,7 @@ func (s *Server) DownloadMissingBlocks() {
|
|||
func (s *Server) Listen() (err error) {
|
||||
var listener net.Listener
|
||||
var ok bool
|
||||
if listener, err = net.Listen("tcp", s.listenAddress.String()); err != nil {
|
||||
if listener, err = (&net.ListenConfig{}).Listen(s.ctx, "tcp", s.listenAddress.String()); err != nil {
|
||||
return err
|
||||
} else if s.listener, ok = listener.(*net.TCPListener); !ok {
|
||||
return errors.New("not a tcp listener")
|
||||
|
@ -258,7 +262,7 @@ func (s *Server) Connect(addrPort netip.AddrPort) error {
|
|||
|
||||
s.NumOutgoingConnections.Add(1)
|
||||
|
||||
if conn, err := net.DialTimeout("tcp", addrPort.String(), time.Second*5); err != nil {
|
||||
if conn, err := (&net.Dialer{Timeout: time.Second * 5}).DialContext(s.ctx, "tcp", addrPort.String()); err != nil {
|
||||
s.NumOutgoingConnections.Add(-1)
|
||||
return err
|
||||
} else if tcpConn, ok := conn.(*net.TCPConn); !ok {
|
||||
|
|
400
p2pool/p2pool.go
400
p2pool/p2pool.go
|
@ -1,17 +1,38 @@
|
|||
package p2pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/mainchain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/p2p"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"log"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type P2Pool struct {
|
||||
consensus *sidechain.Consensus
|
||||
sidechain *sidechain.SideChain
|
||||
mainchain *mainchain.MainChain
|
||||
server *p2p.Server
|
||||
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
|
||||
rpcClient *client.Client
|
||||
zmqClient *zmq.Client
|
||||
|
||||
started atomic.Bool
|
||||
}
|
||||
|
||||
func (p *P2Pool) GetBlob(key []byte) (blob []byte, err error) {
|
||||
|
@ -26,16 +47,35 @@ func (p *P2Pool) RemoveBlob(key []byte) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewP2Pool(consensus *sidechain.Consensus, settings map[string]string) *P2Pool {
|
||||
func (p *P2Pool) Close() {
|
||||
p.ctxCancel()
|
||||
_ = p.zmqClient.Close()
|
||||
}
|
||||
|
||||
func NewP2Pool(consensus *sidechain.Consensus, settings map[string]string) (*P2Pool, error) {
|
||||
pool := &P2Pool{
|
||||
consensus: consensus,
|
||||
}
|
||||
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)
|
||||
|
||||
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 addr, ok := settings["listen"]; ok {
|
||||
listenAddress = addr
|
||||
}
|
||||
|
@ -55,21 +95,373 @@ func NewP2Pool(consensus *sidechain.Consensus, settings map[string]string) *P2Po
|
|||
externalListenPort, _ = strconv.ParseUint(externalPort, 10, 0)
|
||||
}
|
||||
|
||||
if pool.server, err = p2p.NewServer(pool.sidechain, listenAddress, uint16(externalListenPort), uint32(maxOutgoingPeers), uint32(maxIncomingPeers)); err != nil {
|
||||
return nil
|
||||
if pool.server, err = p2p.NewServer(pool.sidechain, listenAddress, uint16(externalListenPort), uint32(maxOutgoingPeers), uint32(maxIncomingPeers), pool.ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pool
|
||||
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()
|
||||
}
|
||||
|
||||
// GetMinimalBlockHeaderByHeight Only Id / Height / Timestamp are assured
|
||||
func (p *P2Pool) GetMinimalBlockHeaderByHeight(height uint64) *block.Header {
|
||||
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,
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
func (p *P2Pool) GetDifficultyByHeight(height uint64) types.Difficulty {
|
||||
if chainMain := p.mainchain.GetChainMainByHeight(height); chainMain != nil && chainMain.Difficulty != types.ZeroDifficulty {
|
||||
return chainMain.Difficulty
|
||||
} else {
|
||||
//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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if zmqStream, err := p.ClientZMQ().Listen(p.ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-zmqStream.ErrC:
|
||||
p.Close()
|
||||
return
|
||||
case fullChainMain := <-zmqStream.FullChainMainC:
|
||||
if len(fullChainMain.MinerTx.Inputs) < 1 {
|
||||
continue
|
||||
}
|
||||
d := &sidechain.ChainMain{
|
||||
Difficulty: types.ZeroDifficulty,
|
||||
Height: fullChainMain.MinerTx.Inputs[0].Gen.Height,
|
||||
Timestamp: uint64(fullChainMain.Timestamp),
|
||||
Reward: 0,
|
||||
Id: types.ZeroHash,
|
||||
}
|
||||
for _, o := range fullChainMain.MinerTx.Outputs {
|
||||
d.Reward += o.Amount
|
||||
}
|
||||
|
||||
outputs := make(transaction.Outputs, 0, len(fullChainMain.MinerTx.Outputs))
|
||||
var totalReward uint64
|
||||
for i, o := range fullChainMain.MinerTx.Outputs {
|
||||
if o.ToKey != nil {
|
||||
outputs = append(outputs, transaction.Output{
|
||||
Index: uint64(i),
|
||||
Reward: o.Amount,
|
||||
Type: transaction.TxOutToKey,
|
||||
EphemeralPublicKey: o.ToKey.Key,
|
||||
ViewTag: 0,
|
||||
})
|
||||
} else if o.ToTaggedKey != nil {
|
||||
tk, _ := hex.DecodeString(o.ToTaggedKey.ViewTag)
|
||||
outputs = append(outputs, transaction.Output{
|
||||
Index: uint64(i),
|
||||
Reward: o.Amount,
|
||||
Type: transaction.TxOutToTaggedKey,
|
||||
EphemeralPublicKey: o.ToTaggedKey.Key,
|
||||
ViewTag: tk[0],
|
||||
})
|
||||
} else {
|
||||
//error
|
||||
break
|
||||
}
|
||||
totalReward += o.Amount
|
||||
}
|
||||
|
||||
if len(outputs) != len(fullChainMain.MinerTx.Outputs) {
|
||||
continue
|
||||
}
|
||||
|
||||
extraDataRaw, _ := hex.DecodeString(fullChainMain.MinerTx.Extra)
|
||||
extraTags := transaction.ExtraTags{}
|
||||
if err = extraTags.UnmarshalBinary(extraDataRaw); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
blockData := &block.Block{
|
||||
MajorVersion: uint8(fullChainMain.MajorVersion),
|
||||
MinorVersion: uint8(fullChainMain.MinorVersion),
|
||||
Timestamp: uint64(fullChainMain.Timestamp),
|
||||
PreviousId: fullChainMain.PrevID,
|
||||
Nonce: uint32(fullChainMain.Nonce),
|
||||
Coinbase: &transaction.CoinbaseTransaction{
|
||||
Version: uint8(fullChainMain.MinerTx.Version),
|
||||
UnlockTime: uint64(fullChainMain.MinerTx.UnlockTime),
|
||||
InputCount: uint8(len(fullChainMain.MinerTx.Inputs)),
|
||||
InputType: transaction.TxInGen,
|
||||
GenHeight: fullChainMain.MinerTx.Inputs[0].Gen.Height,
|
||||
Outputs: outputs,
|
||||
OutputsBlobSize: 0,
|
||||
TotalReward: totalReward,
|
||||
Extra: extraTags,
|
||||
ExtraBaseRCT: 0,
|
||||
},
|
||||
Transactions: fullChainMain.TxHashes,
|
||||
TransactionParentIndices: nil,
|
||||
}
|
||||
p.mainchain.HandleMainBlock(blockData)
|
||||
case fullMinerData := <-zmqStream.FullMinerDataC:
|
||||
p.mainchain.HandleMinerData(&mainchain.MinerData{
|
||||
MajorVersion: fullMinerData.MajorVersion,
|
||||
Height: fullMinerData.Height,
|
||||
PrevId: fullMinerData.PrevId,
|
||||
SeedHash: fullMinerData.SeedHash,
|
||||
Difficulty: fullMinerData.Difficulty,
|
||||
MedianWeight: fullMinerData.MedianWeight,
|
||||
AlreadyGeneratedCoins: fullMinerData.AlreadyGeneratedCoins,
|
||||
MedianTimestamp: fullMinerData.MedianTimestamp,
|
||||
TimeReceived: time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
p.mainchain.HandleMinerData(&mainchain.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(),
|
||||
})
|
||||
|
||||
return p.mainchain.DownloadBlockHeaders(minerData.Height)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) Consensus() *sidechain.Consensus {
|
||||
return p.consensus
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *P2Pool) Started() bool {
|
||||
return p.started.Load()
|
||||
}
|
||||
|
||||
func (p *P2Pool) Broadcast(block *sidechain.PoolBlock) {
|
||||
p.server.Broadcast(block)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package sidechain
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
|
||||
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
|
@ -98,99 +99,120 @@ func NewConsensus(networkType NetworkType, poolName, poolPassword string, target
|
|||
UnclePenalty: unclePenalty,
|
||||
}
|
||||
|
||||
if len(c.PoolName) > 128 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(c.PoolPassword) > 128 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.TargetBlockTime < 1 || c.TargetBlockTime > monero.BlockTime {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.MinimumDifficulty < SmallestMinimumDifficulty || c.MinimumDifficulty > LargestMinimumDifficulty {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.ChainWindowSize < 60 || c.ChainWindowSize > 2160 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.UnclePenalty < 1 || c.UnclePenalty > 99 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var emptyHash types.Hash
|
||||
c.id = c.CalculateId()
|
||||
if c.id == emptyHash {
|
||||
if !c.verify() {
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (i *Consensus) CalculateSideTemplateId(main *mainblock.Block, side *SideData) types.Hash {
|
||||
func NewConsensusFromJSON(data []byte) (*Consensus, error) {
|
||||
var c Consensus
|
||||
if err := json.Unmarshal(data, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !c.verify() {
|
||||
return nil, errors.New("could not verify")
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (c *Consensus) verify() bool {
|
||||
if len(c.PoolName) > 128 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(c.PoolPassword) > 128 {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.TargetBlockTime < 1 || c.TargetBlockTime > monero.BlockTime {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.MinimumDifficulty < SmallestMinimumDifficulty || c.MinimumDifficulty > LargestMinimumDifficulty {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.ChainWindowSize < 60 || c.ChainWindowSize > 2160 {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.UnclePenalty < 1 || c.UnclePenalty > 99 {
|
||||
return false
|
||||
}
|
||||
|
||||
var emptyHash types.Hash
|
||||
c.id = c.CalculateId()
|
||||
if c.id == emptyHash {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Consensus) CalculateSideTemplateId(main *mainblock.Block, side *SideData) types.Hash {
|
||||
|
||||
mainData, _ := main.SideChainHashingBlob()
|
||||
sideData, _ := side.MarshalBinary()
|
||||
|
||||
return i.CalculateSideChainIdFromBlobs(mainData, sideData)
|
||||
return c.CalculateSideChainIdFromBlobs(mainData, sideData)
|
||||
}
|
||||
|
||||
func (i *Consensus) CalculateSideChainIdFromBlobs(mainBlob, sideBlob []byte) types.Hash {
|
||||
return crypto.PooledKeccak256(mainBlob, sideBlob, i.id[:])
|
||||
func (c *Consensus) CalculateSideChainIdFromBlobs(mainBlob, sideBlob []byte) types.Hash {
|
||||
return crypto.PooledKeccak256(mainBlob, sideBlob, c.id[:])
|
||||
}
|
||||
|
||||
func (i *Consensus) Id() types.Hash {
|
||||
func (c *Consensus) Id() types.Hash {
|
||||
var h types.Hash
|
||||
if i.id == h {
|
||||
if c.id == h {
|
||||
//this data race is fine
|
||||
i.id = i.CalculateId()
|
||||
return i.id
|
||||
c.id = c.CalculateId()
|
||||
return c.id
|
||||
}
|
||||
return i.id
|
||||
return c.id
|
||||
}
|
||||
|
||||
func (i *Consensus) IsDefault() bool {
|
||||
return i.id == ConsensusDefault.id
|
||||
func (c *Consensus) IsDefault() bool {
|
||||
return c.id == ConsensusDefault.id
|
||||
}
|
||||
|
||||
func (i *Consensus) IsMini() bool {
|
||||
return i.id == ConsensusMini.id
|
||||
func (c *Consensus) IsMini() bool {
|
||||
return c.id == ConsensusMini.id
|
||||
}
|
||||
|
||||
func (i *Consensus) DefaultPort() uint16 {
|
||||
if i.IsMini() {
|
||||
func (c *Consensus) DefaultPort() uint16 {
|
||||
if c.IsMini() {
|
||||
return 37888
|
||||
}
|
||||
return 37889
|
||||
}
|
||||
|
||||
func (i *Consensus) SeedNode() string {
|
||||
if i.IsMini() {
|
||||
func (c *Consensus) SeedNode() string {
|
||||
if c.IsMini() {
|
||||
return "seeds-mini.p2pool.io"
|
||||
} else if i.IsDefault() {
|
||||
} else if c.IsDefault() {
|
||||
return "seeds.p2pool.io"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (i *Consensus) CalculateId() types.Hash {
|
||||
func (c *Consensus) CalculateId() types.Hash {
|
||||
var buf []byte
|
||||
buf = append(buf, i.NetworkType.String()...)
|
||||
buf = append(buf, c.NetworkType.String()...)
|
||||
buf = append(buf, 0)
|
||||
buf = append(buf, i.PoolName...)
|
||||
buf = append(buf, c.PoolName...)
|
||||
buf = append(buf, 0)
|
||||
buf = append(buf, i.PoolPassword...)
|
||||
buf = append(buf, c.PoolPassword...)
|
||||
buf = append(buf, 0)
|
||||
buf = append(buf, strconv.FormatUint(i.TargetBlockTime, 10)...)
|
||||
buf = append(buf, strconv.FormatUint(c.TargetBlockTime, 10)...)
|
||||
buf = append(buf, 0)
|
||||
buf = append(buf, strconv.FormatUint(i.MinimumDifficulty, 10)...)
|
||||
buf = append(buf, strconv.FormatUint(c.MinimumDifficulty, 10)...)
|
||||
buf = append(buf, 0)
|
||||
buf = append(buf, strconv.FormatUint(i.ChainWindowSize, 10)...)
|
||||
buf = append(buf, strconv.FormatUint(c.ChainWindowSize, 10)...)
|
||||
buf = append(buf, 0)
|
||||
buf = append(buf, strconv.FormatUint(i.UnclePenalty, 10)...)
|
||||
buf = append(buf, strconv.FormatUint(c.UnclePenalty, 10)...)
|
||||
buf = append(buf, 0)
|
||||
|
||||
return randomx.ConsensusHash(buf)
|
||||
|
|
|
@ -235,7 +235,7 @@ func (b *PoolBlock) CalculateFullId(consensus *Consensus) FullId {
|
|||
return buf
|
||||
}
|
||||
|
||||
func (b *PoolBlock) MainDifficulty() types.Difficulty {
|
||||
func (b *PoolBlock) MainDifficulty(f mainblock.GetDifficultyByHeightFunc) types.Difficulty {
|
||||
if difficulty, ok := func() (types.Difficulty, bool) {
|
||||
b.cache.lock.RLock()
|
||||
defer b.cache.lock.RUnlock()
|
||||
|
@ -250,7 +250,7 @@ func (b *PoolBlock) MainDifficulty() types.Difficulty {
|
|||
b.cache.lock.Lock()
|
||||
defer b.cache.lock.Unlock()
|
||||
if b.cache.mainDifficulty == types.ZeroDifficulty { //check again for race
|
||||
b.cache.mainDifficulty = b.Main.Difficulty()
|
||||
b.cache.mainDifficulty = b.Main.Difficulty(f)
|
||||
}
|
||||
return b.cache.mainDifficulty
|
||||
}
|
||||
|
@ -277,12 +277,12 @@ func (b *PoolBlock) SideTemplateId(consensus *Consensus) types.Hash {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *PoolBlock) PowHash() types.Hash {
|
||||
h, _ := b.PowHashWithError()
|
||||
func (b *PoolBlock) PowHash(f mainblock.GetSeedByHeightFunc) types.Hash {
|
||||
h, _ := b.PowHashWithError(f)
|
||||
return h
|
||||
}
|
||||
|
||||
func (b *PoolBlock) PowHashWithError() (powHash types.Hash, err error) {
|
||||
func (b *PoolBlock) PowHashWithError(f mainblock.GetSeedByHeightFunc) (powHash types.Hash, err error) {
|
||||
if hash, ok := func() (types.Hash, bool) {
|
||||
b.cache.lock.RLock()
|
||||
defer b.cache.lock.RUnlock()
|
||||
|
@ -297,7 +297,7 @@ func (b *PoolBlock) PowHashWithError() (powHash types.Hash, err error) {
|
|||
b.cache.lock.Lock()
|
||||
defer b.cache.lock.Unlock()
|
||||
if b.cache.powHash == types.ZeroHash { //check again for race
|
||||
b.cache.powHash, err = b.Main.PowHashWithError()
|
||||
b.cache.powHash, err = b.Main.PowHashWithError(f)
|
||||
}
|
||||
return b.cache.powHash, err
|
||||
}
|
||||
|
@ -359,28 +359,28 @@ func (b *PoolBlock) FromCompactReader(reader readerAndByteReader) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (b *PoolBlock) IsProofHigherThanMainDifficulty() bool {
|
||||
r, _ := b.IsProofHigherThanMainDifficultyWithError()
|
||||
func (b *PoolBlock) IsProofHigherThanMainDifficulty(difficultyFunc mainblock.GetDifficultyByHeightFunc, seedFunc mainblock.GetSeedByHeightFunc) bool {
|
||||
r, _ := b.IsProofHigherThanMainDifficultyWithError(difficultyFunc, seedFunc)
|
||||
return r
|
||||
}
|
||||
|
||||
func (b *PoolBlock) IsProofHigherThanMainDifficultyWithError() (bool, error) {
|
||||
if mainDifficulty := b.MainDifficulty(); mainDifficulty == types.ZeroDifficulty {
|
||||
func (b *PoolBlock) IsProofHigherThanMainDifficultyWithError(difficultyFunc mainblock.GetDifficultyByHeightFunc, seedFunc mainblock.GetSeedByHeightFunc) (bool, error) {
|
||||
if mainDifficulty := b.MainDifficulty(difficultyFunc); mainDifficulty == types.ZeroDifficulty {
|
||||
return false, errors.New("could not get main difficulty")
|
||||
} else if powHash, err := b.PowHashWithError(); err != nil {
|
||||
} else if powHash, err := b.PowHashWithError(seedFunc); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return mainDifficulty.CheckPoW(powHash), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *PoolBlock) IsProofHigherThanDifficulty() bool {
|
||||
r, _ := b.IsProofHigherThanDifficultyWithError()
|
||||
func (b *PoolBlock) IsProofHigherThanDifficulty(f mainblock.GetSeedByHeightFunc) bool {
|
||||
r, _ := b.IsProofHigherThanDifficultyWithError(f)
|
||||
return r
|
||||
}
|
||||
|
||||
func (b *PoolBlock) IsProofHigherThanDifficultyWithError() (bool, error) {
|
||||
if powHash, err := b.PowHashWithError(); err != nil {
|
||||
func (b *PoolBlock) IsProofHigherThanDifficultyWithError(f mainblock.GetSeedByHeightFunc) (bool, error) {
|
||||
if powHash, err := b.PowHashWithError(f); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
return b.Side.Difficulty.CheckPoW(powHash), nil
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package sidechain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
block2 "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"io"
|
||||
"log"
|
||||
|
@ -72,24 +74,42 @@ func TestPoolBlockDecode(t *testing.T) {
|
|||
|
||||
proofResult, _ := types.DifficultyFromString("00000000000000000000006ef6334490")
|
||||
|
||||
if types.DifficultyFromPoW(block.PowHash()).Cmp(proofResult) != 0 {
|
||||
t.Fatalf("expected PoW difficulty %s, got %s", proofResult.String(), types.DifficultyFromPoW(block.PowHash()).String())
|
||||
getSeedByHeight := func(height uint64) (hash types.Hash) {
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
if h, err := client.GetDefaultClient().GetBlockHeaderByHeight(seedHeight, context.Background()); err != nil {
|
||||
return types.ZeroHash
|
||||
} else {
|
||||
hash, _ := types.HashFromString(h.BlockHeader.Hash)
|
||||
return hash
|
||||
}
|
||||
}
|
||||
|
||||
getDifficultyByHeight := func(height uint64) types.Difficulty {
|
||||
if h, err := client.GetDefaultClient().GetBlockHeaderByHeight(height, context.Background()); err != nil {
|
||||
return types.ZeroDifficulty
|
||||
} else {
|
||||
return types.DifficultyFrom64(h.BlockHeader.Difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
if types.DifficultyFromPoW(block.PowHash(getSeedByHeight)).Cmp(proofResult) != 0 {
|
||||
t.Fatalf("expected PoW difficulty %s, got %s", proofResult.String(), types.DifficultyFromPoW(block.PowHash(getSeedByHeight)).String())
|
||||
}
|
||||
|
||||
t.Log(block.Main.Id().String())
|
||||
//t.Log(block.Main.PowHash().String())
|
||||
//t.Log(block.Main.PowHash().String())
|
||||
|
||||
if !block.IsProofHigherThanMainDifficulty() {
|
||||
if !block.IsProofHigherThanMainDifficulty(getDifficultyByHeight, getSeedByHeight) {
|
||||
t.Fatal("expected proof higher than difficulty")
|
||||
}
|
||||
|
||||
block.cache.powHash[31] = 1
|
||||
|
||||
if block.IsProofHigherThanMainDifficulty() {
|
||||
if block.IsProofHigherThanMainDifficulty(getDifficultyByHeight, getSeedByHeight) {
|
||||
t.Fatal("expected proof lower than difficulty")
|
||||
}
|
||||
|
||||
log.Print(types.DifficultyFromPoW(block.PowHash()).String())
|
||||
log.Print(block.MainDifficulty().String())
|
||||
log.Print(types.DifficultyFromPoW(block.PowHash(getSeedByHeight)).String())
|
||||
log.Print(block.MainDifficulty(getDifficultyByHeight).String())
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
|
@ -28,6 +30,23 @@ type P2PoolInterface interface {
|
|||
ConsensusProvider
|
||||
Cache
|
||||
Broadcast(block *PoolBlock)
|
||||
ClientRPC() *client.Client
|
||||
GetChainMainByHeight(height uint64) *ChainMain
|
||||
GetChainMainByHash(hash types.Hash) *ChainMain
|
||||
GetMinimalBlockHeaderByHeight(height uint64) *mainblock.Header
|
||||
GetMinimalBlockHeaderByHash(hash types.Hash) *mainblock.Header
|
||||
GetDifficultyByHeight(height uint64) types.Difficulty
|
||||
UpdateBlockFound(data *ChainMain, block *PoolBlock)
|
||||
SubmitBlock(block *mainblock.Block)
|
||||
GetChainMainTip() *ChainMain
|
||||
}
|
||||
|
||||
type ChainMain struct {
|
||||
Difficulty types.Difficulty
|
||||
Height uint64
|
||||
Timestamp uint64
|
||||
Reward uint64
|
||||
Id types.Hash
|
||||
}
|
||||
|
||||
type SideChain struct {
|
||||
|
@ -36,6 +55,9 @@ type SideChain struct {
|
|||
|
||||
sidechainLock sync.RWMutex
|
||||
|
||||
watchBlock *ChainMain
|
||||
watchBlockSidechainId types.Hash
|
||||
|
||||
sharesCache Shares
|
||||
|
||||
blocksByTemplateId map[types.Hash]*PoolBlock
|
||||
|
@ -133,6 +155,17 @@ func (c *SideChain) isPoolBlockTransactionKeyIsDeterministic(block *PoolBlock) b
|
|||
return bytes.Compare(block.CoinbaseExtra(SideCoinbasePublicKey), kP.PublicKey.AsSlice()) == 0 && bytes.Compare(block.Side.CoinbasePrivateKey[:], kP.PrivateKey.AsSlice()) == 0
|
||||
}
|
||||
|
||||
func (c *SideChain) getSeedByHeightFunc() mainblock.GetSeedByHeightFunc {
|
||||
return func(height uint64) (hash types.Hash) {
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
if h := c.server.GetMinimalBlockHeaderByHeight(seedHeight); h != nil {
|
||||
return h.Id
|
||||
} else {
|
||||
return types.ZeroHash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SideChain) AddPoolBlockExternal(block *PoolBlock) (missingBlocks []types.Hash, err error) {
|
||||
// Technically some p2pool node could keep stuffing block with transactions until reward is less than 0.6 XMR
|
||||
// But default transaction picking algorithm never does that. It's better to just ban such nodes
|
||||
|
@ -201,7 +234,7 @@ func (c *SideChain) AddPoolBlockExternal(block *PoolBlock) (missingBlocks []type
|
|||
|
||||
// This check is not always possible to perform because of mainchain reorgs
|
||||
//TODO: cache current miner data?
|
||||
if data := mainblock.GetBlockHeaderByHash(block.Main.PreviousId); data != nil {
|
||||
if data := c.server.GetChainMainByHash(block.Main.PreviousId); data != nil {
|
||||
if data.Height+1 != block.Main.Coinbase.GenHeight {
|
||||
return nil, fmt.Errorf("wrong mainchain height %d, expected %d", block.Main.Coinbase.GenHeight, data.Height+1)
|
||||
}
|
||||
|
@ -209,11 +242,16 @@ func (c *SideChain) AddPoolBlockExternal(block *PoolBlock) (missingBlocks []type
|
|||
//TODO warn unknown block, reorg
|
||||
}
|
||||
|
||||
if _, err := block.PowHashWithError(); err != nil {
|
||||
if _, err := block.PowHashWithError(c.getSeedByHeightFunc()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
//TODO: fast monero submission
|
||||
if isHigher, err := block.IsProofHigherThanDifficultyWithError(); err != nil {
|
||||
if isHigherMainChain, err := block.IsProofHigherThanMainDifficultyWithError(c.server.GetDifficultyByHeight, c.getSeedByHeightFunc()); err != nil {
|
||||
log.Printf("[SideChain] add_external_block: couldn't get mainchain difficulty for height = %d: %s", block.Main.Coinbase.GenHeight, err)
|
||||
} else if isHigherMainChain {
|
||||
log.Printf("[SideChain]: add_external_block: block %s has enough PoW for Monero height %d, submitting it", templateId.String(), block.Main.Coinbase.GenHeight)
|
||||
c.server.SubmitBlock(&block.Main)
|
||||
}
|
||||
if isHigher, err := block.IsProofHigherThanDifficultyWithError(c.getSeedByHeightFunc()); err != nil {
|
||||
return nil, err
|
||||
} else if !isHigher {
|
||||
return nil, fmt.Errorf("not enough PoW for height = %d, mainchain height %d", block.Side.Height, block.Main.Coinbase.GenHeight)
|
||||
|
@ -252,6 +290,11 @@ func (c *SideChain) AddPoolBlock(block *PoolBlock) (err error) {
|
|||
|
||||
log.Printf("[SideChain] add_block: height = %d, id = %s, mainchain height = %d, verified = %t, total = %d", block.Side.Height, block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.Verified.Load(), len(c.blocksByTemplateId))
|
||||
|
||||
if block.SideTemplateId(c.Consensus()) == c.watchBlockSidechainId {
|
||||
c.server.UpdateBlockFound(c.watchBlock, block)
|
||||
c.watchBlockSidechainId = types.ZeroHash
|
||||
}
|
||||
|
||||
if l, ok := c.blocksByHeight[block.Side.Height]; ok {
|
||||
c.blocksByHeight[block.Side.Height] = append(l, block)
|
||||
} else {
|
||||
|
@ -1016,6 +1059,14 @@ func (c *SideChain) GetPoolBlockCount() int {
|
|||
return len(c.blocksByTemplateId)
|
||||
}
|
||||
|
||||
func (c *SideChain) WatchMainChainBlock(mainData *ChainMain, possibleId types.Hash) {
|
||||
c.sidechainLock.Lock()
|
||||
defer c.sidechainLock.Unlock()
|
||||
|
||||
c.watchBlock = mainData
|
||||
c.watchBlockSidechainId = possibleId
|
||||
}
|
||||
|
||||
func (c *SideChain) GetChainTip() *PoolBlock {
|
||||
return c.chainTip.Load()
|
||||
}
|
||||
|
@ -1085,7 +1136,7 @@ func (c *SideChain) isLongerChain(block, candidate *PoolBlock) (isLonger, isAlte
|
|||
candidateTotalDiff = candidateTotalDiff.Add(newChain.Side.Difficulty)
|
||||
|
||||
if !newChain.Main.PreviousId.Equals(mainchainPrevId) {
|
||||
if data := mainblock.GetBlockHeaderByHash(newChain.Main.PreviousId); data != nil {
|
||||
if data := c.server.GetMinimalBlockHeaderByHash(newChain.Main.PreviousId); data != nil {
|
||||
mainchainPrevId = data.Id
|
||||
candidateMainchainHeight = utils.Max(candidateMainchainHeight, data.Height)
|
||||
}
|
||||
|
@ -1100,14 +1151,14 @@ func (c *SideChain) isLongerChain(block, candidate *PoolBlock) (isLonger, isAlte
|
|||
}
|
||||
|
||||
// Final check: candidate chain must be built on top of recent mainchain blocks
|
||||
if data := mainblock.GetLastBlockHeader(); data != nil {
|
||||
if candidateMainchainHeight+10 < data.Height {
|
||||
if headerTip := c.server.GetChainMainTip(); headerTip != nil {
|
||||
if candidateMainchainHeight+10 < headerTip.Height {
|
||||
//TODO: warn received a longer alternative chain but it's stale: height
|
||||
return false, true
|
||||
}
|
||||
|
||||
limit := c.Consensus().ChainWindowSize * 4 * c.Consensus().TargetBlockTime / monero.BlockTime
|
||||
if candidateMainchainMinHeight+limit < data.Height {
|
||||
if candidateMainchainMinHeight+limit < headerTip.Height {
|
||||
//TODO: warn received a longer alternative chain but it's stale: min height
|
||||
return false, true
|
||||
}
|
||||
|
|
|
@ -13,3 +13,8 @@ func SiUnits(number float64, decimals int) string {
|
|||
|
||||
return fmt.Sprintf("%.*f ", decimals, number)
|
||||
}
|
||||
|
||||
func XMRUnits(v uint64) string {
|
||||
const denomination = 1000000000000
|
||||
return fmt.Sprintf("%d.%012d", v/denomination, v%denomination)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue