Added all entries to single docker-compose.yml
Working docker-compose version, removed old unused database code
This commit is contained in:
parent
e56d7ee46e
commit
4d78339795
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
.git/
|
||||
.env
|
||||
docker-compose.override.yml
|
||||
p2pool.cache
|
||||
p2pool_peers.txt
|
26
.env.example
Normal file
26
.env.example
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
# Monerod p2pool compatible node with open RPC and ZMQ ports
|
||||
MONEROD_HOST=p2pmd.xmrvsbeast.com
|
||||
MONEROD_RPC_PORT=18081
|
||||
MONEROD_ZMQ_PORT=18083
|
||||
|
||||
# Tor private key and public address. You can generate vanity ones via https://github.com/cathugger/mkp224o
|
||||
TOR_SERVICE_KEY=
|
||||
TOR_SERVICE_ADDRESS=
|
||||
NET_SERVICE_ADDRESS=
|
||||
|
||||
SITE_TITLE=
|
||||
# This port will be presented externally
|
||||
SITE_PORT=8189
|
||||
|
||||
# Change for mini if you want public reachable address
|
||||
P2POOL_PORT=37889
|
||||
# This port is what should be reachable externally, and will be reachable via Tor as well
|
||||
P2POOL_EXTERNAL_PORT=37889
|
||||
P2POOL_OUT_PEERS=24
|
||||
P2POOL_IN_PEERS=64
|
||||
|
||||
# Extra arg examples
|
||||
#P2POOL_EXTRA_ARGS=-light-mode
|
||||
#P2POOL_EXTRA_ARGS=-mini
|
||||
#P2POOL_EXTRA_ARGS=-consensus-config /data/consensus.json
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
.idea
|
||||
p2pool.cache
|
||||
p2pool_peers.txt
|
||||
p2pool_peers.txt
|
||||
docker-compose.override.yml
|
||||
.env
|
|
@ -35,11 +35,13 @@ func encodeJson(r *http.Request, d any) ([]byte, error) {
|
|||
|
||||
func main() {
|
||||
torHost := os.Getenv("TOR_SERVICE_ADDRESS")
|
||||
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")
|
||||
dbString := flag.String("db", "", "")
|
||||
p2poolApiHost := flag.String("api-host", "", "Host URL for p2pool go observer consensus")
|
||||
flag.Parse()
|
||||
|
||||
client.SetDefaultClientSettings(os.Getenv("MONEROD_RPC_URL"))
|
||||
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
|
||||
|
||||
p2api := p2poolapi.NewP2PoolApi(*p2poolApiHost)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
|
||||
utils2 "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/index"
|
||||
|
@ -13,7 +14,6 @@ import (
|
|||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -22,13 +22,16 @@ func blockId(b *sidechain.PoolBlock) types.Hash {
|
|||
}
|
||||
|
||||
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")
|
||||
startFromHeight := flag.Uint64("from", 0, "Start sync from this height")
|
||||
dbString := flag.String("db", "", "")
|
||||
p2poolApiHost := flag.String("api-host", "", "Host URL for p2pool go observer consensus")
|
||||
fullMode := flag.Bool("full-mode", false, "Allocate RandomX dataset, uses 2GB of RAM")
|
||||
flag.Parse()
|
||||
randomx.UseFullMemory.Store(*fullMode)
|
||||
client.SetDefaultClientSettings(os.Getenv("MONEROD_RPC_URL"))
|
||||
|
||||
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
|
||||
|
||||
p2api := p2poolapi.NewP2PoolApi(*p2poolApiHost)
|
||||
|
||||
|
@ -128,11 +131,6 @@ func main() {
|
|||
log.Panic(err)
|
||||
}
|
||||
|
||||
heightCount := maxHeight - 1 - currentHeight + 1
|
||||
|
||||
const strideSize = 1000
|
||||
strides := heightCount / strideSize
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
scanHeader := func(h daemon.BlockHeader) error {
|
||||
|
@ -147,6 +145,11 @@ func main() {
|
|||
return nil
|
||||
}
|
||||
|
||||
heightCount := maxHeight - 1 - currentHeight + 1
|
||||
|
||||
const strideSize = 1000
|
||||
strides := heightCount / strideSize
|
||||
|
||||
//backfill headers
|
||||
for stride := uint64(0); stride <= strides; stride++ {
|
||||
start := currentHeight + stride*strideSize
|
||||
|
@ -173,8 +176,8 @@ func main() {
|
|||
if header == nil {
|
||||
break
|
||||
}
|
||||
cur, err := client.GetDefaultClient().GetBlockHeaderByHash(header.Id, ctx)
|
||||
if err != nil {
|
||||
cur, _ := client.GetDefaultClient().GetBlockHeaderByHash(header.Id, ctx)
|
||||
if cur == nil {
|
||||
break
|
||||
}
|
||||
if err := scanHeader(cur.BlockHeader); err != nil {
|
||||
|
@ -206,7 +209,7 @@ func main() {
|
|||
log.Panicf("main tip height less than ours, abort: %d < %d", mainTip.Height, currentMainTip.Height)
|
||||
} else {
|
||||
var prevHash types.Hash
|
||||
for cur, err := client.GetDefaultClient().GetBlockHeaderByHash(mainTip.Id, ctx); err != nil; cur, err = client.GetDefaultClient().GetBlockHeaderByHash(prevHash, ctx) {
|
||||
for cur, _ := client.GetDefaultClient().GetBlockHeaderByHash(mainTip.Id, ctx); cur != nil; cur, _ = client.GetDefaultClient().GetBlockHeaderByHash(prevHash, ctx) {
|
||||
curHash, _ := types.HashFromString(cur.BlockHeader.Hash)
|
||||
if indexDb.GetMainBlockByHeight(cur.BlockHeader.Height).Id == curHash {
|
||||
break
|
||||
|
|
|
@ -42,7 +42,7 @@ func main() {
|
|||
addPeers := flag.String("addpeers", "", "Comma-separated list of IP:port of other p2pool nodes to connect to")
|
||||
lightMode := flag.Bool("light-mode", false, "Don't allocate RandomX dataset, saves 2GB of RAM")
|
||||
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")
|
||||
consensusConfigFile := flag.String("config", "", "Name of the p2pool config file")
|
||||
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.")
|
||||
|
||||
outPeers := flag.Uint64("out-peers", 10, "Maximum number of outgoing connections for p2p server (any value between 10 and 450)")
|
||||
|
|
517
cmd/recoverpoolblock/recoverpoolblock.go
Normal file
517
cmd/recoverpoolblock/recoverpoolblock.go
Normal file
|
@ -0,0 +1,517 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"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/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/archive"
|
||||
crypto2 "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/mempool"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"github.com/floatdrop/lru"
|
||||
"golang.org/x/exp/slices"
|
||||
"hash"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func main() {
|
||||
inputConsensus := flag.String("consensus", "config.json", "Input config.json consensus file")
|
||||
inputArchive := flag.String("input", "", "Input path for archive database")
|
||||
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")
|
||||
inputTemplate := flag.String("template", "", "Template data for share recovery")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
|
||||
|
||||
cf, err := os.ReadFile(*inputConsensus)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
inputTemplateData, err := os.ReadFile(*inputTemplate)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
jsonTemplate, err := JSONFromTemplate(inputTemplateData)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
consensus, err := sidechain.NewConsensusFromJSON(cf)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
var headerCacheLock sync.RWMutex
|
||||
headerByHeightCache := make(map[uint64]*daemon.BlockHeader)
|
||||
|
||||
getHeaderByHeight := func(height uint64) *daemon.BlockHeader {
|
||||
if v := func() *daemon.BlockHeader {
|
||||
headerCacheLock.RLock()
|
||||
defer headerCacheLock.RUnlock()
|
||||
return headerByHeightCache[height]
|
||||
}(); v == nil {
|
||||
if r, err := client.GetDefaultClient().GetBlockHeaderByHeight(height, context.Background()); err == nil {
|
||||
headerCacheLock.Lock()
|
||||
defer headerCacheLock.Unlock()
|
||||
headerByHeightCache[r.BlockHeader.Height] = &r.BlockHeader
|
||||
return &r.BlockHeader
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
getDifficultyByHeight := func(height uint64) types.Difficulty {
|
||||
if v := getHeaderByHeight(height); v != nil {
|
||||
return types.DifficultyFrom64(v.Difficulty)
|
||||
}
|
||||
return types.ZeroDifficulty
|
||||
}
|
||||
|
||||
getSeedByHeight := func(height uint64) (hash types.Hash) {
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
if v := getHeaderByHeight(seedHeight); v != nil {
|
||||
h, _ := types.HashFromString(v.Hash)
|
||||
return h
|
||||
}
|
||||
return types.ZeroHash
|
||||
}
|
||||
|
||||
_ = getSeedByHeight
|
||||
|
||||
archiveCache, err := archive.NewCache(*inputArchive, consensus, getDifficultyByHeight)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer archiveCache.Close()
|
||||
|
||||
blockCache := lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4))
|
||||
|
||||
derivationCache := sidechain.NewDerivationCache()
|
||||
getByTemplateIdDirect := func(h types.Hash) *sidechain.PoolBlock {
|
||||
if v := blockCache.Get(h); v == nil {
|
||||
if bs := archiveCache.LoadByTemplateId(h); len(bs) != 0 {
|
||||
blockCache.Set(h, bs[0])
|
||||
return bs[0]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return *v
|
||||
}
|
||||
}
|
||||
|
||||
processBlock := func(b *sidechain.PoolBlock) error {
|
||||
var preAllocatedShares sidechain.Shares
|
||||
if len(b.Main.Coinbase.Outputs) == 0 {
|
||||
//cannot use SideTemplateId() as it might not be proper to calculate yet. fetch from coinbase only here
|
||||
if b2 := getByTemplateIdDirect(types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId))); b2 != nil && len(b2.Main.Coinbase.Outputs) != 0 {
|
||||
b.Main.Coinbase.Outputs = b2.Main.Coinbase.Outputs
|
||||
} else {
|
||||
preAllocatedShares = sidechain.PreAllocateShares(consensus.ChainWindowSize * 2)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := b.PreProcessBlock(consensus, derivationCache, preAllocatedShares, getDifficultyByHeight, getByTemplateIdDirect)
|
||||
return err
|
||||
}
|
||||
|
||||
getByTemplateId := func(h types.Hash) *sidechain.PoolBlock {
|
||||
if v := getByTemplateIdDirect(h); v != nil {
|
||||
if processBlock(v) != nil {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var poolBlock *sidechain.PoolBlock
|
||||
var expectedMainId types.Hash
|
||||
var expectedTemplateId types.Hash
|
||||
var expectedCoinbaseId types.Hash
|
||||
var baseMainReward, deltaReward uint64
|
||||
|
||||
getTransactionsEntries := func(h ...types.Hash) (r mempool.Mempool) {
|
||||
if data, jsonTx, err := client.GetDefaultClient().GetTransactions(h...); err != nil {
|
||||
log.Printf("could not get txids: %s", err)
|
||||
return nil
|
||||
} else {
|
||||
r = make(mempool.Mempool, len(jsonTx))
|
||||
for i, tx := range jsonTx {
|
||||
r[i] = mempool.NewEntryFromRPCData(h[i], data[i], tx)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
if v2, ok := jsonTemplate.(JsonBlock2); ok {
|
||||
parentBlock := getByTemplateId(v2.PrevId)
|
||||
|
||||
keyPair := crypto.NewKeyPairFromPrivate(&v2.CoinbasePriv)
|
||||
expectedMainId = v2.MainId
|
||||
expectedCoinbaseId = v2.CoinbaseId
|
||||
expectedTemplateId = v2.Id
|
||||
|
||||
header := getHeaderByHeight(v2.MainHeight)
|
||||
|
||||
var nextBlock *sidechain.PoolBlock
|
||||
for _, t2 := range archiveCache.LoadBySideChainHeight(v2.Height + 1) {
|
||||
if t2.Side.Parent == expectedTemplateId {
|
||||
if err := processBlock(t2); err == nil {
|
||||
nextBlock = t2
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if parentBlock == nil {
|
||||
parentBlock = getByTemplateIdDirect(v2.PrevId)
|
||||
}
|
||||
if parentBlock == nil {
|
||||
//use forward method
|
||||
} else {
|
||||
|
||||
if parentBlock.Main.Coinbase.GenHeight == v2.MainHeight {
|
||||
baseMainReward = parentBlock.Main.Coinbase.TotalReward
|
||||
for _, tx := range getTransactionsEntries(parentBlock.Main.Transactions...) {
|
||||
baseMainReward -= tx.Fee
|
||||
}
|
||||
|
||||
deltaReward = v2.CoinbaseReward - baseMainReward
|
||||
} else if nextBlock != nil && nextBlock.Main.Coinbase.GenHeight == v2.MainHeight {
|
||||
for _, tx := range getTransactionsEntries(nextBlock.Main.Transactions...) {
|
||||
baseMainReward -= tx.Fee
|
||||
}
|
||||
|
||||
deltaReward = v2.CoinbaseReward - baseMainReward
|
||||
} else {
|
||||
//fallback method can fail on very full blocks
|
||||
headerId, _ := types.HashFromString(header.Hash)
|
||||
|
||||
if b, err := client.GetDefaultClient().GetBlock(headerId, context.Background()); err != nil {
|
||||
log.Panic(err)
|
||||
} else {
|
||||
//generalization, actual reward could be different in some cases
|
||||
ij, _ := b.InnerJSON()
|
||||
baseMainReward = b.BlockHeader.Reward
|
||||
var txs []types.Hash
|
||||
for _, txid := range ij.TxHashes {
|
||||
h, _ := types.HashFromString(txid)
|
||||
txs = append(txs, h)
|
||||
}
|
||||
for _, tx := range getTransactionsEntries(txs...) {
|
||||
baseMainReward -= tx.Fee
|
||||
}
|
||||
}
|
||||
|
||||
deltaReward = v2.CoinbaseReward - baseMainReward
|
||||
}
|
||||
|
||||
log.Printf("pool delta reward: %d (%s), base %d (%s), expected %d (%s)", deltaReward, utils.XMRUnits(deltaReward), baseMainReward, utils.XMRUnits(baseMainReward), v2.CoinbaseReward, utils.XMRUnits(v2.CoinbaseReward))
|
||||
|
||||
poolBlock = &sidechain.PoolBlock{
|
||||
Main: block.Block{
|
||||
MajorVersion: uint8(header.MajorVersion),
|
||||
MinorVersion: uint8(header.MinorVersion),
|
||||
Timestamp: v2.Ts,
|
||||
PreviousId: v2.MinerMainId,
|
||||
Coinbase: &transaction.CoinbaseTransaction{
|
||||
Version: 2,
|
||||
UnlockTime: v2.MainHeight + monero.MinerRewardUnlockTime,
|
||||
InputCount: 1,
|
||||
InputType: transaction.TxInGen,
|
||||
GenHeight: v2.MainHeight,
|
||||
TotalReward: v2.CoinbaseReward,
|
||||
Extra: transaction.ExtraTags{
|
||||
transaction.ExtraTag{
|
||||
Tag: transaction.TxExtraTagPubKey,
|
||||
VarIntLength: 0,
|
||||
Data: types.Bytes(keyPair.PublicKey.AsSlice()),
|
||||
},
|
||||
transaction.ExtraTag{
|
||||
Tag: transaction.TxExtraTagNonce,
|
||||
VarIntLength: 4,
|
||||
Data: make(types.Bytes, 4), //TODO: expand nonce size as needed
|
||||
},
|
||||
transaction.ExtraTag{
|
||||
Tag: transaction.TxExtraTagMergeMining,
|
||||
VarIntLength: types.HashSize,
|
||||
Data: v2.Id[:],
|
||||
},
|
||||
},
|
||||
ExtraBaseRCT: 0,
|
||||
},
|
||||
Transactions: nil,
|
||||
},
|
||||
Side: sidechain.SideData{
|
||||
PublicSpendKey: v2.Wallet.SpendPub.AsBytes(),
|
||||
PublicViewKey: v2.Wallet.ViewPub.AsBytes(),
|
||||
CoinbasePrivateKey: keyPair.PrivateKey.AsBytes(),
|
||||
Parent: v2.PrevId,
|
||||
Uncles: func() (result []types.Hash) {
|
||||
for _, u := range v2.Uncles {
|
||||
result = append(result, u.Id)
|
||||
}
|
||||
return result
|
||||
}(),
|
||||
Height: v2.Height,
|
||||
Difficulty: v2.Diff,
|
||||
CumulativeDifficulty: parentBlock.Side.CumulativeDifficulty.Add(v2.Diff),
|
||||
// no extrabuffer
|
||||
},
|
||||
NetworkType: consensus.NetworkType,
|
||||
}
|
||||
}
|
||||
|
||||
poolBlock.Depth.Store(math.MaxUint64)
|
||||
|
||||
if poolBlock.ShareVersion() > sidechain.ShareVersion_V1 {
|
||||
poolBlock.Side.CoinbasePrivateKeySeed = parentBlock.Side.CoinbasePrivateKeySeed
|
||||
if parentBlock.Main.PreviousId != poolBlock.Main.PreviousId {
|
||||
poolBlock.Side.CoinbasePrivateKeySeed = parentBlock.CalculateTransactionPrivateKeySeed()
|
||||
}
|
||||
} else {
|
||||
expectedSeed := poolBlock.CalculateTransactionPrivateKeySeed()
|
||||
kP := crypto.NewKeyPairFromPrivate(crypto2.GetDeterministicTransactionPrivateKey(expectedSeed, poolBlock.Main.PreviousId))
|
||||
|
||||
if bytes.Compare(poolBlock.CoinbaseExtra(sidechain.SideCoinbasePublicKey), kP.PublicKey.AsSlice()) == 0 && bytes.Compare(kP.PrivateKey.AsSlice(), poolBlock.Side.CoinbasePrivateKey[:]) == 0 {
|
||||
poolBlock.Side.CoinbasePrivateKeySeed = expectedSeed
|
||||
} else {
|
||||
log.Printf("not deterministic private key")
|
||||
}
|
||||
}
|
||||
|
||||
currentOutputs, _ := sidechain.CalculateOutputs(poolBlock, consensus, getDifficultyByHeight, getByTemplateIdDirect, derivationCache, sidechain.PreAllocateShares(consensus.ChainWindowSize*2))
|
||||
|
||||
if currentOutputs == nil {
|
||||
log.Panic("could not calculate outputs blob")
|
||||
}
|
||||
poolBlock.Main.Coinbase.Outputs = currentOutputs
|
||||
|
||||
if blob, err := currentOutputs.MarshalBinary(); err != nil {
|
||||
log.Panic(err)
|
||||
} else {
|
||||
poolBlock.Main.Coinbase.OutputsBlobSize = uint64(len(blob))
|
||||
}
|
||||
log.Printf("expected main id %s, template id %s, coinbase id %s", expectedMainId, expectedTemplateId, expectedCoinbaseId)
|
||||
|
||||
rctHash := crypto.Keccak256([]byte{0})
|
||||
type partialBlobWork struct {
|
||||
Hashers [2]hash.Hash
|
||||
Tx *transaction.CoinbaseTransaction
|
||||
EncodedBuffer []byte
|
||||
EncodedOffset int
|
||||
TempHash types.Hash
|
||||
}
|
||||
var stop atomic.Bool
|
||||
|
||||
var foundExtraNonce atomic.Uint32
|
||||
var foundExtraNonceSize atomic.Uint32
|
||||
|
||||
minerTxBlob, _ := poolBlock.Main.Coinbase.MarshalBinary()
|
||||
|
||||
searchForNonces := func(nonceSize int, max uint64) {
|
||||
coinbases := make([]*partialBlobWork, runtime.NumCPU())
|
||||
utils.SplitWork(0, max, func(workIndex uint64, routineIndex int) error {
|
||||
if stop.Load() {
|
||||
return errors.New("found nonce")
|
||||
}
|
||||
w := coinbases[routineIndex]
|
||||
if workIndex%(1024*256) == 0 {
|
||||
log.Printf("try %d/%d @ %d ~%.2f%%", workIndex, max, nonceSize, (float64(workIndex)/math.MaxUint32)*100)
|
||||
}
|
||||
binary.LittleEndian.PutUint32(w.EncodedBuffer[w.EncodedOffset:], uint32(workIndex))
|
||||
idHasher := w.Hashers[0]
|
||||
txHasher := w.Hashers[1]
|
||||
|
||||
txHasher.Write(w.EncodedBuffer)
|
||||
crypto.HashFastSum(txHasher, w.TempHash[:])
|
||||
|
||||
idHasher.Write(w.TempHash[:])
|
||||
// Base RCT, single 0 byte in miner tx
|
||||
idHasher.Write(rctHash[:])
|
||||
// Prunable RCT, empty in miner tx
|
||||
idHasher.Write(types.ZeroHash[:])
|
||||
crypto.HashFastSum(idHasher, w.TempHash[:])
|
||||
|
||||
if w.TempHash == expectedCoinbaseId {
|
||||
foundExtraNonce.Store(uint32(workIndex))
|
||||
foundExtraNonceSize.Store(uint32(nonceSize))
|
||||
//FOUND!
|
||||
stop.Store(true)
|
||||
return errors.New("found nonce")
|
||||
}
|
||||
|
||||
idHasher.Reset()
|
||||
txHasher.Reset()
|
||||
|
||||
return nil
|
||||
}, func(routines, routineIndex int) error {
|
||||
if len(coinbases) < routines {
|
||||
coinbases = slices.Grow(coinbases, routines)
|
||||
}
|
||||
|
||||
tx := &transaction.CoinbaseTransaction{}
|
||||
if err := tx.UnmarshalBinary(minerTxBlob); err != nil {
|
||||
return err
|
||||
}
|
||||
tx.Extra[1].VarIntLength = uint64(nonceSize)
|
||||
tx.Extra[1].Data = make([]byte, nonceSize)
|
||||
|
||||
buf, _ := tx.MarshalBinary()
|
||||
|
||||
coinbases[routineIndex] = &partialBlobWork{
|
||||
Hashers: [2]hash.Hash{crypto.GetKeccak256Hasher(), crypto.GetKeccak256Hasher()},
|
||||
Tx: tx,
|
||||
EncodedBuffer: buf[:len(buf)-1], /* remove RCT */
|
||||
EncodedOffset: len(buf) - 1 - (types.HashSize + 1 + 1 /*Merge mining tag*/) - nonceSize,
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
nonceSize := sidechain.SideExtraNonceSize
|
||||
|
||||
//do quick search first
|
||||
for ; nonceSize <= sidechain.SideExtraNonceMaxSize; nonceSize++ {
|
||||
if stop.Load() {
|
||||
break
|
||||
}
|
||||
searchForNonces(nonceSize, math.MaxUint16)
|
||||
}
|
||||
//do deeper search next
|
||||
for nonceSize = sidechain.SideExtraNonceSize; nonceSize <= sidechain.SideExtraNonceMaxSize; nonceSize++ {
|
||||
if stop.Load() {
|
||||
break
|
||||
}
|
||||
searchForNonces(nonceSize, math.MaxUint32)
|
||||
}
|
||||
|
||||
log.Printf("found extra nonce %d, size %d", foundExtraNonce.Load(), foundExtraNonceSize.Load())
|
||||
poolBlock.Main.Coinbase.Extra[1].VarIntLength = uint64(foundExtraNonceSize.Load())
|
||||
poolBlock.Main.Coinbase.Extra[1].Data = make([]byte, foundExtraNonceSize.Load())
|
||||
binary.LittleEndian.PutUint32(poolBlock.Main.Coinbase.Extra[1].Data, foundExtraNonce.Load())
|
||||
|
||||
if poolBlock.Main.Coinbase.CalculateId() != expectedCoinbaseId {
|
||||
log.Panic()
|
||||
}
|
||||
|
||||
log.Printf("got coinbase id %s", poolBlock.Main.Coinbase.CalculateId())
|
||||
minerTxBlob, _ = poolBlock.Main.Coinbase.MarshalBinary()
|
||||
log.Printf("raw coinbase: %s", hex.EncodeToString(minerTxBlob))
|
||||
|
||||
var collectedTransactions mempool.Mempool
|
||||
|
||||
collectTxs := func(hashes ...types.Hash) {
|
||||
var txs []types.Hash
|
||||
for _, h := range hashes {
|
||||
if slices.ContainsFunc(collectedTransactions, func(entry *mempool.MempoolEntry) bool {
|
||||
return entry.Id == h
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
txs = append(txs, h)
|
||||
}
|
||||
|
||||
collectedTransactions = append(collectedTransactions, getTransactionsEntries(txs...)...)
|
||||
}
|
||||
|
||||
collectTxsHex := func(hashes ...string) {
|
||||
var txs []types.Hash
|
||||
for _, txid := range hashes {
|
||||
h, _ := types.HashFromString(txid)
|
||||
if slices.ContainsFunc(collectedTransactions, func(entry *mempool.MempoolEntry) bool {
|
||||
return entry.Id == h
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
txs = append(txs, h)
|
||||
}
|
||||
|
||||
collectedTransactions = append(collectedTransactions, getTransactionsEntries(txs...)...)
|
||||
}
|
||||
|
||||
collectTxs(parentBlock.Main.Transactions...)
|
||||
if nextBlock != nil {
|
||||
collectTxs(nextBlock.Main.Transactions...)
|
||||
}
|
||||
|
||||
if bh, err := client.GetDefaultClient().GetBlockByHeight(poolBlock.Main.Coinbase.GenHeight, context.Background()); err == nil {
|
||||
ij, _ := bh.InnerJSON()
|
||||
collectTxsHex(ij.TxHashes...)
|
||||
}
|
||||
|
||||
/*if bh, err := client.GetDefaultClient().GetBlockByHeight(poolBlock.Main.Coinbase.GenHeight+1, context.Background()); err == nil {
|
||||
ij, _ := bh.InnerJSON()
|
||||
collectTxsHex(ij.TxHashes...)
|
||||
}*/
|
||||
|
||||
for _, uncleId := range poolBlock.Side.Uncles {
|
||||
if u := getByTemplateId(uncleId); u != nil {
|
||||
if u.Main.Coinbase.GenHeight == poolBlock.Main.Coinbase.GenHeight {
|
||||
collectTxs(u.Main.Transactions...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bh, err := client.GetDefaultClient().GetBlockByHeight(poolBlock.Main.Coinbase.GenHeight-1, context.Background()); err == nil {
|
||||
ij, _ := bh.InnerJSON()
|
||||
for _, txH := range ij.TxHashes {
|
||||
//remove mined tx
|
||||
txid, _ := types.HashFromString(txH)
|
||||
if i := slices.IndexFunc(collectedTransactions, func(entry *mempool.MempoolEntry) bool {
|
||||
return entry.Id == txid
|
||||
}); i != -1 {
|
||||
collectedTransactions = slices.Delete(collectedTransactions, i, i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
minerTxWeight := uint64(len(minerTxBlob))
|
||||
totalTxWeight := collectedTransactions.Weight()
|
||||
|
||||
medianWeight := getHeaderByHeight(poolBlock.Main.Coinbase.GenHeight).LongTermWeight
|
||||
|
||||
log.Printf("collected transaction candidates: %d", len(collectedTransactions))
|
||||
|
||||
if totalTxWeight+minerTxWeight <= medianWeight {
|
||||
//special case
|
||||
for solution := range collectedTransactions.PerfectSum(deltaReward) {
|
||||
log.Printf("got %d, %d (%s)", solution.Weight(), solution.Fees(), utils.XMRUnits(solution.Fees()))
|
||||
}
|
||||
} else {
|
||||
//sort in preference order
|
||||
pickedTxs := collectedTransactions.Pick(baseMainReward, minerTxWeight, medianWeight)
|
||||
|
||||
log.Printf("got %d, %d (%s)", pickedTxs.Weight(), pickedTxs.Fees(), utils.XMRUnits(pickedTxs.Fees()))
|
||||
}
|
||||
|
||||
//TODO: nonce
|
||||
}
|
||||
|
||||
}
|
107
cmd/recoverpoolblock/template.go
Normal file
107
cmd/recoverpoolblock/template.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
)
|
||||
|
||||
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"`
|
||||
|
||||
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"`
|
||||
} `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"`
|
||||
|
||||
Uncles []struct {
|
||||
Diff uint64 `json:"diff,string"`
|
||||
PrevId types.Hash `json:"prev_id"`
|
||||
Ts uint64 `json:"ts,string"`
|
||||
MHeight uint64 `json:"mheight,string"`
|
||||
PrevHash types.Hash `json:"prev_hash"`
|
||||
Height uint64 `json:"height,string"`
|
||||
Wallet *address.Address `json:"wallet"`
|
||||
Id types.Hash `json:"id"`
|
||||
} `json:"uncles,omitempty"`
|
||||
}
|
||||
|
||||
type versionBlock struct {
|
||||
Version uint64 `json:"version,string"`
|
||||
}
|
||||
|
||||
func JSONFromTemplate(data []byte) (any, error) {
|
||||
var version versionBlock
|
||||
|
||||
if err := json.Unmarshal(data, &version); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if version.Version == 2 {
|
||||
var b JsonBlock2
|
||||
if err = json.Unmarshal(data, &b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
} else if version.Version == 0 || version.Version == 1 {
|
||||
|
||||
var b JsonBlock1
|
||||
if err = json.Unmarshal(data, &b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown version %d", version.Version)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<div style="text-align: center">
|
||||
<h2>Weekly Miners</h2>
|
||||
<p>This is a list of the miners that have shares in the last 28 windows, or about seven days.</p>
|
||||
<p>This is a list of the miners that have shares in the last 28 full windows, or about seven days.</p>
|
||||
<p class="small">Entries are sorted by current window "weight". There are more total miners currently active, but without a share to show at the moment.</p>
|
||||
<p class="small">Pool share % is relative to whole pool hashrate. Miners can join or leave anytime and there is no ceiling limit. </p>
|
||||
<table class="center datatable" style="border-collapse: collapse; max-width: calc(4em + 12em + 8em + 8em + 12em + 32em)">
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
address2 "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/p2pool"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
|
@ -113,6 +114,7 @@ func toFloat64(t any) float64 {
|
|||
}
|
||||
|
||||
func main() {
|
||||
client.SetDefaultClientSettings(os.Getenv("MONEROD_RPC_URL"))
|
||||
env := twig.New(&loader{})
|
||||
|
||||
render := func(writer http.ResponseWriter, template string, ctx map[string]stick.Value) {
|
||||
|
@ -629,12 +631,13 @@ func main() {
|
|||
|
||||
poolInfo := getFromAPI("pool_info", 5).(map[string]any)
|
||||
|
||||
windowSize := toUint64(poolInfo["sidechain"].(map[string]any)["consensus"].(map[string]any)["pplns_window"])
|
||||
currentWindowSize := toUint64(poolInfo["sidechain"].(map[string]any)["window_size"])
|
||||
shareCount := uint64(currentWindowSize)
|
||||
size := uint64(30)
|
||||
cacheTime := 30
|
||||
if params.Has("weekly") {
|
||||
shareCount = sidechain.PPLNSWindow * 4 * 7
|
||||
shareCount = windowSize * 4 * 7
|
||||
size *= 2
|
||||
if params.Has("refresh") {
|
||||
writer.Header().Set("refresh", "3600")
|
||||
|
|
|
@ -1,461 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var NilHash = types.ZeroHash
|
||||
var UndefinedHash types.Hash
|
||||
var UndefinedDifficulty types.Difficulty
|
||||
|
||||
func init() {
|
||||
copy(NilHash[:], bytes.Repeat([]byte{0}, types.HashSize))
|
||||
copy(UndefinedHash[:], bytes.Repeat([]byte{0xff}, types.HashSize))
|
||||
UndefinedDifficulty = types.DifficultyFromBytes(bytes.Repeat([]byte{0xff}, types.DifficultySize))
|
||||
}
|
||||
|
||||
type BlockInterface interface {
|
||||
GetBlock() *Block
|
||||
}
|
||||
|
||||
type BlockCoinbase struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
type BlockMainData struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
Found bool `json:"found"`
|
||||
|
||||
//Orphan extra JSON field, do not use
|
||||
Orphan bool `json:"orphan,omitempty"`
|
||||
}
|
||||
|
||||
type JSONBlockParent struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
}
|
||||
|
||||
type JSONUncleBlockSimple struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
Weight uint64 `json:"weight"`
|
||||
}
|
||||
|
||||
type JSONCoinbaseOutput struct {
|
||||
Amount uint64 `json:"amount"`
|
||||
Index uint64 `json:"index"`
|
||||
Address string `json:"address"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
}
|
||||
|
||||
type JSONUncleBlockExtra struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
Miner string `json:"miner"`
|
||||
MinerAlias string `json:"miner_alias,omitempty"`
|
||||
PowHash types.Hash `json:"pow"`
|
||||
Weight uint64 `json:"weight"`
|
||||
}
|
||||
|
||||
type Block struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
PreviousId types.Hash `json:"previous_id"`
|
||||
Coinbase BlockCoinbase `json:"coinbase"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
MinerId uint64 `json:"-"`
|
||||
//Address extra JSON field, do not use
|
||||
Address string `json:"miner,omitempty"`
|
||||
MinerAlias string `json:"miner_alias,omitempty"`
|
||||
PowHash types.Hash `json:"pow"`
|
||||
|
||||
Main BlockMainData `json:"main"`
|
||||
|
||||
Template struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
} `json:"template"`
|
||||
|
||||
//Lock extra JSON field, do not use
|
||||
Lock sync.Mutex `json:"-"`
|
||||
|
||||
//Parent extra JSON field, do not use
|
||||
Parent *JSONBlockParent `json:"parent,omitempty"`
|
||||
//Uncles extra JSON field, do not use
|
||||
Uncles []any `json:"uncles"`
|
||||
//Weight extra JSON field, do not use
|
||||
Weight uint64 `json:"weight"`
|
||||
|
||||
//Orphan extra JSON field, do not use
|
||||
Orphan bool `json:"orphan,omitempty"`
|
||||
|
||||
//Invalid extra JSON field, do not use
|
||||
Invalid *bool `json:"invalid,omitempty"`
|
||||
}
|
||||
|
||||
func NewBlockFromBinaryBlock(getSeedByHeight mainblock.GetSeedByHeightFunc, getDifficultyByHeight mainblock.GetDifficultyByHeightFunc, db *Database, b *sidechain.PoolBlock, knownUncles sidechain.UniquePoolBlockSlice, errOnUncles bool) (block *Block, uncles []*UncleBlock, err error) {
|
||||
if b == nil {
|
||||
return nil, nil, errors.New("nil block")
|
||||
}
|
||||
miner := db.GetOrCreateMinerByAddress(b.GetAddress().ToBase58())
|
||||
if miner == nil {
|
||||
return nil, nil, errors.New("could not get or create miner")
|
||||
}
|
||||
|
||||
block = &Block{
|
||||
Id: types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId)),
|
||||
Height: b.Side.Height,
|
||||
PreviousId: b.Side.Parent,
|
||||
Coinbase: BlockCoinbase{
|
||||
Id: b.Main.Coinbase.Id(),
|
||||
Reward: func() (v uint64) {
|
||||
for _, o := range b.Main.Coinbase.Outputs {
|
||||
v += o.Reward
|
||||
}
|
||||
return
|
||||
}(),
|
||||
PrivateKey: b.Side.CoinbasePrivateKey,
|
||||
},
|
||||
Difficulty: b.Side.Difficulty,
|
||||
Timestamp: b.Main.Timestamp,
|
||||
MinerId: miner.Id(),
|
||||
PowHash: b.PowHash(getSeedByHeight),
|
||||
Main: BlockMainData{
|
||||
Id: b.MainId(),
|
||||
Height: b.Main.Coinbase.GenHeight,
|
||||
Found: b.IsProofHigherThanMainDifficulty(getDifficultyByHeight, getSeedByHeight),
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: b.Main.PreviousId,
|
||||
Difficulty: b.MainDifficulty(getDifficultyByHeight),
|
||||
},
|
||||
}
|
||||
|
||||
for _, u := range b.Side.Uncles {
|
||||
if uncle := knownUncles.Get(u); uncle != nil {
|
||||
uncleMiner := db.GetOrCreateMinerByAddress(uncle.GetAddress().ToBase58())
|
||||
if uncleMiner == nil {
|
||||
return nil, nil, errors.New("could not get or create miner")
|
||||
}
|
||||
uncles = append(uncles, &UncleBlock{
|
||||
Block: Block{
|
||||
Id: types.HashFromBytes(uncle.CoinbaseExtra(sidechain.SideTemplateId)),
|
||||
Height: uncle.Side.Height,
|
||||
PreviousId: uncle.Side.Parent,
|
||||
Coinbase: BlockCoinbase{
|
||||
Id: uncle.Main.Coinbase.Id(),
|
||||
Reward: func() (v uint64) {
|
||||
for _, o := range uncle.Main.Coinbase.Outputs {
|
||||
v += o.Reward
|
||||
}
|
||||
return
|
||||
}(),
|
||||
PrivateKey: uncle.Side.CoinbasePrivateKey.AsBytes(),
|
||||
},
|
||||
Difficulty: uncle.Side.Difficulty,
|
||||
Timestamp: uncle.Main.Timestamp,
|
||||
MinerId: uncleMiner.Id(),
|
||||
PowHash: uncle.PowHash(getSeedByHeight),
|
||||
Main: BlockMainData{
|
||||
Id: uncle.MainId(),
|
||||
Height: uncle.Main.Coinbase.GenHeight,
|
||||
Found: uncle.IsProofHigherThanMainDifficulty(getDifficultyByHeight, getSeedByHeight),
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: uncle.Main.PreviousId,
|
||||
Difficulty: uncle.MainDifficulty(getDifficultyByHeight),
|
||||
},
|
||||
},
|
||||
ParentId: block.Id,
|
||||
ParentHeight: block.Height,
|
||||
})
|
||||
} else if errOnUncles {
|
||||
return nil, nil, fmt.Errorf("could not find uncle %s", hex.EncodeToString(u[:]))
|
||||
}
|
||||
}
|
||||
|
||||
return block, uncles, nil
|
||||
}
|
||||
|
||||
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"`
|
||||
|
||||
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"`
|
||||
} `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"`
|
||||
|
||||
Uncles []struct {
|
||||
Diff uint64 `json:"diff,string"`
|
||||
PrevId types.Hash `json:"prev_id"`
|
||||
Ts uint64 `json:"ts,string"`
|
||||
MHeight uint64 `json:"mheight,string"`
|
||||
PrevHash types.Hash `json:"prev_hash"`
|
||||
Height uint64 `json:"height,string"`
|
||||
Wallet *address.Address `json:"wallet"`
|
||||
Id types.Hash `json:"id"`
|
||||
} `json:"uncles,omitempty"`
|
||||
}
|
||||
|
||||
type versionBlock struct {
|
||||
Version uint64 `json:"version,string"`
|
||||
}
|
||||
|
||||
func NewBlockFromJSONBlock(db *Database, data []byte) (block *Block, uncles []*UncleBlock, err error) {
|
||||
var version versionBlock
|
||||
|
||||
if err = json.Unmarshal(data, &version); err != nil {
|
||||
return nil, nil, err
|
||||
} else {
|
||||
if version.Version == 2 {
|
||||
|
||||
var b JsonBlock2
|
||||
if err = json.Unmarshal(data, &b); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
miner := db.GetOrCreateMinerByAddress(b.Wallet.ToBase58())
|
||||
if miner == nil {
|
||||
return nil, nil, errors.New("could not get or create miner")
|
||||
}
|
||||
|
||||
block = &Block{
|
||||
Id: b.Id,
|
||||
Height: b.Height,
|
||||
PreviousId: b.PrevId,
|
||||
Coinbase: BlockCoinbase{
|
||||
Id: b.CoinbaseId,
|
||||
Reward: b.CoinbaseReward,
|
||||
PrivateKey: b.CoinbasePriv,
|
||||
},
|
||||
Difficulty: b.Diff,
|
||||
Timestamp: b.Ts,
|
||||
MinerId: miner.Id(),
|
||||
PowHash: b.PowHash,
|
||||
Main: BlockMainData{
|
||||
Id: b.MainId,
|
||||
Height: b.MainHeight,
|
||||
Found: b.MainFound == "true",
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: b.MinerMainId,
|
||||
Difficulty: b.MinerMainDiff,
|
||||
},
|
||||
}
|
||||
|
||||
if block.IsProofHigherThanDifficulty() {
|
||||
block.Main.Found = true
|
||||
}
|
||||
|
||||
for _, u := range b.Uncles {
|
||||
uncleMiner := db.GetOrCreateMinerByAddress(u.Wallet.ToBase58())
|
||||
if uncleMiner == nil {
|
||||
return nil, nil, errors.New("could not get or create miner")
|
||||
}
|
||||
|
||||
uncle := &UncleBlock{
|
||||
Block: Block{
|
||||
Id: u.Id,
|
||||
Height: u.Height,
|
||||
PreviousId: u.PrevId,
|
||||
Coinbase: BlockCoinbase{
|
||||
Id: u.CoinbaseId,
|
||||
Reward: u.CoinbaseReward,
|
||||
PrivateKey: u.CoinbasePriv,
|
||||
},
|
||||
Difficulty: u.Diff,
|
||||
Timestamp: u.Ts,
|
||||
MinerId: uncleMiner.Id(),
|
||||
PowHash: u.PowHash,
|
||||
Main: BlockMainData{
|
||||
Id: u.MainId,
|
||||
Height: u.MainHeight,
|
||||
Found: u.MainFound == "true",
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: u.MinerMainId,
|
||||
Difficulty: u.MinerMainDiff,
|
||||
},
|
||||
},
|
||||
ParentId: block.Id,
|
||||
ParentHeight: block.Height,
|
||||
}
|
||||
|
||||
if uncle.Block.IsProofHigherThanDifficulty() {
|
||||
uncle.Block.Main.Found = true
|
||||
}
|
||||
|
||||
uncles = append(uncles, uncle)
|
||||
}
|
||||
return block, uncles, nil
|
||||
} else if version.Version == 0 || version.Version == 1 {
|
||||
|
||||
var b JsonBlock1
|
||||
if err = json.Unmarshal(data, &b); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
miner := db.GetOrCreateMinerByAddress(b.Wallet.ToBase58())
|
||||
if miner == nil {
|
||||
return nil, nil, errors.New("could not get or create miner")
|
||||
}
|
||||
|
||||
block = &Block{
|
||||
Id: b.Id,
|
||||
Height: b.Height,
|
||||
PreviousId: b.PrevId,
|
||||
Coinbase: BlockCoinbase{
|
||||
Id: b.TxCoinbase,
|
||||
Reward: 0,
|
||||
PrivateKey: b.TxPriv,
|
||||
},
|
||||
Difficulty: types.DifficultyFrom64(b.Diff),
|
||||
Timestamp: b.Ts,
|
||||
MinerId: miner.Id(),
|
||||
PowHash: b.PowHash,
|
||||
Main: BlockMainData{
|
||||
Id: b.MHash,
|
||||
Height: b.MHeight,
|
||||
Found: b.BlockFound == "true",
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: UndefinedHash,
|
||||
Difficulty: UndefinedDifficulty,
|
||||
},
|
||||
}
|
||||
|
||||
for _, u := range b.Uncles {
|
||||
uncleMiner := db.GetOrCreateMinerByAddress(u.Wallet.ToBase58())
|
||||
if uncleMiner == nil {
|
||||
return nil, nil, errors.New("could not get or create miner")
|
||||
}
|
||||
|
||||
uncle := &UncleBlock{
|
||||
Block: Block{
|
||||
Id: u.Id,
|
||||
Height: u.Height,
|
||||
PreviousId: u.PrevId,
|
||||
Coinbase: BlockCoinbase{
|
||||
Id: NilHash,
|
||||
Reward: 0,
|
||||
PrivateKey: crypto.PrivateKeyBytes(NilHash),
|
||||
},
|
||||
Difficulty: types.DifficultyFrom64(b.Diff),
|
||||
Timestamp: u.Ts,
|
||||
MinerId: uncleMiner.Id(),
|
||||
PowHash: NilHash,
|
||||
Main: BlockMainData{
|
||||
Id: NilHash,
|
||||
Height: 0,
|
||||
Found: false,
|
||||
},
|
||||
Template: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
}{
|
||||
Id: UndefinedHash,
|
||||
Difficulty: UndefinedDifficulty,
|
||||
},
|
||||
},
|
||||
ParentId: block.Id,
|
||||
ParentHeight: block.Height,
|
||||
}
|
||||
|
||||
uncles = append(uncles, uncle)
|
||||
}
|
||||
return block, uncles, nil
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("unknown version %d", version.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Block) GetBlock() *Block {
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Block) IsProofHigherThanDifficulty() bool {
|
||||
return b.Template.Difficulty.CheckPoW(b.PowHash)
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type CoinbaseTransaction struct {
|
||||
id types.Hash
|
||||
privateKey crypto.PrivateKeyBytes
|
||||
outputs []*CoinbaseTransactionOutput
|
||||
}
|
||||
|
||||
func NewCoinbaseTransaction(id types.Hash, privateKey crypto.PrivateKeyBytes, outputs []*CoinbaseTransactionOutput) *CoinbaseTransaction {
|
||||
return &CoinbaseTransaction{
|
||||
id: id,
|
||||
privateKey: privateKey,
|
||||
outputs: outputs,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *CoinbaseTransaction) Outputs() []*CoinbaseTransactionOutput {
|
||||
return t.outputs
|
||||
}
|
||||
|
||||
func (t *CoinbaseTransaction) Reward() (result uint64) {
|
||||
for _, o := range t.outputs {
|
||||
result += o.amount
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *CoinbaseTransaction) OutputByIndex(index uint64) *CoinbaseTransactionOutput {
|
||||
if uint64(len(t.outputs)) > index {
|
||||
return t.outputs[index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CoinbaseTransaction) OutputByMiner(miner uint64) *CoinbaseTransactionOutput {
|
||||
if i := slices.IndexFunc(t.outputs, func(e *CoinbaseTransactionOutput) bool {
|
||||
return e.Miner() == miner
|
||||
}); i != -1 {
|
||||
return t.outputs[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CoinbaseTransaction) PrivateKey() crypto.PrivateKeyBytes {
|
||||
return t.privateKey
|
||||
}
|
||||
|
||||
func (t *CoinbaseTransaction) Id() types.Hash {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t *CoinbaseTransaction) GetEphemeralPublicKey(miner *Miner, index int64) crypto.PublicKey {
|
||||
if index != -1 {
|
||||
return address.GetEphemeralPublicKey(miner.MoneroAddress(), &t.privateKey, uint64(index))
|
||||
} else {
|
||||
return address.GetEphemeralPublicKey(miner.MoneroAddress(), &t.privateKey, t.OutputByMiner(miner.Id()).Index())
|
||||
}
|
||||
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package database
|
||||
|
||||
import "git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
|
||||
type CoinbaseTransactionOutput struct {
|
||||
id types.Hash
|
||||
index uint64
|
||||
amount uint64
|
||||
miner uint64
|
||||
}
|
||||
|
||||
func NewCoinbaseTransactionOutput(id types.Hash, index, amount, miner uint64) *CoinbaseTransactionOutput {
|
||||
return &CoinbaseTransactionOutput{
|
||||
id: id,
|
||||
index: index,
|
||||
amount: amount,
|
||||
miner: miner,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *CoinbaseTransactionOutput) Miner() uint64 {
|
||||
return o.miner
|
||||
}
|
||||
|
||||
func (o *CoinbaseTransactionOutput) Amount() uint64 {
|
||||
return o.amount
|
||||
}
|
||||
|
||||
func (o *CoinbaseTransactionOutput) Index() uint64 {
|
||||
return o.index
|
||||
}
|
||||
|
||||
func (o *CoinbaseTransactionOutput) Id() types.Hash {
|
||||
return o.id
|
||||
}
|
1027
database/database.go
1027
database/database.go
|
@ -1,1027 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
_ "github.com/lib/pq"
|
||||
"log"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
handle *sql.DB
|
||||
statements struct {
|
||||
GetMinerById *sql.Stmt
|
||||
GetMinerByAddress *sql.Stmt
|
||||
GetMinerByAlias *sql.Stmt
|
||||
GetMinerByAddressBounds *sql.Stmt
|
||||
InsertMiner *sql.Stmt
|
||||
GetUnclesByParentId *sql.Stmt
|
||||
}
|
||||
|
||||
cacheLock sync.RWMutex
|
||||
minerCache map[uint64]*Miner
|
||||
unclesByParentCache map[types.Hash][]*UncleBlock
|
||||
}
|
||||
|
||||
func NewDatabase(connStr string) (db *Database, err error) {
|
||||
db = &Database{
|
||||
minerCache: make(map[uint64]*Miner, 4096),
|
||||
unclesByParentCache: make(map[types.Hash][]*UncleBlock, p2pool.PPLNSWindow*16),
|
||||
}
|
||||
if db.handle, err = sql.Open("postgres", connStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if db.statements.GetMinerById, err = db.handle.Prepare("SELECT id, alias, address FROM miners WHERE id = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if db.statements.GetMinerByAddress, err = db.handle.Prepare("SELECT id, alias, address FROM miners WHERE address = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if db.statements.GetMinerByAlias, err = db.handle.Prepare("SELECT id, alias, address FROM miners WHERE alias = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if db.statements.GetMinerByAddressBounds, err = db.handle.Prepare("SELECT id, alias, address FROM miners WHERE address LIKE $1 AND address LIKE $2;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if db.statements.InsertMiner, err = db.handle.Prepare("INSERT INTO miners (address) VALUES ($1) RETURNING id, alias, address;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if db.statements.GetUnclesByParentId, err = db.PrepareUncleBlocksByQueryStatement("WHERE parent_id = encode($1, 'hex');"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
//cache methods
|
||||
|
||||
func (db *Database) GetMiner(miner uint64) *Miner {
|
||||
if m := func() *Miner {
|
||||
db.cacheLock.RLock()
|
||||
defer db.cacheLock.RUnlock()
|
||||
return db.minerCache[miner]
|
||||
}(); m != nil {
|
||||
return m
|
||||
} else if m = db.getMiner(miner); m != nil {
|
||||
db.cacheLock.Lock()
|
||||
defer db.cacheLock.Unlock()
|
||||
db.minerCache[miner] = m
|
||||
return m
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetUnclesByParentId(id types.Hash) chan *UncleBlock {
|
||||
if c := func() chan *UncleBlock {
|
||||
db.cacheLock.RLock()
|
||||
defer db.cacheLock.RUnlock()
|
||||
if s, ok := db.unclesByParentCache[id]; ok {
|
||||
c := make(chan *UncleBlock, len(s))
|
||||
defer close(c)
|
||||
for _, u := range s {
|
||||
c <- u
|
||||
}
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}(); c != nil {
|
||||
return c
|
||||
} else if c = db.getUnclesByParentId(id); c != nil {
|
||||
c2 := make(chan *UncleBlock)
|
||||
go func() {
|
||||
val := make([]*UncleBlock, 0, 6)
|
||||
defer func() {
|
||||
db.cacheLock.Lock()
|
||||
defer db.cacheLock.Unlock()
|
||||
if len(db.unclesByParentCache) >= p2pool.PPLNSWindow*4*30 {
|
||||
for k := range db.unclesByParentCache {
|
||||
delete(db.unclesByParentCache, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
db.unclesByParentCache[id] = val
|
||||
}()
|
||||
defer close(c2)
|
||||
for u := range c {
|
||||
val = append(val, u)
|
||||
c2 <- u
|
||||
}
|
||||
}()
|
||||
return c2
|
||||
} else {
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) getMiner(miner uint64) *Miner {
|
||||
if rows, err := db.statements.GetMinerById.Query(miner); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
m := &Miner{}
|
||||
if err = rows.Scan(&m.id, &m.alias, &m.addr); err != nil {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetMinerByAlias(alias string) *Miner {
|
||||
if rows, err := db.statements.GetMinerByAlias.Query(alias); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
m := &Miner{}
|
||||
if err = rows.Scan(&m.id, &m.alias, &m.addr); err != nil {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetMinerByAddress(addr string) *Miner {
|
||||
if rows, err := db.statements.GetMinerByAddress.Query(addr); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
m := &Miner{}
|
||||
if err = rows.Scan(&m.id, &m.alias, &m.addr); err != nil {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetOrCreateMinerByAddress(addr string) *Miner {
|
||||
if m := db.GetMinerByAddress(addr); m != nil {
|
||||
return m
|
||||
} else {
|
||||
if rows, err := db.statements.InsertMiner.Query(addr); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
m = &Miner{}
|
||||
if err = rows.Scan(&m.id, &m.alias, &m.addr); err != nil {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetMinerByAddressBounds(addrStart, addrEnd string) *Miner {
|
||||
if rows, err := db.statements.GetMinerByAddressBounds.Query(addrStart+"%", "%"+addrEnd); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
m := &Miner{}
|
||||
if err = rows.Scan(&m.id, &m.alias, &m.addr); err != nil {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) SetMinerAlias(minerId uint64, alias string) error {
|
||||
miner := db.GetMiner(minerId)
|
||||
if miner == nil {
|
||||
return nil
|
||||
}
|
||||
if alias == "" {
|
||||
if err := db.Query("UPDATE miners SET alias = NULL WHERE id = $1;", nil, miner.Id()); err != nil {
|
||||
return err
|
||||
}
|
||||
miner.alias.String = ""
|
||||
miner.alias.Valid = false
|
||||
} else {
|
||||
if err := db.Query("UPDATE miners SET alias = $2 WHERE id = $1;", nil, miner.Id(), alias); err != nil {
|
||||
return err
|
||||
}
|
||||
miner.alias.String = alias
|
||||
miner.alias.Valid = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RowScanInterface interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
func (db *Database) Query(query string, callback func(row RowScanInterface) error, params ...any) error {
|
||||
if stmt, err := db.handle.Prepare(query); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer stmt.Close()
|
||||
|
||||
return db.QueryStatement(stmt, callback, params...)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) QueryStatement(stmt *sql.Stmt, callback func(row RowScanInterface) error, params ...any) error {
|
||||
if rows, err := stmt.Query(params...); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer rows.Close()
|
||||
for callback != nil && rows.Next() {
|
||||
//callback will call sql.Rows.Scan
|
||||
if err = callback(rows); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) PrepareBlocksByQueryStatement(where string) (stmt *sql.Stmt, err error) {
|
||||
return db.handle.Prepare(fmt.Sprintf("SELECT decode(id, 'hex'), height, decode(previous_id, 'hex'), decode(coinbase_id, 'hex'), coinbase_reward, decode(coinbase_privkey, 'hex'), difficulty, timestamp, miner, decode(pow_hash, 'hex'), main_height, decode(main_id, 'hex'), main_found, decode(miner_main_id, 'hex'), miner_main_difficulty FROM blocks %s;", where))
|
||||
}
|
||||
|
||||
func (db *Database) PrepareUncleBlocksByQueryStatement(where string) (stmt *sql.Stmt, err error) {
|
||||
return db.handle.Prepare(fmt.Sprintf("SELECT decode(parent_id, 'hex'), parent_height, decode(id, 'hex'), height, decode(previous_id, 'hex'), decode(coinbase_id, 'hex'), coinbase_reward, decode(coinbase_privkey, 'hex'), difficulty, timestamp, miner, decode(pow_hash, 'hex'), main_height, decode(main_id, 'hex'), main_found, decode(miner_main_id, 'hex'), miner_main_difficulty FROM uncles %s;", where))
|
||||
}
|
||||
|
||||
func (db *Database) GetBlocksByQuery(where string, params ...any) chan *Block {
|
||||
if stmt, err := db.PrepareBlocksByQueryStatement(where); err != nil {
|
||||
returnChannel := make(chan *Block)
|
||||
close(returnChannel)
|
||||
return returnChannel
|
||||
} else {
|
||||
return db.GetBlocksByQueryStatement(stmt, params...)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetBlocksByQueryStatement(stmt *sql.Stmt, params ...any) chan *Block {
|
||||
returnChannel := make(chan *Block)
|
||||
go func() {
|
||||
defer close(returnChannel)
|
||||
err := db.QueryStatement(stmt, func(row RowScanInterface) (err error) {
|
||||
block := &Block{}
|
||||
|
||||
var difficultyHex, minerMainDifficultyHex string
|
||||
|
||||
var IdPtr, PreviousIdPtr, CoinbaseIdPtr, CoinbasePrivateKeyPtr, PowHashPtr, MainIdPtr, MinerMainId sql.RawBytes
|
||||
|
||||
if err = row.Scan(&IdPtr, &block.Height, &PreviousIdPtr, &CoinbaseIdPtr, &block.Coinbase.Reward, &CoinbasePrivateKeyPtr, &difficultyHex, &block.Timestamp, &block.MinerId, &PowHashPtr, &block.Main.Height, &MainIdPtr, &block.Main.Found, &MinerMainId, &minerMainDifficultyHex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copy(block.Id[:], IdPtr)
|
||||
copy(block.PreviousId[:], PreviousIdPtr)
|
||||
copy(block.Coinbase.Id[:], CoinbaseIdPtr)
|
||||
copy(block.Coinbase.PrivateKey[:], CoinbasePrivateKeyPtr)
|
||||
copy(block.PowHash[:], PowHashPtr)
|
||||
copy(block.Main.Id[:], MainIdPtr)
|
||||
copy(block.Template.Id[:], MinerMainId)
|
||||
|
||||
block.Difficulty, _ = types.DifficultyFromString(difficultyHex)
|
||||
block.Template.Difficulty, _ = types.DifficultyFromString(minerMainDifficultyHex)
|
||||
|
||||
returnChannel <- block
|
||||
|
||||
return nil
|
||||
}, params...)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return returnChannel
|
||||
}
|
||||
|
||||
func (db *Database) GetUncleBlocksByQuery(where string, params ...any) chan *UncleBlock {
|
||||
if stmt, err := db.PrepareUncleBlocksByQueryStatement(where); err != nil {
|
||||
returnChannel := make(chan *UncleBlock)
|
||||
close(returnChannel)
|
||||
return returnChannel
|
||||
} else {
|
||||
return db.GetUncleBlocksByQueryStatement(stmt, params...)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetUncleBlocksByQueryStatement(stmt *sql.Stmt, params ...any) chan *UncleBlock {
|
||||
returnChannel := make(chan *UncleBlock)
|
||||
go func() {
|
||||
defer close(returnChannel)
|
||||
err := db.QueryStatement(stmt, func(row RowScanInterface) (err error) {
|
||||
uncle := &UncleBlock{}
|
||||
block := &uncle.Block
|
||||
|
||||
var difficultyHex, minerMainDifficultyHex string
|
||||
|
||||
var ParentId, IdPtr, PreviousIdPtr, CoinbaseIdPtr, CoinbasePrivateKeyPtr, PowHashPtr, MainIdPtr, MinerMainId sql.RawBytes
|
||||
|
||||
if err = row.Scan(&ParentId, &uncle.ParentHeight, &IdPtr, &block.Height, &PreviousIdPtr, &CoinbaseIdPtr, &block.Coinbase.Reward, &CoinbasePrivateKeyPtr, &difficultyHex, &block.Timestamp, &block.MinerId, &PowHashPtr, &block.Main.Height, &MainIdPtr, &block.Main.Found, &MinerMainId, &minerMainDifficultyHex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copy(uncle.ParentId[:], ParentId)
|
||||
copy(block.Id[:], IdPtr)
|
||||
copy(block.PreviousId[:], PreviousIdPtr)
|
||||
copy(block.Coinbase.Id[:], CoinbaseIdPtr)
|
||||
copy(block.Coinbase.PrivateKey[:], CoinbasePrivateKeyPtr)
|
||||
copy(block.PowHash[:], PowHashPtr)
|
||||
copy(block.Main.Id[:], MainIdPtr)
|
||||
copy(block.Template.Id[:], MinerMainId)
|
||||
|
||||
block.Difficulty, _ = types.DifficultyFromString(difficultyHex)
|
||||
block.Template.Difficulty, _ = types.DifficultyFromString(minerMainDifficultyHex)
|
||||
|
||||
returnChannel <- uncle
|
||||
|
||||
return nil
|
||||
}, params...)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return returnChannel
|
||||
}
|
||||
|
||||
func (db *Database) GetBlockById(id types.Hash) *Block {
|
||||
r := db.GetBlocksByQuery("WHERE id = encode($1, 'hex');", id[:])
|
||||
defer func() {
|
||||
for range r {
|
||||
|
||||
}
|
||||
}()
|
||||
return <-r
|
||||
}
|
||||
|
||||
func (db *Database) GetBlockByPreviousId(id types.Hash) *Block {
|
||||
r := db.GetBlocksByQuery("WHERE previous_id = encode($1, 'hex');", id[:])
|
||||
defer func() {
|
||||
for range r {
|
||||
|
||||
}
|
||||
}()
|
||||
return <-r
|
||||
}
|
||||
|
||||
func (db *Database) GetBlockByHeight(height uint64) *Block {
|
||||
r := db.GetBlocksByQuery("WHERE height = $1;", height)
|
||||
defer func() {
|
||||
for range r {
|
||||
|
||||
}
|
||||
}()
|
||||
return <-r
|
||||
}
|
||||
|
||||
func (db *Database) GetBlocksInWindow(startHeight *uint64, windowSize uint64) chan *Block {
|
||||
if windowSize == 0 {
|
||||
windowSize = p2pool.PPLNSWindow
|
||||
}
|
||||
|
||||
if startHeight == nil {
|
||||
return db.GetBlocksByQuery("WHERE height > ((SELECT MAX(height) FROM blocks) - $1) AND height <= ((SELECT MAX(height) FROM blocks)) ORDER BY height DESC", windowSize)
|
||||
} else {
|
||||
return db.GetBlocksByQuery("WHERE height > ($1) AND height <= ($2) ORDER BY height DESC", *startHeight-windowSize, *startHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetBlocksByMinerIdInWindow(minerId uint64, startHeight *uint64, windowSize uint64) chan *Block {
|
||||
if windowSize == 0 {
|
||||
windowSize = p2pool.PPLNSWindow
|
||||
}
|
||||
|
||||
if startHeight == nil {
|
||||
return db.GetBlocksByQuery("WHERE height > ((SELECT MAX(height) FROM blocks) - $2) AND height <= ((SELECT MAX(height) FROM blocks)) AND miner = $1 ORDER BY height DESC", minerId, windowSize)
|
||||
} else {
|
||||
return db.GetBlocksByQuery("WHERE height > ($2) AND height <= ($3) AND miner = $1 ORDER BY height DESC", minerId, *startHeight-windowSize, *startHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetChainTip() *Block {
|
||||
r := db.GetBlocksByQuery("ORDER BY height DESC LIMIT 1;")
|
||||
defer func() {
|
||||
for range r {
|
||||
|
||||
}
|
||||
}()
|
||||
return <-r
|
||||
}
|
||||
|
||||
func (db *Database) GetLastFound() BlockInterface {
|
||||
r := db.GetAllFound(1, 0)
|
||||
defer func() {
|
||||
for range r {
|
||||
|
||||
}
|
||||
}()
|
||||
return <-r
|
||||
}
|
||||
|
||||
func (db *Database) GetUncleById(id types.Hash) *UncleBlock {
|
||||
r := db.GetUncleBlocksByQuery("WHERE id = encode($1, 'hex');", id[:])
|
||||
defer func() {
|
||||
for range r {
|
||||
|
||||
}
|
||||
}()
|
||||
return <-r
|
||||
}
|
||||
|
||||
func (db *Database) getUnclesByParentId(id types.Hash) chan *UncleBlock {
|
||||
return db.GetUncleBlocksByQueryStatement(db.statements.GetUnclesByParentId, id[:])
|
||||
}
|
||||
|
||||
func (db *Database) GetUnclesByParentHeight(height uint64) chan *UncleBlock {
|
||||
return db.GetUncleBlocksByQuery("WHERE parent_height = $1;", height)
|
||||
}
|
||||
|
||||
func (db *Database) GetUnclesInWindow(startHeight *uint64, windowSize uint64) chan *UncleBlock {
|
||||
if windowSize == 0 {
|
||||
windowSize = p2pool.PPLNSWindow
|
||||
}
|
||||
|
||||
//TODO add p2pool.UncleBlockDepth ?
|
||||
if startHeight == nil {
|
||||
return db.GetUncleBlocksByQuery("WHERE parent_height > ((SELECT MAX(height) FROM blocks) - $1) AND parent_height <= ((SELECT MAX(height) FROM blocks)) AND height > ((SELECT MAX(height) FROM blocks) - $1) AND height <= ((SELECT MAX(height) FROM blocks)) ORDER BY height DESC", windowSize)
|
||||
} else {
|
||||
return db.GetUncleBlocksByQuery("WHERE parent_height > ($1) AND parent_height <= ($2) AND height > ($1) AND height <= ($2) ORDER BY height DESC", *startHeight-windowSize, *startHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetUnclesByMinerIdInWindow(minerId uint64, startHeight *uint64, windowSize uint64) chan *UncleBlock {
|
||||
if windowSize == 0 {
|
||||
windowSize = p2pool.PPLNSWindow
|
||||
}
|
||||
|
||||
//TODO add p2pool.UncleBlockDepth ?
|
||||
if startHeight == nil {
|
||||
return db.GetUncleBlocksByQuery("WHERE parent_height > ((SELECT MAX(height) FROM blocks) - $2) AND parent_height <= ((SELECT MAX(height) FROM blocks)) AND height > ((SELECT MAX(height) FROM blocks) - $2) AND height <= ((SELECT MAX(height) FROM blocks)) AND miner = $1 ORDER BY height DESC", minerId, windowSize)
|
||||
} else {
|
||||
return db.GetUncleBlocksByQuery("WHERE parent_height > ($2) AND parent_height <= ($3) AND height > ($2) AND height <= ($3) AND miner = $1 ORDER BY height DESC", minerId, *startHeight-windowSize, *startHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetAllFound(limit, minerId uint64) chan BlockInterface {
|
||||
blocks := db.GetFound(limit, minerId)
|
||||
uncles := db.GetFoundUncles(limit, minerId)
|
||||
|
||||
result := make(chan BlockInterface)
|
||||
go func() {
|
||||
defer close(result)
|
||||
defer func() {
|
||||
for range blocks {
|
||||
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
for range uncles {
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
var i uint64
|
||||
|
||||
var currentBlock *Block
|
||||
var currentUncle *UncleBlock
|
||||
|
||||
for {
|
||||
var current BlockInterface
|
||||
|
||||
if limit != 0 && i >= limit {
|
||||
break
|
||||
}
|
||||
|
||||
if currentBlock == nil {
|
||||
currentBlock = <-blocks
|
||||
}
|
||||
if currentUncle == nil {
|
||||
currentUncle = <-uncles
|
||||
}
|
||||
|
||||
if currentBlock != nil {
|
||||
if current == nil || currentBlock.Main.Height > current.GetBlock().Main.Height {
|
||||
current = currentBlock
|
||||
}
|
||||
}
|
||||
|
||||
if currentUncle != nil {
|
||||
if current == nil || currentUncle.Block.Main.Height > current.GetBlock().Main.Height {
|
||||
current = currentUncle
|
||||
}
|
||||
}
|
||||
|
||||
if current == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if currentBlock == current {
|
||||
currentBlock = nil
|
||||
} else if currentUncle == current {
|
||||
currentUncle = nil
|
||||
}
|
||||
|
||||
result <- current
|
||||
|
||||
i++
|
||||
}
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
func (db *Database) GetShares(limit uint64, minerId uint64, onlyBlocks bool) chan BlockInterface {
|
||||
var blocks chan *Block
|
||||
var uncles chan *UncleBlock
|
||||
|
||||
result := make(chan BlockInterface)
|
||||
|
||||
if limit == 0 {
|
||||
if minerId != 0 {
|
||||
blocks = db.GetBlocksByQuery("WHERE miner = $1 ORDER BY height DESC;", minerId)
|
||||
if !onlyBlocks {
|
||||
uncles = db.GetUncleBlocksByQuery("WHERE miner = $1 ORDER BY height DESC, timestamp DESC;", minerId)
|
||||
}
|
||||
} else {
|
||||
blocks = db.GetBlocksByQuery("ORDER BY height DESC;")
|
||||
if !onlyBlocks {
|
||||
uncles = db.GetUncleBlocksByQuery("ORDER BY height DESC, timestamp DESC;")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if minerId != 0 {
|
||||
blocks = db.GetBlocksByQuery("WHERE miner = $2 ORDER BY height DESC LIMIT $1;", limit, minerId)
|
||||
if !onlyBlocks {
|
||||
uncles = db.GetUncleBlocksByQuery("WHERE miner = $2 ORDER BY height DESC, timestamp DESC LIMIT $1;", limit, minerId)
|
||||
}
|
||||
} else {
|
||||
blocks = db.GetBlocksByQuery("ORDER BY height DESC LIMIT $1;", limit)
|
||||
if !onlyBlocks {
|
||||
uncles = db.GetUncleBlocksByQuery("ORDER BY height DESC, timestamp DESC LIMIT $1;", limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if onlyBlocks {
|
||||
go func() {
|
||||
defer close(result)
|
||||
for b := range blocks {
|
||||
result <- b
|
||||
}
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if blocks == nil {
|
||||
return
|
||||
}
|
||||
for range blocks {
|
||||
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if uncles == nil {
|
||||
return
|
||||
}
|
||||
for range uncles {
|
||||
|
||||
}
|
||||
}()
|
||||
defer close(result)
|
||||
|
||||
var i uint64
|
||||
|
||||
var currentBlock *Block
|
||||
var currentUncle *UncleBlock
|
||||
|
||||
for {
|
||||
var current BlockInterface
|
||||
|
||||
if limit != 0 && i >= limit {
|
||||
break
|
||||
}
|
||||
|
||||
if currentBlock == nil {
|
||||
currentBlock = <-blocks
|
||||
}
|
||||
if !onlyBlocks && currentUncle == nil {
|
||||
currentUncle = <-uncles
|
||||
}
|
||||
|
||||
if currentBlock != nil {
|
||||
if current == nil || currentBlock.Height > current.GetBlock().Height {
|
||||
current = currentBlock
|
||||
}
|
||||
}
|
||||
|
||||
if !onlyBlocks && currentUncle != nil {
|
||||
if current == nil || currentUncle.Block.Height > current.GetBlock().Height {
|
||||
current = currentUncle
|
||||
}
|
||||
}
|
||||
|
||||
if current == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if currentBlock == current {
|
||||
currentBlock = nil
|
||||
} else if !onlyBlocks && currentUncle == current {
|
||||
currentUncle = nil
|
||||
}
|
||||
|
||||
result <- current
|
||||
|
||||
i++
|
||||
}
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
func (db *Database) GetFound(limit, minerId uint64) chan *Block {
|
||||
if minerId == 0 {
|
||||
if limit == 0 {
|
||||
return db.GetBlocksByQuery("WHERE main_found IS TRUE ORDER BY main_height DESC;")
|
||||
} else {
|
||||
return db.GetBlocksByQuery("WHERE main_found IS TRUE ORDER BY main_height DESC LIMIT $1;", limit)
|
||||
}
|
||||
} else {
|
||||
if limit == 0 {
|
||||
return db.GetBlocksByQuery("WHERE main_found IS TRUE AND miner = $1 ORDER BY main_height DESC;", minerId)
|
||||
} else {
|
||||
return db.GetBlocksByQuery("WHERE main_found IS TRUE AND miner = $2 ORDER BY main_height DESC LIMIT $1;", limit, minerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetFoundUncles(limit, minerId uint64) chan *UncleBlock {
|
||||
if minerId == 0 {
|
||||
if limit == 0 {
|
||||
return db.GetUncleBlocksByQuery("WHERE main_found IS TRUE ORDER BY main_height DESC;")
|
||||
} else {
|
||||
return db.GetUncleBlocksByQuery("WHERE main_found IS TRUE ORDER BY main_height DESC LIMIT $1;", limit)
|
||||
}
|
||||
} else {
|
||||
if limit == 0 {
|
||||
return db.GetUncleBlocksByQuery("WHERE main_found IS TRUE AND miner = $1 ORDER BY main_height DESC;", minerId)
|
||||
} else {
|
||||
return db.GetUncleBlocksByQuery("WHERE main_found IS TRUE AND miner = $2 ORDER BY main_height DESC LIMIT $1;", limit, minerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) DeleteBlockById(id types.Hash) (n int, err error) {
|
||||
if block := db.GetBlockById(id); block == nil {
|
||||
return 0, nil
|
||||
} else {
|
||||
for {
|
||||
if err = db.Query("DELETE FROM coinbase_outputs WHERE id = (SELECT coinbase_id FROM blocks WHERE id = $1) OR id = (SELECT coinbase_id FROM uncles WHERE id = $1);", nil, block.Id.String()); err != nil {
|
||||
return n, err
|
||||
} else if err = db.Query("DELETE FROM uncles WHERE parent_id = $1;", nil, block.Id.String()); err != nil {
|
||||
return n, err
|
||||
} else if err = db.Query("DELETE FROM blocks WHERE id = $1;", nil, block.Id.String()); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
block = db.GetBlockByPreviousId(block.Id)
|
||||
if block == nil {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) SetBlockMainDifficulty(id types.Hash, difficulty types.Difficulty) error {
|
||||
if err := db.Query("UPDATE blocks SET miner_main_difficulty = $2 WHERE id = $1;", nil, id.String(), difficulty.String()); err != nil {
|
||||
return err
|
||||
} else if err = db.Query("UPDATE uncles SET miner_main_difficulty = $2 WHERE id = $1;", nil, id.String(), difficulty.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) SetBlockFound(id types.Hash, found bool) error {
|
||||
if err := db.Query("UPDATE blocks SET main_found = $2 WHERE id = $1;", nil, id.String(), found); err != nil {
|
||||
return err
|
||||
} else if err = db.Query("UPDATE uncles SET main_found = $2 WHERE id = $1;", nil, id.String(), found); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !found {
|
||||
if err := db.Query("DELETE FROM coinbase_outputs WHERE id = (SELECT coinbase_id FROM blocks WHERE id = $1) OR id = (SELECT coinbase_id FROM uncles WHERE id = $1);", nil, id.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) CoinbaseTransactionExists(block *Block) bool {
|
||||
var count uint64
|
||||
if err := db.Query("SELECT COUNT(*) as count FROM coinbase_outputs WHERE id = encode($1, 'hex');", func(row RowScanInterface) error {
|
||||
return row.Scan(&count)
|
||||
}, block.Coinbase.Id[:]); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (db *Database) GetCoinbaseTransaction(block *Block) *CoinbaseTransaction {
|
||||
var outputs []*CoinbaseTransactionOutput
|
||||
if err := db.Query("SELECT index, amount, miner FROM coinbase_outputs WHERE id = encode($1, 'hex') ORDER BY index DESC;", func(row RowScanInterface) error {
|
||||
output := &CoinbaseTransactionOutput{
|
||||
id: block.Coinbase.Id,
|
||||
}
|
||||
|
||||
if err := row.Scan(&output.index, &output.amount, &output.miner); err != nil {
|
||||
return err
|
||||
}
|
||||
outputs = append(outputs, output)
|
||||
return nil
|
||||
}, block.Coinbase.Id[:]); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &CoinbaseTransaction{
|
||||
id: block.Coinbase.Id,
|
||||
privateKey: block.Coinbase.PrivateKey,
|
||||
outputs: outputs,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) GetCoinbaseTransactionOutputByIndex(coinbaseId types.Hash, index uint64) *CoinbaseTransactionOutput {
|
||||
output := &CoinbaseTransactionOutput{
|
||||
id: coinbaseId,
|
||||
index: index,
|
||||
}
|
||||
if err := db.Query("SELECT amount, miner FROM coinbase_outputs WHERE id = encode($1, 'hex') AND index = $2 ORDER BY index DESC;", func(row RowScanInterface) error {
|
||||
if err := row.Scan(&output.amount, &output.miner); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, coinbaseId[:], index); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (db *Database) GetCoinbaseTransactionOutputByMinerId(coinbaseId types.Hash, minerId uint64) *CoinbaseTransactionOutput {
|
||||
output := &CoinbaseTransactionOutput{
|
||||
id: coinbaseId,
|
||||
miner: minerId,
|
||||
}
|
||||
if err := db.Query("SELECT amount, index FROM coinbase_outputs WHERE id = encode($1, 'hex') AND miner = $2 ORDER BY index DESC;", func(row RowScanInterface) error {
|
||||
if err := row.Scan(&output.amount, &output.index); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, coinbaseId[:], minerId); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
type Payout struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
Main struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
} `json:"main"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
Uncle bool `json:"uncle,omitempty"`
|
||||
Coinbase struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Reward uint64 `json:"reward"`
|
||||
PrivateKey crypto.PrivateKeyBytes `json:"private_key"`
|
||||
Index uint64 `json:"index"`
|
||||
} `json:"coinbase"`
|
||||
}
|
||||
|
||||
func (db *Database) GetPayoutsByMinerId(minerId uint64, limit uint64) chan *Payout {
|
||||
out := make(chan *Payout)
|
||||
|
||||
go func() {
|
||||
defer close(out)
|
||||
|
||||
miner := db.getMiner(minerId)
|
||||
|
||||
if miner == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resultFunc := func(row RowScanInterface) error {
|
||||
var blockId, mainId, privKey, coinbaseId []byte
|
||||
var height, mainHeight, timestamp, amount, index uint64
|
||||
var uncle bool
|
||||
|
||||
if err := row.Scan(&blockId, &mainId, &height, &mainHeight, ×tamp, &privKey, &uncle, &coinbaseId, &amount, &index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out <- &Payout{
|
||||
Id: types.HashFromBytes(blockId),
|
||||
Height: height,
|
||||
Timestamp: timestamp,
|
||||
Main: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
}{Id: types.HashFromBytes(mainId), Height: mainHeight},
|
||||
Uncle: uncle,
|
||||
Coinbase: struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Reward uint64 `json:"reward"`
|
||||
PrivateKey crypto.PrivateKeyBytes `json:"private_key"`
|
||||
Index uint64 `json:"index"`
|
||||
}{Id: types.HashFromBytes(coinbaseId), Reward: amount, PrivateKey: crypto.PrivateKeyBytes(types.HashFromBytes(privKey)), Index: index},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if limit == 0 {
|
||||
if err := db.Query("SELECT decode(b.id, 'hex') AS id, decode(b.main_id, 'hex') AS main_id, b.height AS height, b.main_height AS main_height, b.timestamp AS timestamp, decode(b.coinbase_privkey, 'hex') AS coinbase_privkey, b.uncle AS uncle, decode(o.id, 'hex') AS coinbase_id, o.amount AS amount, o.index AS index FROM (SELECT id, amount, index FROM coinbase_outputs WHERE miner = $1) o LEFT JOIN LATERAL (SELECT id, coinbase_id, coinbase_privkey, height, main_height, main_id, timestamp, FALSE AS uncle FROM blocks WHERE coinbase_id = o.id UNION SELECT id, coinbase_id, coinbase_privkey, height, main_height, main_id, timestamp, TRUE AS uncle FROM uncles WHERE coinbase_id = o.id) b ON b.coinbase_id = o.id ORDER BY main_height DESC;", resultFunc, minerId); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := db.Query("SELECT decode(b.id, 'hex') AS id, decode(b.main_id, 'hex') AS main_id, b.height AS height, b.main_height AS main_height, b.timestamp AS timestamp, decode(b.coinbase_privkey, 'hex') AS coinbase_privkey, b.uncle AS uncle, decode(o.id, 'hex') AS coinbase_id, o.amount AS amount, o.index AS index FROM (SELECT id, amount, index FROM coinbase_outputs WHERE miner = $1) o LEFT JOIN LATERAL (SELECT id, coinbase_id, coinbase_privkey, height, main_height, main_id, timestamp, FALSE AS uncle FROM blocks WHERE coinbase_id = o.id UNION SELECT id, coinbase_id, coinbase_privkey, height, main_height, main_id, timestamp, TRUE AS uncle FROM uncles WHERE coinbase_id = o.id) b ON b.coinbase_id = o.id ORDER BY main_height DESC LIMIT $2;", resultFunc, minerId, limit); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (db *Database) InsertCoinbaseTransaction(coinbase *CoinbaseTransaction) error {
|
||||
if tx, err := db.handle.BeginTx(context.Background(), nil); err != nil {
|
||||
return err
|
||||
} else if stmt, err := tx.Prepare("INSERT INTO coinbase_outputs (id, index, miner, amount) VALUES ($1, $2, $3, $4);"); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
} else {
|
||||
defer stmt.Close()
|
||||
for _, o := range coinbase.Outputs() {
|
||||
if rows, err := stmt.Query(o.Id().String(), o.Index(), o.Miner(), o.Amount()); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
} else {
|
||||
if err = rows.Close(); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) InsertBlock(b *Block, fallbackDifficulty *types.Difficulty) error {
|
||||
|
||||
block := db.GetBlockById(b.Id)
|
||||
mainDiff := b.Template.Difficulty
|
||||
if mainDiff == UndefinedDifficulty && fallbackDifficulty != nil {
|
||||
mainDiff = *fallbackDifficulty
|
||||
}
|
||||
|
||||
if block != nil { //Update found status if existent
|
||||
if b.Id != block.Id {
|
||||
return errors.New("block exists but has different id")
|
||||
}
|
||||
if block.Template.Difficulty != mainDiff && mainDiff != UndefinedDifficulty {
|
||||
if err := db.SetBlockMainDifficulty(block.Id, mainDiff); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if b.Main.Found && !block.Main.Found {
|
||||
if err := db.SetBlockFound(block.Id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return db.Query(
|
||||
"INSERT INTO blocks (id, height, previous_id, coinbase_id, coinbase_reward, coinbase_privkey, difficulty, timestamp, miner, pow_hash, main_height, main_id, main_found, miner_main_id, miner_main_difficulty) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15);",
|
||||
nil,
|
||||
b.Id.String(),
|
||||
b.Height,
|
||||
b.PreviousId.String(),
|
||||
b.Coinbase.Id.String(),
|
||||
b.Coinbase.Reward,
|
||||
b.Coinbase.PrivateKey.String(),
|
||||
b.Difficulty.String(),
|
||||
b.Timestamp,
|
||||
b.MinerId,
|
||||
b.PowHash.String(),
|
||||
b.Main.Height,
|
||||
b.Main.Id.String(),
|
||||
b.Main.Found,
|
||||
b.Template.Id.String(),
|
||||
b.Template.Difficulty.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func (db *Database) InsertUncleBlock(u *UncleBlock, fallbackDifficulty *types.Difficulty) error {
|
||||
|
||||
if b := db.GetBlockById(u.ParentId); b == nil {
|
||||
return errors.New("parent does not exist")
|
||||
}
|
||||
|
||||
uncle := db.GetUncleById(u.Block.Id)
|
||||
|
||||
mainDiff := u.Block.Template.Difficulty
|
||||
if mainDiff == UndefinedDifficulty && fallbackDifficulty != nil {
|
||||
mainDiff = *fallbackDifficulty
|
||||
}
|
||||
|
||||
if uncle != nil { //Update found status if existent
|
||||
if u.Block.Id != uncle.Block.Id {
|
||||
return errors.New("block exists but has different id")
|
||||
}
|
||||
if uncle.Block.Template.Difficulty != mainDiff && mainDiff != UndefinedDifficulty {
|
||||
if err := db.SetBlockMainDifficulty(u.Block.Id, mainDiff); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if u.Block.Main.Found && !uncle.Block.Main.Found {
|
||||
if err := db.SetBlockFound(u.Block.Id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return db.Query(
|
||||
"INSERT INTO uncles (parent_id, parent_height, id, height, previous_id, coinbase_id, coinbase_reward, coinbase_privkey, difficulty, timestamp, miner, pow_hash, main_height, main_id, main_found, miner_main_id, miner_main_difficulty) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17);",
|
||||
nil,
|
||||
u.ParentId.String(),
|
||||
u.ParentHeight,
|
||||
u.Block.Id.String(),
|
||||
u.Block.Height,
|
||||
u.Block.PreviousId.String(),
|
||||
u.Block.Coinbase.Id.String(),
|
||||
u.Block.Coinbase.Reward,
|
||||
u.Block.Coinbase.PrivateKey.String(),
|
||||
u.Block.Difficulty.String(),
|
||||
u.Block.Timestamp,
|
||||
u.Block.MinerId,
|
||||
u.Block.PowHash.String(),
|
||||
u.Block.Main.Height,
|
||||
u.Block.Main.Id.String(),
|
||||
u.Block.Main.Found,
|
||||
u.Block.Template.Id.String(),
|
||||
u.Block.Template.Difficulty.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func (db *Database) Close() error {
|
||||
|
||||
//cleanup statements
|
||||
v := reflect.ValueOf(db.statements)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if stmt, ok := v.Field(i).Interface().(*sql.Stmt); ok && stmt != nil {
|
||||
//v.Field(i).Elem().Set(reflect.ValueOf((*sql.Stmt)(nil)))
|
||||
stmt.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return db.handle.Close()
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Miner struct {
|
||||
id uint64
|
||||
addr string
|
||||
alias sql.NullString
|
||||
moneroAddress atomic.Pointer[address.Address]
|
||||
}
|
||||
|
||||
func (m *Miner) Id() uint64 {
|
||||
return m.id
|
||||
}
|
||||
|
||||
func (m *Miner) Alias() string {
|
||||
if m.alias.Valid {
|
||||
return m.alias.String
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Miner) Address() string {
|
||||
return m.addr
|
||||
}
|
||||
|
||||
func (m *Miner) MoneroAddress() *address.Address {
|
||||
if a := m.moneroAddress.Load(); a != nil {
|
||||
return a
|
||||
} else {
|
||||
a = address.FromBase58(m.addr)
|
||||
m.moneroAddress.Store(a)
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
type outputResult struct {
|
||||
Miner *Miner
|
||||
Output *transaction.Output
|
||||
}
|
||||
|
||||
func MatchOutputs(c *transaction.CoinbaseTransaction, miners []*Miner, privateKey crypto.PrivateKey) (result []outputResult) {
|
||||
addresses := make(map[address.PackedAddress]*Miner, len(miners))
|
||||
|
||||
outputs := make([]*transaction.Output, len(c.Outputs))
|
||||
for i := range c.Outputs {
|
||||
outputs[i] = &c.Outputs[i]
|
||||
}
|
||||
|
||||
//TODO: this sorting will be inefficient come the hard fork
|
||||
|
||||
for _, m := range miners {
|
||||
addresses[*m.MoneroAddress().ToPackedAddress()] = m
|
||||
}
|
||||
|
||||
sortedAddresses := maps.Keys(addresses)
|
||||
|
||||
slices.SortFunc(sortedAddresses, func(a address.PackedAddress, b address.PackedAddress) bool {
|
||||
return a.Compare(&b) < 0
|
||||
})
|
||||
|
||||
result = make([]outputResult, 0, len(miners))
|
||||
|
||||
for _, k := range sortedAddresses {
|
||||
derivation := privateKey.GetDerivationCofactor(k.ViewPublicKey())
|
||||
for i, o := range outputs {
|
||||
if o == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if o.Type == transaction.TxOutToTaggedKey && o.ViewTag != crypto.GetDerivationViewTagForOutputIndex(derivation, o.Index) { //fast check
|
||||
continue
|
||||
}
|
||||
|
||||
sharedData := crypto.GetDerivationSharedDataForOutputIndex(derivation, o.Index)
|
||||
if bytes.Compare(o.EphemeralPublicKey[:], address.GetPublicKeyForSharedData(&k, sharedData).AsSlice()) == 0 {
|
||||
//TODO: maybe clone?
|
||||
result = append(result, outputResult{
|
||||
Miner: addresses[k],
|
||||
Output: o,
|
||||
})
|
||||
outputs[i] = nil
|
||||
outputs = slices.Compact(outputs)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(result, func(a outputResult, b outputResult) bool {
|
||||
return a.Output.Index < b.Output.Index
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package database
|
||||
|
||||
type Subscription struct {
|
||||
miner uint64
|
||||
nick string
|
||||
}
|
||||
|
||||
func (s *Subscription) Miner() uint64 {
|
||||
return s.miner
|
||||
}
|
||||
|
||||
func (s *Subscription) Nick() string {
|
||||
return s.nick
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package database
|
||||
|
||||
import "git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
|
||||
type UncleBlock struct {
|
||||
Block Block
|
||||
ParentId types.Hash
|
||||
ParentHeight uint64
|
||||
}
|
||||
|
||||
func (u *UncleBlock) GetBlock() *Block {
|
||||
return &u.Block
|
||||
}
|
|
@ -5,10 +5,46 @@ networks:
|
|||
external: false
|
||||
|
||||
volumes:
|
||||
p2pool:
|
||||
external: false
|
||||
db:
|
||||
external: false
|
||||
|
||||
services:
|
||||
tor:
|
||||
image: goldy/tor-hidden-service:v0.4.7.12-54c0e54
|
||||
tmpfs:
|
||||
- /tmp
|
||||
restart: always
|
||||
environment:
|
||||
TOR_SOCKS_PORT: 9050
|
||||
SERVICE1_TOR_SERVICE_HOSTS: 80:site:80,${P2POOL_EXTERNAL_PORT}:p2pool:${P2POOL_PORT}
|
||||
SERVICE1_TOR_SERVICE_VERSION: '3'
|
||||
SERVICE1_TOR_SERVICE_KEY: ${TOR_SERVICE_KEY}
|
||||
depends_on:
|
||||
- site
|
||||
- p2pool
|
||||
networks:
|
||||
- p2pool-observer
|
||||
site:
|
||||
build:
|
||||
context: ./docker/nginx
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- TOR_SERVICE_ADDRESS=${TOR_SERVICE_ADDRESS}
|
||||
- NET_SERVICE_ADDRESS=${NET_SERVICE_ADDRESS}
|
||||
restart: always
|
||||
depends_on:
|
||||
- api
|
||||
- web
|
||||
tmpfs:
|
||||
- /run
|
||||
- /var/cache/nginx
|
||||
- /tmp
|
||||
networks:
|
||||
- p2pool-observer
|
||||
ports:
|
||||
- ${SITE_PORT}:80
|
||||
db:
|
||||
image: postgres:15.2
|
||||
restart: always
|
||||
|
@ -46,4 +82,92 @@ services:
|
|||
# needed configuration. The read-only flag does not make volumes and tmpfs read-only.
|
||||
- /tmp
|
||||
- /run
|
||||
- /run/postgresql
|
||||
- /run/postgresql
|
||||
|
||||
|
||||
p2pool:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./docker/golang/Dockerfile
|
||||
restart: always
|
||||
depends_on:
|
||||
- db
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
volumes:
|
||||
- p2pool:/data:rw
|
||||
networks:
|
||||
- p2pool-observer
|
||||
working_dir: /data
|
||||
ports:
|
||||
- ${P2POOL_PORT}:${P2POOL_PORT}
|
||||
command: >-
|
||||
/usr/bin/p2pool
|
||||
-out-peers ${P2POOL_OUT_PEERS}
|
||||
-in-peers ${P2POOL_IN_PEERS}
|
||||
-host ${MONEROD_HOST}
|
||||
-rpc-port ${MONEROD_RPC_PORT}
|
||||
-zmq-port ${MONEROD_ZMQ_PORT}
|
||||
-p2p 0.0.0.0:${P2POOL_PORT}
|
||||
-p2p-external-port ${P2POOL_EXTERNAL_PORT}
|
||||
-api-bind '0.0.0.0:3131'
|
||||
-archive /data/archive.db ${P2POOL_EXTRA_ARGS}
|
||||
api:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./docker/golang/Dockerfile
|
||||
restart: always
|
||||
environment:
|
||||
- TOR_SERVICE_ADDRESS=${TOR_SERVICE_ADDRESS}
|
||||
depends_on:
|
||||
- db
|
||||
- p2pool
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- p2pool-observer
|
||||
command: >-
|
||||
/usr/bin/api
|
||||
-host ${MONEROD_HOST}
|
||||
-rpc-port ${MONEROD_RPC_PORT}
|
||||
-api-host "http://p2pool:3131"
|
||||
-db="host=db port=5432 dbname=p2pool user=p2pool password=p2pool sslmode=disable"
|
||||
web:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./docker/golang/Dockerfile
|
||||
restart: always
|
||||
environment:
|
||||
- MONEROD_RPC_URL=http://${MONEROD_HOST}:${MONEROD_RPC_PORT}
|
||||
- TOR_SERVICE_ADDRESS=${TOR_SERVICE_ADDRESS}
|
||||
- NET_SERVICE_ADDRESS=${NET_SERVICE_ADDRESS}
|
||||
- API_URL=http://api:8080/api/
|
||||
- SITE_TITLE=${SITE_TITLE}
|
||||
depends_on:
|
||||
- api
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- p2pool-observer
|
||||
command: >-
|
||||
/usr/bin/web
|
||||
daemon:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./docker/golang/Dockerfile
|
||||
restart: always
|
||||
environment:
|
||||
- TOR_SERVICE_ADDRESS=${TOR_SERVICE_ADDRESS}
|
||||
depends_on:
|
||||
- db
|
||||
- p2pool
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- p2pool-observer
|
||||
command: >-
|
||||
/usr/bin/daemon
|
||||
-host ${MONEROD_HOST}
|
||||
-rpc-port ${MONEROD_RPC_PORT}
|
||||
-api-host "http://p2pool:3131"
|
||||
-db="host=db port=5432 dbname=p2pool user=p2pool password=p2pool sslmode=disable"
|
40
docker/golang/Dockerfile
Normal file
40
docker/golang/Dockerfile
Normal file
|
@ -0,0 +1,40 @@
|
|||
FROM golang:1.20-alpine
|
||||
|
||||
|
||||
ENV CFLAGS="-march=native -Ofast -flto"
|
||||
ENV CXXFLAGS="-march=native -Ofast -flto"
|
||||
ENV LDFLAGS="-flto"
|
||||
ENV CGO_CFLAGS="-march=native -Ofast"
|
||||
ENV GOPROXY="direct"
|
||||
|
||||
RUN apk update && apk add --no-cache \
|
||||
git gcc g++ musl-dev bash autoconf automake cmake make libtool gettext
|
||||
|
||||
RUN git clone --depth 1 --branch master https://github.com/tevador/RandomX.git /tmp/RandomX && cd /tmp/RandomX && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX:PATH=/usr && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
cd ../ && \
|
||||
rm -rf /tmp/RandomX
|
||||
|
||||
|
||||
|
||||
WORKDIR /usr/src/p2pool
|
||||
|
||||
COPY ./ .
|
||||
|
||||
# p2pool
|
||||
RUN go build -buildvcs=false -trimpath -v -o /usr/bin/p2pool git.gammaspectra.live/P2Pool/p2pool-observer/cmd/p2pool
|
||||
|
||||
# observer stuff
|
||||
RUN go build -buildvcs=false -trimpath -v -o /usr/bin/api git.gammaspectra.live/P2Pool/p2pool-observer/cmd/api
|
||||
RUN go build -buildvcs=false -trimpath -v -o /usr/bin/daemon git.gammaspectra.live/P2Pool/p2pool-observer/cmd/daemon
|
||||
RUN go build -buildvcs=false -trimpath -v -o /usr/bin/web git.gammaspectra.live/P2Pool/p2pool-observer/cmd/web
|
||||
|
||||
# utilities
|
||||
RUN go build -buildvcs=false -trimpath -v -o /usr/bin/cachetoarchive git.gammaspectra.live/P2Pool/p2pool-observer/cmd/cachetoarchive
|
||||
RUN go build -buildvcs=false -trimpath -v -o /usr/bin/archivetoindex git.gammaspectra.live/P2Pool/p2pool-observer/cmd/archivetoindex
|
||||
RUN go build -buildvcs=false -trimpath -v -o /usr/bin/archivetoarchive git.gammaspectra.live/P2Pool/p2pool-observer/cmd/archivetoarchive
|
||||
|
||||
WORKDIR /data
|
9
docker/nginx/Dockerfile
Normal file
9
docker/nginx/Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
|||
FROM nginx:mainline
|
||||
|
||||
COPY static /web
|
||||
COPY snippets/*.conf /etc/nginx/snippets/
|
||||
COPY site.conf /etc/nginx/conf.d/site.conf
|
||||
|
||||
ARG TOR_SERVICE_ADDRESS
|
||||
|
||||
RUN echo "add_header Onion-Location \"http://${TOR_SERVICE_ADDRESS}\$request_uri\" always;" > /etc/nginx/snippets/onion-headers.conf
|
104
docker/nginx/site.conf
Normal file
104
docker/nginx/site.conf
Normal file
|
@ -0,0 +1,104 @@
|
|||
server {
|
||||
listen 80 fastopen=200 default_server;
|
||||
|
||||
access_log /dev/null;
|
||||
error_log /dev/null;
|
||||
|
||||
server_name _;
|
||||
|
||||
root /web;
|
||||
|
||||
proxy_connect_timeout 60;
|
||||
proxy_read_timeout 120;
|
||||
max_ranges 1;
|
||||
tcp_nodelay on;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/html text/css text/xml text/plain text/javascript text/xml application/xml application/x-javascript application/javascript application/json image/svg+xml application/font-woff application/font-woff2 application/font-ttf application/octet-stream application/wasm;
|
||||
gzip_min_length 1000;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 5;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
server_tokens off;
|
||||
|
||||
real_ip_header X-Forwarded-For;
|
||||
set_real_ip_from 0.0.0.0/0;
|
||||
absolute_redirect off;
|
||||
|
||||
location ~/c/(.*)$ {
|
||||
return 302 /api/redirect/coinbase/$1;
|
||||
}
|
||||
location ~/t/(.*)$ {
|
||||
return 302 /api/redirect/transaction/$1;
|
||||
}
|
||||
location ~/b/(.*)$ {
|
||||
return 302 /api/redirect/block/$1;
|
||||
}
|
||||
location ~/s/(.*)$ {
|
||||
return 302 /api/redirect/share/$1;
|
||||
}
|
||||
location ~/p/(.*)$ {
|
||||
return 302 /api/redirect/prove/$1;
|
||||
}
|
||||
location ~/m/(.*)$ {
|
||||
return 302 /api/redirect/miner/$1;
|
||||
}
|
||||
|
||||
try_files $uri $uri/ @web;
|
||||
include snippets/onion-headers.conf;
|
||||
|
||||
location = / {
|
||||
proxy_pass http://web:8444;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
add_header Allow "GET, POST";
|
||||
|
||||
include snippets/security-headers.conf;
|
||||
include snippets/csp.conf;
|
||||
include snippets/onion-headers.conf;
|
||||
}
|
||||
|
||||
location = /api {
|
||||
proxy_pass http://web:8444;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
add_header Allow "GET, POST";
|
||||
|
||||
include snippets/security-headers.conf;
|
||||
include snippets/csp.conf;
|
||||
include snippets/onion-headers.conf;
|
||||
}
|
||||
|
||||
|
||||
location @web {
|
||||
proxy_pass http://web:8444;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
add_header Allow "GET, POST";
|
||||
|
||||
include snippets/security-headers.conf;
|
||||
include snippets/csp.conf;
|
||||
include snippets/onion-headers.conf;
|
||||
}
|
||||
|
||||
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://api:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
add_header Allow "HEAD, OPTIONS, GET, POST";
|
||||
|
||||
include snippets/security-headers.conf;
|
||||
include snippets/cors.conf;
|
||||
include snippets/onion-headers.conf;
|
||||
}
|
||||
|
||||
|
||||
location ~* \.(jpg|jpeg|png|webp|gif|svg|ico|css|js|mjs|xml|woff|ttf|ttc|wasm|data|mem)$ {
|
||||
add_header Cache-Control "public, max-age=2592000"; # 30 days
|
||||
gzip on;
|
||||
sendfile on;
|
||||
}
|
||||
|
||||
|
||||
}
|
5
docker/nginx/snippets/cors.conf
Normal file
5
docker/nginx/snippets/cors.conf
Normal file
|
@ -0,0 +1,5 @@
|
|||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Max-Age 1728000 always;
|
||||
add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS' always;
|
||||
add_header Access-Control-Allow-Headers 'DNT,Authorization,Origin,Accept,User-Agent,X-Requested-With,If-Modified-Since,If-None-Match,Keep-Alive,Cache-Control,Content-Type,Range' always;
|
||||
add_header Access-Control-Expose-Headers 'Content-Type, Accept-Ranges, Content-Encoding, Content-Length, Content-Range, Cache-Control' always;
|
1
docker/nginx/snippets/csp.conf
Normal file
1
docker/nginx/snippets/csp.conf
Normal file
|
@ -0,0 +1 @@
|
|||
add_header Content-Security-Policy "default-src 'none'; img-src blob: data: ; object-src 'none'; style-src 'unsafe-inline'; style-src-elem 'unsafe-inline'; style-src-attr 'unsafe-inline'; prefetch-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; navigate-to * 'self'" always;
|
18
docker/nginx/snippets/security-headers.conf
Normal file
18
docker/nginx/snippets/security-headers.conf
Normal file
|
@ -0,0 +1,18 @@
|
|||
add_header X-Frame-Options deny always;
|
||||
add_header X-DNS-Prefetch-Control off always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
||||
|
||||
add_header X-Robots-Tag "nofollow, notranslate" always;
|
||||
add_header Tk "N" always;
|
||||
add_header Permissions-Policy "interest-cohort=(), autoplay=(), oversized-images=(), payment=(), fullscreen=(), camera=(), microphone=(), geolocation=(), usb=(), midi=()" always;
|
||||
add_header Vary 'Origin, Accept-Encoding' always;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
add_header Expect-CT "max-age=31536000, enforce" always;
|
||||
add_header Expect-Staple 'max-age=31536000; includeSubDomains; preload' always;
|
3
docker/nginx/static/robots.txt
Normal file
3
docker/nginx/static/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
User-agent: *
|
||||
Disallow: /miner
|
||||
Allow: /
|
|
@ -1,6 +1,7 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"os"
|
||||
"testing"
|
||||
|
@ -22,7 +23,7 @@ func TestOutputIndexes(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInputs(t *testing.T) {
|
||||
if result, err := GetDefaultClient().GetTransactionInputs(txHash); err != nil {
|
||||
if result, err := GetDefaultClient().GetTransactionInputs(context.Background(), txHash); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Log(result)
|
||||
|
|
|
@ -22,8 +22,6 @@ const (
|
|||
|
||||
const (
|
||||
PPLNSWindow = 2160
|
||||
BlockTime = 10
|
||||
UnclePenalty = 20
|
||||
UncleBlockDepth = 3
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue