P2Pool consensus v3.0.0
Migrate cmd utils, cmds to own repository
This commit is contained in:
parent
bd934bb5a3
commit
fe75cf259c
|
@ -1,12 +0,0 @@
|
|||
.git/
|
||||
.env
|
||||
.env.example
|
||||
.gitignore
|
||||
testdata/
|
||||
default.pgo.tmp
|
||||
docker/nginx
|
||||
docker/postgres
|
||||
docker-compose.override.yml
|
||||
p2pool.cache
|
||||
p2pool_peers.txt
|
||||
README.md
|
33
.env.example
33
.env.example
|
@ -1,33 +0,0 @@
|
|||
#GOPROXY=direct
|
||||
|
||||
# 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=
|
||||
|
||||
# Other APIs to lookup for transaction sweep besides local, for example, https://p2pool.observer. Separate by commas.
|
||||
TRANSACTION_LOOKUP_OTHER=
|
||||
|
||||
SITE_TITLE=
|
||||
# This port will be presented externally
|
||||
SITE_PORT=8189
|
||||
|
||||
# If specified, these IRC details will be shown on site. If the network is supported a Matrix link will be provided.
|
||||
#SITE_IRC_URL=ircs://irc.libera.chat:6697/#p2pool-observer/monero.social
|
||||
|
||||
# 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
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,6 +1,4 @@
|
|||
.idea
|
||||
p2pool.cache
|
||||
p2pool_peers.txt
|
||||
docker-compose.override.yml
|
||||
.env
|
||||
default.pgo.tmp
|
|
@ -7,7 +7,7 @@ Other general tools to work with Monero cryptography are also included.
|
|||
## Reporting issues
|
||||
|
||||
You can give feedback or report / discuss issues on:
|
||||
* [The issue tracker on git.gammaspectra.live/P2Pool/p2pool-observer](https://git.gammaspectra.live/P2Pool/p2pool-observer/issues?state=open)
|
||||
* [The issue tracker on git.gammaspectra.live/P2Pool/consensus](https://git.gammaspectra.live/P2Pool/consensus/issues?state=open)
|
||||
* Via IRC on [#p2pool-observer@libera.chat](ircs://irc.libera.chat/#p2pool-observer), or via [Matrix](https://matrix.to/#/#p2pool-observer:monero.social)
|
||||
* Any of the relevant rooms for the specific observer instances listed below.
|
||||
|
||||
|
@ -42,7 +42,7 @@ If desired each tool can be run individually, but that is left to the user to co
|
|||
|
||||
### Initial setup
|
||||
```bash
|
||||
$ git clone https://git.gammaspectra.live/P2Pool/p2pool-observer.git test-instance
|
||||
$ git clone https://git.gammaspectra.live/P2Pool/consensus.git test-instance
|
||||
$ cd test-instance
|
||||
$ cp .env.example .env
|
||||
```
|
||||
|
@ -67,7 +67,7 @@ It is recommended to run `docker system prune` regularly or after update to clea
|
|||
When a new instance starts with previously imported archives you might want to backfill sweep transactions. For new instances this is not necessary, and you can also skip this step and just rely on future data.
|
||||
```bash
|
||||
$ docker-compose exec --workdir /usr/src/p2pool daemon \
|
||||
go run -v git.gammaspectra.live/P2Pool/p2pool-observer/cmd/scansweeps \
|
||||
go run -v git.gammaspectra.live/P2Pool/consensus/cmd/scansweeps \
|
||||
-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"
|
||||
|
|
2128
cmd/api/api.go
2128
cmd/api/api.go
File diff suppressed because it is too large
Load diff
|
@ -1,52 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
CacheTotalKnownBlocksAndMiners = iota
|
||||
totalSize
|
||||
)
|
||||
|
||||
var cache = make([]*cachedResult, totalSize)
|
||||
var cacheLock sync.RWMutex
|
||||
|
||||
type cachedResult struct {
|
||||
t time.Time
|
||||
result any
|
||||
}
|
||||
|
||||
func cacheResult(k uint, cacheTime time.Duration, result func() any) any {
|
||||
if k >= totalSize {
|
||||
return result()
|
||||
}
|
||||
if r := func() any {
|
||||
if cacheTime > 0 {
|
||||
cacheLock.RLock()
|
||||
defer cacheLock.RUnlock()
|
||||
|
||||
if r := cache[k]; r != nil && r.t.Add(cacheTime).After(time.Now()) {
|
||||
return r.result
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}(); r != nil {
|
||||
return r
|
||||
}
|
||||
|
||||
r := result()
|
||||
|
||||
if cacheTime > 0 && r != nil {
|
||||
cacheLock.Lock()
|
||||
defer cacheLock.Unlock()
|
||||
cache[k] = &cachedResult{
|
||||
t: time.Now(),
|
||||
result: r,
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
Binary file not shown.
|
@ -1,42 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/api
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0 => ../index
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0 => ../utils
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/httputils v0.0.0 => ../httputils
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/httputils v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/floatdrop/lru v1.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,48 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,124 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/api"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func PayoutAmountHint(shares map[uint64]types.Difficulty, reward uint64) map[uint64]uint64 {
|
||||
amountShares := make(map[uint64]uint64)
|
||||
totalWeight := types.DifficultyFrom64(0)
|
||||
for _, w := range shares {
|
||||
totalWeight = totalWeight.Add(w)
|
||||
}
|
||||
|
||||
w := types.DifficultyFrom64(0)
|
||||
rewardGiven := types.DifficultyFrom64(0)
|
||||
|
||||
for miner, weight := range shares {
|
||||
w = w.Add(weight)
|
||||
nextValue := w.Mul64(reward).Div(totalWeight)
|
||||
amountShares[miner] = nextValue.Sub(rewardGiven).Lo
|
||||
rewardGiven = nextValue
|
||||
}
|
||||
return amountShares
|
||||
}
|
||||
|
||||
func Outputs(p2api *api.P2PoolApi, indexDb *index.Index, tip *index.SideBlock, derivationCache sidechain.DerivationCacheInterface, preAllocatedShares sidechain.Shares, preAllocatedRewards []uint64) (outputs transaction.Outputs, bottomHeight uint64) {
|
||||
if tip == nil {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
poolBlock := p2api.LightByMainId(tip.MainId)
|
||||
if poolBlock != nil {
|
||||
window := index.QueryIterateToSlice(indexDb.GetSideBlocksInPPLNSWindow(tip))
|
||||
if len(window) == 0 {
|
||||
return nil, 0
|
||||
}
|
||||
var hintIndex int
|
||||
|
||||
getByTemplateIdFull := func(h types.Hash) *index.SideBlock {
|
||||
if i := slices.IndexFunc(window, func(e *index.SideBlock) bool {
|
||||
return e.TemplateId == h
|
||||
}); i != -1 {
|
||||
hintIndex = i
|
||||
return window[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
getByTemplateId := func(h types.Hash) *index.SideBlock {
|
||||
//fast lookup first
|
||||
if i := slices.IndexFunc(window[hintIndex:], func(e *index.SideBlock) bool {
|
||||
return e.TemplateId == h
|
||||
}); i != -1 {
|
||||
hintIndex += i
|
||||
return window[hintIndex]
|
||||
}
|
||||
return getByTemplateIdFull(h)
|
||||
}
|
||||
|
||||
getUnclesOf := func(h types.Hash) index.QueryIterator[index.SideBlock] {
|
||||
parentEffectiveHeight := window[hintIndex].EffectiveHeight
|
||||
if window[hintIndex].TemplateId != h {
|
||||
parentEffectiveHeight = 0
|
||||
}
|
||||
|
||||
startIndex := 0
|
||||
|
||||
return &index.FakeQueryResult[index.SideBlock]{
|
||||
NextFunction: func() (int, *index.SideBlock) {
|
||||
for _, b := range window[startIndex+hintIndex:] {
|
||||
if b.UncleOf == h {
|
||||
startIndex++
|
||||
return startIndex, b
|
||||
}
|
||||
startIndex++
|
||||
|
||||
if parentEffectiveHeight != 0 && b.EffectiveHeight < parentEffectiveHeight {
|
||||
//early exit
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return index.CalculateOutputs(indexDb,
|
||||
tip,
|
||||
poolBlock.GetTransactionOutputType(),
|
||||
poolBlock.Main.Coinbase.TotalReward,
|
||||
&poolBlock.Side.CoinbasePrivateKey,
|
||||
poolBlock.Side.CoinbasePrivateKeySeed,
|
||||
p2api.MainDifficultyByHeight,
|
||||
getByTemplateId,
|
||||
getUnclesOf,
|
||||
derivationCache,
|
||||
preAllocatedShares,
|
||||
preAllocatedRewards,
|
||||
)
|
||||
} else {
|
||||
return nil, 0
|
||||
}
|
||||
}
|
||||
|
||||
func PayoutHint(p2api *api.P2PoolApi, indexDb *index.Index, tip *index.SideBlock) (shares map[uint64]types.Difficulty, blockDepth uint64) {
|
||||
shares = make(map[uint64]types.Difficulty)
|
||||
|
||||
blockDepth, err := index.BlocksInPPLNSWindowFast(indexDb, tip, p2api.MainDifficultyByHeight, func(b *index.SideBlock, weight types.Difficulty) {
|
||||
if _, ok := shares[b.Miner]; !ok {
|
||||
shares[b.Miner] = types.DifficultyFrom64(0)
|
||||
}
|
||||
shares[b.Miner] = shares[b.Miner].Add(weight)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, blockDepth
|
||||
}
|
||||
|
||||
return shares, blockDepth
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var moneroVersion, p2poolVersion cmdutils.VersionInfo
|
||||
var moneroVersionLock, p2poolVersionLock sync.Mutex
|
||||
|
||||
const versionCheckInterval = 3600
|
||||
|
||||
func getMoneroVersion() cmdutils.VersionInfo {
|
||||
moneroVersionLock.Lock()
|
||||
defer moneroVersionLock.Unlock()
|
||||
now := time.Now().Unix()
|
||||
if (moneroVersion.CheckedTimestamp + versionCheckInterval) >= now {
|
||||
return moneroVersion
|
||||
}
|
||||
response, err := http.DefaultClient.Get("https://api.github.com/repos/monero-project/monero/releases/latest")
|
||||
if err != nil {
|
||||
return moneroVersion
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var releaseData cmdutils.ReleaseDataJson
|
||||
if data, err := io.ReadAll(response.Body); err != nil {
|
||||
return moneroVersion
|
||||
} else if err = utils.UnmarshalJSON(data, &releaseData); err != nil {
|
||||
return moneroVersion
|
||||
} else {
|
||||
moneroVersion.Version = releaseData.TagName
|
||||
moneroVersion.Timestamp = releaseData.PublishedAt.Unix()
|
||||
moneroVersion.Link = "https://github.com/monero-project/monero/releases/tag/" + releaseData.TagName
|
||||
moneroVersion.CheckedTimestamp = now
|
||||
}
|
||||
|
||||
return moneroVersion
|
||||
}
|
||||
|
||||
func getP2PoolVersion() cmdutils.VersionInfo {
|
||||
p2poolVersionLock.Lock()
|
||||
defer p2poolVersionLock.Unlock()
|
||||
now := time.Now().Unix()
|
||||
if (p2poolVersion.CheckedTimestamp + versionCheckInterval) >= now {
|
||||
return p2poolVersion
|
||||
}
|
||||
response, err := http.DefaultClient.Get("https://api.github.com/repos/SChernykh/p2pool/releases/latest")
|
||||
if err != nil {
|
||||
return p2poolVersion
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var releaseData cmdutils.ReleaseDataJson
|
||||
if data, err := io.ReadAll(response.Body); err != nil {
|
||||
return p2poolVersion
|
||||
} else if err = utils.UnmarshalJSON(data, &releaseData); err != nil {
|
||||
return p2poolVersion
|
||||
} else {
|
||||
p2poolVersion.Version = releaseData.TagName
|
||||
p2poolVersion.Timestamp = releaseData.PublishedAt.Unix()
|
||||
p2poolVersion.Link = "https://github.com/SChernykh/p2pool/releases/tag/" + releaseData.TagName
|
||||
p2poolVersion.CheckedTimestamp = now
|
||||
}
|
||||
|
||||
return p2poolVersion
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var apiUrl string
|
||||
|
||||
func getTypeFromAPI[T any](method string) *T {
|
||||
uri, _ := url.Parse(apiUrl + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
if response.StatusCode == http.StatusOK {
|
||||
var result T
|
||||
decoder := utils.NewJSONDecoder(response.Body)
|
||||
if decoder.Decode(&result) != nil {
|
||||
return nil
|
||||
} else {
|
||||
return &result
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getSliceFromAPI[T any](method string) []T {
|
||||
|
||||
uri, _ := url.Parse(apiUrl + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
if response.StatusCode == http.StatusOK {
|
||||
var result []T
|
||||
decoder := utils.NewJSONDecoder(response.Body)
|
||||
if decoder.Decode(&result) != nil {
|
||||
return nil
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getStreamFromAPI[T any](method string) <-chan T {
|
||||
result := make(chan T, 1)
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
uri, _ := url.Parse(apiUrl + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
|
||||
if response.StatusCode == http.StatusOK {
|
||||
|
||||
var err error
|
||||
|
||||
// Read opening
|
||||
var b [1]byte
|
||||
for {
|
||||
if _, err = response.Body.Read(b[:]); err != nil {
|
||||
return
|
||||
}
|
||||
if b[0] == '[' {
|
||||
break
|
||||
} else if b[0] != ' ' && b[0] != 0xa {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
decoder := utils.NewJSONDecoder(response.Body)
|
||||
for decoder.More() {
|
||||
var item T
|
||||
if err := decoder.Decode(&item); err != nil {
|
||||
return
|
||||
} else {
|
||||
result <- item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func getFromAPIRaw(method string) []byte {
|
||||
uri, _ := url.Parse(apiUrl + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
if response.StatusCode == http.StatusOK {
|
||||
if data, err := io.ReadAll(response.Body); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/legacy"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"slices"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
selectedApiUrl := flag.String("api", "", "Input API url, for example, https://p2pool.observer/api/")
|
||||
outputFile := flag.String("output", "p2pool.cache", "Output p2pool.cache path")
|
||||
fromBlock := flag.String("from", "tip", "Block to start from. Can be an ID or a height")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
apiUrl = *selectedApiUrl
|
||||
|
||||
poolInfo := getTypeFromAPI[cmdutils.PoolInfoResult]("pool_info")
|
||||
if poolInfo == nil {
|
||||
panic("could not fetch consensus")
|
||||
}
|
||||
|
||||
consensusData, _ := utils.MarshalJSON(poolInfo.SideChain.Consensus)
|
||||
consensus, err := sidechain.NewConsensusFromJSON(consensusData)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
utils.Logf("Consensus", "Consensus id = %s", consensus.Id)
|
||||
|
||||
cache, err := legacy.NewCache(consensus, *outputFile)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer cache.Close()
|
||||
|
||||
var toFetchUrls []string
|
||||
|
||||
if *fromBlock == "tip" {
|
||||
toFetchUrls = append(toFetchUrls, fmt.Sprintf("redirect/tip/raw"))
|
||||
} else if n, err := strconv.ParseUint(*fromBlock, 10, 0); err == nil {
|
||||
toFetchUrls = append(toFetchUrls, fmt.Sprintf("block_by_height/%d/raw", n))
|
||||
} else {
|
||||
toFetchUrls = append(toFetchUrls, fmt.Sprintf("block_by_id/%s/raw", *fromBlock))
|
||||
}
|
||||
|
||||
var fetches int
|
||||
|
||||
addBlockId := func(h types.Hash) {
|
||||
k := fmt.Sprintf("block_by_id/%s/raw", h)
|
||||
if slices.Contains(toFetchUrls, k) {
|
||||
return
|
||||
}
|
||||
toFetchUrls = append(toFetchUrls, k)
|
||||
}
|
||||
|
||||
for len(toFetchUrls) > 0 {
|
||||
nextUrl := toFetchUrls[0]
|
||||
fmt.Printf("[%d] fetching %s\n", fetches, nextUrl)
|
||||
toFetchUrls = slices.Delete(toFetchUrls, 0, 1)
|
||||
|
||||
rawBlock := getFromAPIRaw(nextUrl)
|
||||
b := &sidechain.PoolBlock{}
|
||||
err := b.UnmarshalBinary(consensus, &sidechain.NilDerivationCache{}, rawBlock)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not fetch block from url %s: %w", nextUrl, err))
|
||||
}
|
||||
|
||||
cache.Store(b)
|
||||
|
||||
for _, u := range b.Side.Uncles {
|
||||
addBlockId(u)
|
||||
}
|
||||
addBlockId(b.Side.Parent)
|
||||
|
||||
fetches++
|
||||
|
||||
if fetches >= legacy.NumBlocks {
|
||||
print("reached max limit of block cache, exiting\n")
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cache.Flush()
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/apitocache
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0 => ../../p2pool/cache
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0 => ../index
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0 => ../utils
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0 // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/floatdrop/lru v1.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,46 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,114 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/archive"
|
||||
"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"
|
||||
"math"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
inputConsensus := flag.String("consensus", "config.json", "Input config.json consensus file")
|
||||
inputArchive := flag.String("input", "", "Input path for archive database")
|
||||
outputArchive := flag.String("output", "", "Output 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")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
|
||||
|
||||
cf, err := os.ReadFile(*inputConsensus)
|
||||
|
||||
consensus, err := sidechain.NewConsensusFromJSON(cf)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
difficultyCache := lru.New[uint64, types.Difficulty](1024)
|
||||
|
||||
getDifficultyByHeight := func(height uint64) types.Difficulty {
|
||||
if v := difficultyCache.Get(height); v == nil {
|
||||
if r, err := client.GetDefaultClient().GetBlockHeaderByHeight(height, context.Background()); err == nil {
|
||||
d := types.DifficultyFrom64(r.BlockHeader.Difficulty)
|
||||
difficultyCache.Set(height, d)
|
||||
return d
|
||||
}
|
||||
return types.ZeroDifficulty
|
||||
} else {
|
||||
return *v
|
||||
}
|
||||
}
|
||||
|
||||
inputCache, err := archive.NewCache(*inputArchive, consensus, getDifficultyByHeight)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer inputCache.Close()
|
||||
|
||||
outputCache, err := archive.NewCache(*outputArchive, consensus, getDifficultyByHeight)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer outputCache.Close()
|
||||
|
||||
derivationCache := sidechain.NewDerivationLRUCache()
|
||||
|
||||
blockCache := lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4))
|
||||
|
||||
getByTemplateId := func(h types.Hash) *sidechain.PoolBlock {
|
||||
if v := blockCache.Get(h); v == nil {
|
||||
if bs := inputCache.LoadByTemplateId(h); len(bs) != 0 {
|
||||
bs[0].Depth.Store(math.MaxUint64)
|
||||
blockCache.Set(h, bs[0])
|
||||
return bs[0]
|
||||
} else if bs = outputCache.LoadByTemplateId(h); len(bs) != 0 {
|
||||
bs[0].Depth.Store(math.MaxUint64)
|
||||
blockCache.Set(h, bs[0])
|
||||
return bs[0]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return *v
|
||||
}
|
||||
}
|
||||
|
||||
preAllocatedShares := sidechain.PreAllocateShares(consensus.ChainWindowSize * 4)
|
||||
blocksProcessed := 0
|
||||
for blocksAtHeight := range inputCache.ScanHeights(0, math.MaxUint64) {
|
||||
for i, b := range blocksAtHeight {
|
||||
blocksProcessed++
|
||||
if _, err := b.PreProcessBlock(consensus, derivationCache, preAllocatedShares, getDifficultyByHeight, getByTemplateId); err != nil {
|
||||
utils.Errorf("", "error processing block %s at %d, %s", types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId)), b.Side.Height, err)
|
||||
} else {
|
||||
if parent := getByTemplateId(b.Side.Parent); parent != nil {
|
||||
topDepth := parent.Depth.Load()
|
||||
if topDepth == math.MaxUint64 {
|
||||
b.Depth.Store(consensus.ChainWindowSize * 2)
|
||||
} else if topDepth == 0 {
|
||||
b.Depth.Store(0)
|
||||
} else {
|
||||
b.Depth.Store(topDepth - 1)
|
||||
}
|
||||
} else {
|
||||
utils.Errorf("", "block %s at %d, could not get parent, fallback", types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId)), b.Side.Height)
|
||||
b.Depth.Store(math.MaxUint64)
|
||||
}
|
||||
outputCache.Store(b)
|
||||
if i == 0 {
|
||||
blockCache.Set(types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId)), b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Logf("Archive", "blocks processed %d", blocksProcessed)
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/archivetoarchive
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0 => ../../p2pool/cache
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0
|
||||
github.com/floatdrop/lru v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,47 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,347 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/archive"
|
||||
"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"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
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")
|
||||
connString := flag.String("conn", "", "Connection string for postgres database")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
|
||||
client.GetDefaultClient().SetThrottle(1000)
|
||||
|
||||
cf, err := os.ReadFile(*inputConsensus)
|
||||
|
||||
consensus, err := sidechain.NewConsensusFromJSON(cf)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
if err = consensus.InitHasher(2, randomx.FlagSecure, randomx.FlagFullMemory); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
var headerCacheLock sync.RWMutex
|
||||
headerByHeightCache := make(map[uint64]*block.Header)
|
||||
headerByIdCache := make(map[types.Hash]*block.Header)
|
||||
|
||||
getHeaderByHeight := func(height uint64) *block.Header {
|
||||
if v := func() *block.Header {
|
||||
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()
|
||||
prevHash, _ := types.HashFromString(r.BlockHeader.PrevHash)
|
||||
h, _ := types.HashFromString(r.BlockHeader.Hash)
|
||||
header := &block.Header{
|
||||
MajorVersion: uint8(r.BlockHeader.MajorVersion),
|
||||
MinorVersion: uint8(r.BlockHeader.MinorVersion),
|
||||
Timestamp: uint64(r.BlockHeader.Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: r.BlockHeader.Height,
|
||||
Nonce: uint32(r.BlockHeader.Nonce),
|
||||
Reward: r.BlockHeader.Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(r.BlockHeader.Difficulty),
|
||||
}
|
||||
headerByIdCache[header.Id] = header
|
||||
headerByHeightCache[header.Height] = header
|
||||
return header
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
getDifficultyByHeight := func(height uint64) types.Difficulty {
|
||||
if v := getHeaderByHeight(height); v != nil {
|
||||
return v.Difficulty
|
||||
}
|
||||
return types.ZeroDifficulty
|
||||
}
|
||||
|
||||
getSeedByHeight := func(height uint64) (hash types.Hash) {
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
if v := getHeaderByHeight(seedHeight); v != nil {
|
||||
return v.Id
|
||||
}
|
||||
return types.ZeroHash
|
||||
}
|
||||
|
||||
archiveCache, err := archive.NewCache(*inputArchive, consensus, getDifficultyByHeight)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer archiveCache.Close()
|
||||
|
||||
blockCache := lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4))
|
||||
|
||||
derivationCache := sidechain.NewDerivationLRUCache()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
indexDb, err := index.OpenIndex(*connString, consensus, getDifficultyByHeight, getSeedByHeight, getByTemplateIdDirect)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer indexDb.Close()
|
||||
|
||||
totalStored := 0
|
||||
|
||||
var lastRangeHeight, rangeStart uint64 = math.MaxUint64, math.MaxUint64
|
||||
var lastTipEntries []*sidechain.PoolBlock
|
||||
|
||||
type rangeEntry struct {
|
||||
//todo: check time of range
|
||||
startHeight uint64
|
||||
tipHeight uint64
|
||||
tipEntries []*sidechain.PoolBlock
|
||||
}
|
||||
|
||||
var heightRanges []rangeEntry
|
||||
|
||||
/*id, _ := types.HashFromString("b83a96d30c7db3b15a65fa43ddcf6914bb4e176dea49a94a5263c9adca93d4cc")
|
||||
heightRanges = append(heightRanges, rangeEntry{
|
||||
startHeight: 3087255,
|
||||
tipHeight: 4728558,
|
||||
tipEntries: []*sidechain.PoolBlock{getByTemplateId(id)},
|
||||
})*/
|
||||
|
||||
if len(heightRanges) == 0 {
|
||||
for blocksAtHeight := range archiveCache.ScanHeights(0, math.MaxUint64) {
|
||||
if len(blocksAtHeight) == 0 {
|
||||
utils.Panicf("no blocks at %d + 1?", lastRangeHeight)
|
||||
}
|
||||
if lastRangeHeight == math.MaxUint64 {
|
||||
rangeStart = blocksAtHeight[0].Side.Height
|
||||
} else if blocksAtHeight[0].Side.Height != (lastRangeHeight + 1) { // new range
|
||||
heightRanges = append(heightRanges, rangeEntry{
|
||||
startHeight: rangeStart,
|
||||
tipHeight: lastRangeHeight,
|
||||
tipEntries: lastTipEntries,
|
||||
})
|
||||
utils.Logf("", "range %d -> %d, total of %d height(s)", rangeStart, lastRangeHeight, lastRangeHeight-rangeStart+1)
|
||||
utils.Logf("", "missing %d -> %d, total of %d height(s)", lastRangeHeight+1, blocksAtHeight[0].Side.Height-1, (blocksAtHeight[0].Side.Height-1)-(lastRangeHeight+1)+1)
|
||||
rangeStart = blocksAtHeight[0].Side.Height
|
||||
}
|
||||
lastRangeHeight = blocksAtHeight[0].Side.Height
|
||||
totalStored += len(blocksAtHeight)
|
||||
lastTipEntries = blocksAtHeight
|
||||
}
|
||||
utils.Logf("", "range %d -> %d, total of %d height(s)", rangeStart, lastRangeHeight, lastRangeHeight-rangeStart+1)
|
||||
heightRanges = append(heightRanges, rangeEntry{
|
||||
startHeight: rangeStart,
|
||||
tipHeight: lastRangeHeight,
|
||||
tipEntries: lastTipEntries,
|
||||
})
|
||||
|
||||
utils.Logf("", "total stored %d", totalStored)
|
||||
}
|
||||
|
||||
var lastTime, lastHeight uint64 = math.MaxUint64, math.MaxUint64
|
||||
|
||||
for i := len(heightRanges) - 1; i >= 0; i-- {
|
||||
r := heightRanges[i]
|
||||
|
||||
var bestTip *sidechain.PoolBlock
|
||||
if len(r.tipEntries) == 0 {
|
||||
bestTip = r.tipEntries[0]
|
||||
} else {
|
||||
for _, b := range r.tipEntries {
|
||||
if (b.Main.Timestamp - 60*5) > lastTime { //2m offset of max time drift
|
||||
continue
|
||||
}
|
||||
|
||||
if bestTip == nil {
|
||||
bestTip = b
|
||||
} else if bestTip.Main.Coinbase.GenHeight < b.Main.Coinbase.GenHeight {
|
||||
bestTip = b
|
||||
} else if bestTip.Main.Timestamp < b.Main.Timestamp {
|
||||
bestTip = b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bestTip == nil {
|
||||
utils.Logf("", "skipped range %d to %d due to: nil tip", r.startHeight, r.tipHeight)
|
||||
continue
|
||||
} else if bestTip.Main.Coinbase.GenHeight > lastHeight {
|
||||
utils.Logf("", "skipped range %d to %d due to: main height %d > %d", r.startHeight, r.tipHeight, bestTip.Main.Coinbase.GenHeight, lastHeight)
|
||||
continue
|
||||
} else if (bestTip.Main.Timestamp - 60*5) > lastTime {
|
||||
utils.Logf("", "skipped range %d to %d due to: timestamp %d > %d", r.startHeight, r.tipHeight, bestTip.Main.Timestamp, lastTime)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := processBlock(bestTip); err != nil {
|
||||
utils.Logf("", "skipped range %d to %d due to: could not process tip: %s", r.startHeight, r.tipHeight, err)
|
||||
continue
|
||||
}
|
||||
|
||||
lastTime = bestTip.Main.Timestamp
|
||||
lastHeight = bestTip.Main.Coinbase.GenHeight
|
||||
|
||||
if r.startHeight-r.tipHeight > consensus.ChainWindowSize*2 &&
|
||||
indexDb.GetTipSideBlockByTemplateId(bestTip.SideTemplateId(consensus)) != nil &&
|
||||
indexDb.GetTipSideBlockByHeight(r.startHeight+consensus.ChainWindowSize+1) != nil &&
|
||||
index.QueryHasResults(indexDb.GetSideBlocksByHeight(r.startHeight)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip inserted heights
|
||||
for bestTip != nil && indexDb.GetTipSideBlockByTemplateId(bestTip.SideTemplateId(consensus)) != nil {
|
||||
utils.Logf("", "skip id = %s, template id = %s, height = %d", bestTip.MainId(), bestTip.SideTemplateId(consensus), bestTip.Side.Height)
|
||||
bestTip = getByTemplateId(bestTip.Side.Parent)
|
||||
}
|
||||
|
||||
for cur := bestTip; cur != nil; cur = getByTemplateId(cur.Side.Parent) {
|
||||
utils.Logf("", "id = %s, template id = %s, height = %d", cur.MainId(), cur.SideTemplateId(consensus), cur.Side.Height)
|
||||
|
||||
if err = indexDb.InsertOrUpdatePoolBlock(cur, index.InclusionInVerifiedChain); err != nil {
|
||||
utils.Errorf("", "error inserting %s, %s at %d: %s", cur.SideTemplateId(consensus), cur.MainId(), cur.Side.Height, err)
|
||||
break
|
||||
}
|
||||
|
||||
lastTime = cur.Main.Timestamp
|
||||
lastHeight = cur.Main.Coinbase.GenHeight
|
||||
|
||||
curId := cur.SideTemplateId(consensus)
|
||||
|
||||
for _, e := range archiveCache.LoadBySideChainHeight(cur.Side.Height) {
|
||||
if cur.FullId() == e.FullId() {
|
||||
continue
|
||||
}
|
||||
timeDiff := int64(e.Main.Timestamp) - int64(cur.Main.Timestamp)
|
||||
if timeDiff < 0 {
|
||||
timeDiff = -timeDiff
|
||||
}
|
||||
if timeDiff > 3600*12 { //More than 12 hours difference, do not include
|
||||
continue
|
||||
}
|
||||
if processBlock(e) != nil {
|
||||
utils.Errorf("", "error processing orphan/alternate %s, %s at %d: %s", e.SideTemplateId(consensus), e.MainId(), e.Side.Height, err)
|
||||
continue
|
||||
}
|
||||
if indexDb.GetSideBlockByMainId(e.MainId()) != nil {
|
||||
continue
|
||||
}
|
||||
if curId == e.SideTemplateId(consensus) {
|
||||
if err = indexDb.InsertOrUpdatePoolBlock(e, index.InclusionAlternateInVerifiedChain); err != nil {
|
||||
utils.Panicf("error inserting alternate %s, %s at %d: %s", e.SideTemplateId(consensus), e.MainId(), e.Side.Height, err)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if err = indexDb.InsertOrUpdatePoolBlock(e, index.InclusionOrphan); err != nil {
|
||||
utils.Panicf("error inserting orphan %s, %s at %d: %s", e.SideTemplateId(consensus), e.MainId(), e.Side.Height, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockCache = lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4))
|
||||
headerByIdCache = make(map[types.Hash]*block.Header)
|
||||
headerByHeightCache = make(map[uint64]*block.Header)
|
||||
derivationCache.Clear()
|
||||
}
|
||||
|
||||
var maxHeight, minHeight uint64
|
||||
if err := indexDb.Query("SELECT MAX(main_height), MIN(main_height) FROM side_blocks WHERE inclusion = $1;", func(row index.RowScanInterface) error {
|
||||
return row.Scan(&maxHeight, &minHeight)
|
||||
}, index.InclusionInVerifiedChain); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
heightCount := maxHeight - minHeight + 1
|
||||
|
||||
const strideSize = 1000
|
||||
strides := heightCount / strideSize
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for stride := uint64(0); stride <= strides; stride++ {
|
||||
start := minHeight + stride*strideSize
|
||||
end := min(maxHeight, minHeight+stride*strideSize+strideSize)
|
||||
utils.Logf("", "checking %d to %d", start, end)
|
||||
if headers, err := client.GetDefaultClient().GetBlockHeadersRangeResult(start, end, ctx); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
for _, h := range headers.Headers {
|
||||
if err := cmdutils.FindAndInsertMainHeader(h, indexDb, func(b *sidechain.PoolBlock) {
|
||||
archiveCache.Store(b)
|
||||
}, client.GetDefaultClient(), getDifficultyByHeight, getByTemplateIdDirect, archiveCache.LoadByMainId, archiveCache.LoadByMainChainHeight, processBlock); err != nil {
|
||||
utils.Panic(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mainBlocks, _ := indexDb.GetMainBlocksByQuery("WHERE side_template_id IS NOT NULL ORDER BY height DESC;")
|
||||
index.QueryIterate(mainBlocks, func(_ int, mb *index.MainBlock) (stop bool) {
|
||||
if err := cmdutils.FindAndInsertMainHeaderOutputs(mb, indexDb, client.GetDefaultClient(), getDifficultyByHeight, getByTemplateIdDirect, archiveCache.LoadByMainId, archiveCache.LoadByMainChainHeight, processBlock); err != nil {
|
||||
utils.Error(err)
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/archivetoindex
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0 => ../index
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0 => ../utils
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0 => ../../p2pool/cache
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0
|
||||
github.com/floatdrop/lru v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,49 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,104 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/archive"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/legacy"
|
||||
"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"
|
||||
"math"
|
||||
"os"
|
||||
)
|
||||
|
||||
type loadee struct {
|
||||
c *sidechain.Consensus
|
||||
cb func(block *sidechain.PoolBlock)
|
||||
}
|
||||
|
||||
func (l *loadee) Consensus() *sidechain.Consensus {
|
||||
return l.c
|
||||
}
|
||||
|
||||
func (l *loadee) AddCachedBlock(block *sidechain.PoolBlock) {
|
||||
l.cb(block)
|
||||
}
|
||||
|
||||
func main() {
|
||||
inputConsensus := flag.String("consensus", "config.json", "Input config.json consensus file")
|
||||
inputFile := flag.String("input", "p2pool.cache", "Input p2pool.cache path")
|
||||
outputArchive := flag.String("output", "", "Output path for archive database")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
cf, err := os.ReadFile(*inputConsensus)
|
||||
|
||||
consensus, err := sidechain.NewConsensusFromJSON(cf)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
cache, err := legacy.NewCache(consensus, *inputFile)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer cache.Close()
|
||||
|
||||
difficultyCache := lru.New[uint64, types.Difficulty](1024)
|
||||
|
||||
getDifficultyByHeight := func(height uint64) types.Difficulty {
|
||||
if v := difficultyCache.Get(height); v == nil {
|
||||
if r, err := client.GetDefaultClient().GetBlockHeaderByHeight(height, context.Background()); err == nil {
|
||||
d := types.DifficultyFrom64(r.BlockHeader.Difficulty)
|
||||
difficultyCache.Set(height, d)
|
||||
return d
|
||||
}
|
||||
return types.ZeroDifficulty
|
||||
} else {
|
||||
return *v
|
||||
}
|
||||
}
|
||||
|
||||
archiveCache, err := archive.NewCache(*outputArchive, consensus, getDifficultyByHeight)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer archiveCache.Close()
|
||||
|
||||
cachedBlocks := make(map[types.Hash]*sidechain.PoolBlock)
|
||||
|
||||
l := &loadee{
|
||||
c: consensus,
|
||||
cb: func(block *sidechain.PoolBlock) {
|
||||
expectedBlockId := types.HashFromBytes(block.CoinbaseExtra(sidechain.SideTemplateId))
|
||||
calculatedBlockId := block.SideTemplateId(consensus)
|
||||
|
||||
if expectedBlockId != calculatedBlockId {
|
||||
utils.Errorf("", "block height %d, template id %s, expected %s", block.Side.Height, calculatedBlockId, expectedBlockId)
|
||||
} else {
|
||||
cachedBlocks[expectedBlockId] = block
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cache.LoadAll(l)
|
||||
|
||||
var storeBlock func(b *sidechain.PoolBlock)
|
||||
storeBlock = func(b *sidechain.PoolBlock) {
|
||||
if parent := cachedBlocks[b.Side.Parent]; parent != nil {
|
||||
b.FillTransactionParentIndices(parent)
|
||||
storeBlock(parent)
|
||||
}
|
||||
b.Depth.Store(math.MaxUint64)
|
||||
archiveCache.Store(b)
|
||||
}
|
||||
for _, b := range cachedBlocks {
|
||||
if b.Depth.Load() == math.MaxUint64 {
|
||||
continue
|
||||
}
|
||||
storeBlock(b)
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cachetoarchive
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0 => ../../p2pool/cache
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0
|
||||
github.com/floatdrop/lru v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,47 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,353 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
p2poolapi "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/api"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
func blockId(b *sidechain.PoolBlock) types.Hash {
|
||||
return types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId))
|
||||
}
|
||||
|
||||
var sideBlocksLock sync.RWMutex
|
||||
|
||||
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")
|
||||
debugListen := flag.String("debug-listen", "", "Provide a bind address and port to expose a pprof HTTP API on it.")
|
||||
hookProxy := flag.String("hook-proxy", "", "socks5 proxy host:port for webhook requests")
|
||||
flag.Parse()
|
||||
|
||||
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
|
||||
|
||||
if *hookProxy != "" {
|
||||
cmdutils.SetWebHookProxy(*hookProxy)
|
||||
}
|
||||
|
||||
p2api := p2poolapi.NewP2PoolApi(*p2poolApiHost)
|
||||
|
||||
if err := p2api.WaitSync(); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
if *fullMode {
|
||||
if err := p2api.Consensus().InitHasher(1, randomx.FlagSecure, randomx.FlagFullMemory); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
} else {
|
||||
if err := p2api.Consensus().InitHasher(1, randomx.FlagSecure); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
indexDb, err := index.OpenIndex(*dbString, p2api.Consensus(), p2api.DifficultyByHeight, p2api.SeedByHeight, p2api.ByTemplateId)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer indexDb.Close()
|
||||
|
||||
dbTip := indexDb.GetSideBlockTip()
|
||||
|
||||
var tipHeight uint64
|
||||
if dbTip != nil {
|
||||
tipHeight = dbTip.SideHeight
|
||||
}
|
||||
utils.Logf("CHAIN", "Last known database tip is %d\n", tipHeight)
|
||||
|
||||
window, uncles := p2api.StateFromTip()
|
||||
if *startFromHeight != 0 {
|
||||
tip := p2api.BySideHeight(*startFromHeight)
|
||||
if len(tip) != 0 {
|
||||
window, uncles = p2api.WindowFromTemplateId(blockId(tip[0]))
|
||||
} else {
|
||||
tip = p2api.BySideHeight(*startFromHeight + p2api.Consensus().ChainWindowSize)
|
||||
if len(tip) != 0 {
|
||||
window, uncles = p2api.WindowFromTemplateId(blockId(tip[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insertFromTip := func(tip *sidechain.PoolBlock) {
|
||||
if indexDb.GetTipSideBlockByTemplateId(blockId(tip)) != nil {
|
||||
//reached old tip
|
||||
return
|
||||
}
|
||||
for cur := tip; cur != nil; cur = p2api.ByTemplateId(cur.Side.Parent) {
|
||||
utils.Logf("CHAIN", "Inserting share %s at height %d\n", blockId(cur).String(), cur.Side.Height)
|
||||
for _, u := range cur.Side.Uncles {
|
||||
utils.Logf("CHAIN", "Inserting uncle %s at parent height %d\n", u.String(), cur.Side.Height)
|
||||
}
|
||||
if err := indexDb.InsertOrUpdatePoolBlock(cur, index.InclusionInVerifiedChain); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
if indexDb.GetTipSideBlockByTemplateId(cur.Side.Parent) != nil {
|
||||
//reached old tip
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var backfillScan []types.Hash
|
||||
|
||||
for len(window) > 0 {
|
||||
utils.Logf("CHAIN", "Found range %d -> %d (%s to %s), %d shares, %d uncles", window[0].Side.Height, window[len(window)-1].Side.Height, window[0].SideTemplateId(p2api.Consensus()), window[len(window)-1].SideTemplateId(p2api.Consensus()), len(window), len(uncles))
|
||||
for _, b := range window {
|
||||
indexDb.CachePoolBlock(b)
|
||||
}
|
||||
for _, u := range uncles {
|
||||
indexDb.CachePoolBlock(u)
|
||||
}
|
||||
for _, b := range window {
|
||||
if indexDb.GetTipSideBlockByTemplateId(b.SideTemplateId(p2api.Consensus())) != nil {
|
||||
//reached old tip
|
||||
window = nil
|
||||
break
|
||||
}
|
||||
utils.Logf("CHAIN", "Inserting share %s at height %d\n", blockId(b).String(), b.Side.Height)
|
||||
for _, u := range b.Side.Uncles {
|
||||
utils.Logf("CHAIN", "Inserting uncle %s at parent height %d\n", u.String(), b.Side.Height)
|
||||
}
|
||||
if err := indexDb.InsertOrUpdatePoolBlock(b, index.InclusionInVerifiedChain); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
if b.IsProofHigherThanMainDifficulty(p2api.Consensus().GetHasher(), indexDb.GetDifficultyByHeight, indexDb.GetSeedByHeight) {
|
||||
backfillScan = append(backfillScan, b.MainId())
|
||||
}
|
||||
|
||||
for _, uncleId := range b.Side.Uncles {
|
||||
if u := uncles.Get(uncleId); u != nil && u.IsProofHigherThanMainDifficulty(p2api.Consensus().GetHasher(), indexDb.GetDifficultyByHeight, indexDb.GetSeedByHeight) {
|
||||
backfillScan = append(backfillScan, u.MainId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(window) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
parent := p2api.ByTemplateId(window[len(window)-1].Side.Parent)
|
||||
if parent == nil {
|
||||
break
|
||||
}
|
||||
window, uncles = p2api.WindowFromTemplateId(blockId(parent))
|
||||
if len(window) == 0 {
|
||||
insertFromTip(parent)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var maxHeight, currentHeight uint64
|
||||
if err = indexDb.Query("SELECT (SELECT MAX(main_height) FROM side_blocks) AS max_height, (SELECT MAX(height) FROM main_blocks) AS current_height;", func(row index.RowScanInterface) error {
|
||||
return row.Scan(&maxHeight, ¤tHeight)
|
||||
}); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
scanHeader := func(h daemon.BlockHeader) error {
|
||||
if err := cmdutils.FindAndInsertMainHeader(h, indexDb, func(b *sidechain.PoolBlock) {
|
||||
p2api.InsertAlternate(b)
|
||||
}, client.GetDefaultClient(), indexDb.GetDifficultyByHeight, indexDb.GetByTemplateId, p2api.ByMainId, p2api.LightByMainHeight, func(b *sidechain.PoolBlock) error {
|
||||
_, err := b.PreProcessBlock(p2api.Consensus(), &sidechain.NilDerivationCache{}, sidechain.PreAllocateShares(p2api.Consensus().ChainWindowSize*2), indexDb.GetDifficultyByHeight, indexDb.GetByTemplateId)
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
end := min(maxHeight-1, currentHeight+stride*strideSize+strideSize)
|
||||
utils.Logf("", "checking %d to %d", start, end)
|
||||
if headers, err := client.GetDefaultClient().GetBlockHeadersRangeResult(start, end, ctx); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
for _, h := range headers.Headers {
|
||||
if err := scanHeader(h); err != nil {
|
||||
utils.Panic(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// backfill any missing headers when p2pool was down
|
||||
for _, mainId := range backfillScan {
|
||||
utils.Logf("", "checking backfill %s", mainId)
|
||||
if header, err := client.GetDefaultClient().GetBlockHeaderByHash(mainId, ctx); err != nil {
|
||||
utils.Errorf("", "not found %s", mainId)
|
||||
} else {
|
||||
if err := scanHeader(*header); err != nil {
|
||||
utils.Panic(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupEventHandler(p2api, indexDb)
|
||||
|
||||
var doCheckOfOldBlocks atomic.Bool
|
||||
|
||||
doCheckOfOldBlocks.Store(true)
|
||||
|
||||
go func() {
|
||||
//do deep scan for any missed main headers or deep reorgs every once in a while
|
||||
for range time.Tick(time.Second * monero.BlockTime) {
|
||||
if !doCheckOfOldBlocks.Load() {
|
||||
continue
|
||||
}
|
||||
mainTip := indexDb.GetMainBlockTip()
|
||||
for h := mainTip.Height; h >= 0 && h >= (mainTip.Height-monero.TransactionUnlockTime); h-- {
|
||||
header := indexDb.GetMainBlockByHeight(h)
|
||||
if header == nil {
|
||||
break
|
||||
}
|
||||
cur, _ := client.GetDefaultClient().GetBlockHeaderByHash(header.Id, ctx)
|
||||
if cur == nil {
|
||||
break
|
||||
}
|
||||
go func() {
|
||||
sideBlocksLock.Lock()
|
||||
defer sideBlocksLock.Unlock()
|
||||
if err := scanHeader(*cur); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
//process older full blocks and sweeps
|
||||
for range time.Tick(time.Second * monero.BlockTime) {
|
||||
|
||||
actualTip := indexDb.GetMainBlockTip()
|
||||
mainTip := actualTip
|
||||
maxDepth := mainTip.Height - randomx.SeedHashEpochBlocks*4
|
||||
|
||||
//find top start height
|
||||
for h := mainTip.Height - monero.TransactionUnlockTime; h >= maxDepth; h-- {
|
||||
mainTip = indexDb.GetMainBlockByHeight(h)
|
||||
if mainTip == nil {
|
||||
continue
|
||||
}
|
||||
if isProcessed, ok := mainTip.GetMetadata("processed").(bool); ok && isProcessed {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if mainTip.Height == maxDepth {
|
||||
utils.Logf("", "Reached maxdepth %d: Use scansweeps to backfill data", maxDepth)
|
||||
}
|
||||
|
||||
for h := mainTip.Height - monero.MinerRewardUnlockTime; h <= actualTip.Height-monero.TransactionUnlockTime; h++ {
|
||||
b := indexDb.GetMainBlockByHeight(h)
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
if isProcessed, ok := b.GetMetadata("processed").(bool); ok && isProcessed {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := cmdutils.ProcessFullBlock(b, indexDb); err != nil {
|
||||
utils.Logf("", "error processing block %s at %d: %s", b.Id, b.Height, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if *debugListen != "" {
|
||||
go func() {
|
||||
if err := http.ListenAndServe(*debugListen, nil); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for range time.Tick(time.Second * 1) {
|
||||
currentTip := indexDb.GetSideBlockTip()
|
||||
currentMainTip := indexDb.GetMainBlockTip()
|
||||
|
||||
tip := p2api.Tip()
|
||||
mainTip := p2api.MainTip()
|
||||
|
||||
if tip == nil || mainTip == nil {
|
||||
utils.Errorf("", "could not fetch tip or main tip")
|
||||
continue
|
||||
}
|
||||
|
||||
if blockId(tip) != currentTip.TemplateId {
|
||||
if tip.Side.Height < currentTip.SideHeight {
|
||||
//wtf
|
||||
utils.Panicf("tip height less than ours, abort: %d < %d", tip.Side.Height, currentTip.SideHeight)
|
||||
} else {
|
||||
func() {
|
||||
sideBlocksLock.Lock()
|
||||
defer sideBlocksLock.Unlock()
|
||||
insertFromTip(tip)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
if mainTip.Id != currentMainTip.Id {
|
||||
if mainTip.Height < currentMainTip.Height {
|
||||
//wtf
|
||||
utils.Panicf("main tip height less than ours, abort: %d < %d", mainTip.Height, currentMainTip.Height)
|
||||
} else {
|
||||
var prevHash types.Hash
|
||||
for cur, _ := client.GetDefaultClient().GetBlockHeaderByHash(mainTip.Id, ctx); cur != nil; cur, _ = client.GetDefaultClient().GetBlockHeaderByHash(prevHash, ctx) {
|
||||
curHash, _ := types.HashFromString(cur.Hash)
|
||||
curDb := indexDb.GetMainBlockByHeight(cur.Height)
|
||||
if curDb != nil {
|
||||
if curDb.Id == curHash {
|
||||
break
|
||||
} else { //there has been a swap
|
||||
doCheckOfOldBlocks.Store(true)
|
||||
}
|
||||
}
|
||||
utils.Logf("MAIN", "Insert main block %d, id %s", cur.Height, curHash)
|
||||
func() {
|
||||
sideBlocksLock.Lock()
|
||||
defer sideBlocksLock.Unlock()
|
||||
doCheckOfOldBlocks.Store(true)
|
||||
|
||||
if err := scanHeader(*cur); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
prevHash, _ = types.HashFromString(cur.PrevHash)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -1,39 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/daemon
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0 => ../index
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0 => ../utils
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0
|
||||
nhooyr.io/websocket v1.8.10
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/floatdrop/lru v1.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,48 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
|
||||
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
519
cmd/daemon/ws.go
519
cmd/daemon/ws.go
|
@ -1,519 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/api"
|
||||
types2 "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"net/http"
|
||||
"nhooyr.io/websocket"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type listener struct {
|
||||
ListenerId uint64
|
||||
Write func(buf []byte)
|
||||
Context context.Context
|
||||
Cancel func()
|
||||
}
|
||||
|
||||
type SortedBuf[T any] struct {
|
||||
buf []T
|
||||
compare func(a, b T) int
|
||||
}
|
||||
|
||||
func NewSortedBuf[T any](size int, compare func(a, b T) int) *SortedBuf[T] {
|
||||
return &SortedBuf[T]{
|
||||
buf: make([]T, size, size+1),
|
||||
compare: compare,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SortedBuf[T]) Insert(value T) bool {
|
||||
if b.Has(value) {
|
||||
return false
|
||||
}
|
||||
b.buf = append(b.buf, value)
|
||||
slices.SortFunc(b.buf, func(i, j T) int {
|
||||
//keep highest value at 0
|
||||
return b.compare(i, j) * -1
|
||||
})
|
||||
b.buf = b.buf[:len(b.buf)-1]
|
||||
return b.Has(value)
|
||||
}
|
||||
|
||||
func (b *SortedBuf[T]) Remove(value T) {
|
||||
var zeroValue T
|
||||
for i, v := range b.buf {
|
||||
if b.compare(value, v) == 0 {
|
||||
b.buf[i] = zeroValue
|
||||
}
|
||||
}
|
||||
slices.SortFunc(b.buf, func(i, j T) int {
|
||||
//keep highest value at index 0
|
||||
return b.compare(i, j) * -1
|
||||
})
|
||||
}
|
||||
|
||||
func (b *SortedBuf[T]) Has(value T) bool {
|
||||
for _, v := range b.buf {
|
||||
if b.compare(value, v) == 0 {
|
||||
//value inserted
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var listenerLock sync.RWMutex
|
||||
var listenerIdCounter atomic.Uint64
|
||||
var listeners []*listener
|
||||
|
||||
func setupEventHandler(p2api *api.P2PoolApi, indexDb *index.Index) {
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "0.0.0.0:8787",
|
||||
ReadTimeout: time.Second * 2,
|
||||
Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
requestTime := time.Now()
|
||||
c, err := websocket.Accept(writer, request, nil)
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
listenerId := listenerIdCounter.Add(1)
|
||||
defer func() {
|
||||
listenerLock.Lock()
|
||||
defer listenerLock.Unlock()
|
||||
if i := slices.IndexFunc(listeners, func(listener *listener) bool {
|
||||
return listener.ListenerId == listenerId
|
||||
}); i != -1 {
|
||||
listeners = slices.Delete(listeners, i, i+1)
|
||||
}
|
||||
utils.Logf("WS", "Client %d detached after %.02f seconds", listenerId, time.Now().Sub(requestTime).Seconds())
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(request.Context())
|
||||
defer cancel()
|
||||
func() {
|
||||
listenerLock.Lock()
|
||||
defer listenerLock.Unlock()
|
||||
listeners = append(listeners, &listener{
|
||||
ListenerId: listenerId,
|
||||
Write: func(buf []byte) {
|
||||
ctx2, cancel2 := context.WithTimeout(ctx, time.Second*5)
|
||||
defer cancel2()
|
||||
if c.Write(ctx2, websocket.MessageText, buf) != nil {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
Context: ctx,
|
||||
Cancel: cancel,
|
||||
})
|
||||
utils.Logf("WS", "Client %d attached", listenerId)
|
||||
}()
|
||||
defer c.Close(websocket.StatusInternalError, "closing")
|
||||
|
||||
c.CloseRead(context.Background())
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
//wait
|
||||
}
|
||||
|
||||
}),
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
//remember last few template for events
|
||||
sideBlockBuffer := NewSortedBuf[*index.SideBlock](int(p2api.Consensus().ChainWindowSize*2), index.SortSideBlock)
|
||||
|
||||
//remember last few found main for events
|
||||
foundBlockBuffer := NewSortedBuf[*index.FoundBlock](10, func(a, b *index.FoundBlock) int {
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
if a == nil {
|
||||
return -1
|
||||
} else if b == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if a.MainBlock.Height < b.MainBlock.Height {
|
||||
return -1
|
||||
} else if a.MainBlock.Height > b.MainBlock.Height {
|
||||
return 1
|
||||
}
|
||||
|
||||
if a.SideHeight < b.SideHeight {
|
||||
return -1
|
||||
} else if a.SideHeight > b.SideHeight {
|
||||
return 1
|
||||
}
|
||||
//same height, sort by main id
|
||||
return a.MainBlock.Id.Compare(b.MainBlock.Id)
|
||||
})
|
||||
|
||||
//initialize
|
||||
tip := indexDb.GetSideBlockTip()
|
||||
for cur := tip; cur != nil; cur = indexDb.GetTipSideBlockByTemplateId(cur.ParentTemplateId) {
|
||||
if (cur.EffectiveHeight + p2api.Consensus().ChainWindowSize) < tip.EffectiveHeight {
|
||||
break
|
||||
}
|
||||
sideBlockBuffer.Insert(cur)
|
||||
index.QueryIterate(indexDb.GetSideBlocksByUncleOfId(cur.TemplateId), func(_ int, u *index.SideBlock) (stop bool) {
|
||||
sideBlockBuffer.Insert(u)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func() {
|
||||
foundBlocks, _ := indexDb.GetFoundBlocks("", 5)
|
||||
index.QueryIterate(foundBlocks, func(_ int, b *index.FoundBlock) (stop bool) {
|
||||
foundBlockBuffer.Insert(b)
|
||||
return false
|
||||
})
|
||||
}()
|
||||
|
||||
fillMainCoinbaseOutputs := func(outputs index.MainCoinbaseOutputs) (result index.MainCoinbaseOutputs) {
|
||||
result = make(index.MainCoinbaseOutputs, 0, len(outputs))
|
||||
for _, output := range outputs {
|
||||
miner := indexDb.GetMiner(output.Miner)
|
||||
output.MinerAddress = miner.Address()
|
||||
output.MinerAlias = miner.Alias()
|
||||
result = append(result, output)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fillFoundBlockResult := func(foundBlock *index.FoundBlock) *index.FoundBlock {
|
||||
miner := indexDb.GetMiner(foundBlock.Miner)
|
||||
foundBlock.MinerAddress = miner.Address()
|
||||
foundBlock.MinerAlias = miner.Alias()
|
||||
return foundBlock
|
||||
}
|
||||
|
||||
var minerData *types2.MinerData
|
||||
var minerDataLock sync.Mutex
|
||||
getMinerData := func(expectedHeight uint64) *types2.MinerData {
|
||||
minerDataLock.Lock()
|
||||
defer minerDataLock.Unlock()
|
||||
if minerData == nil || minerData.Height < expectedHeight {
|
||||
minerData = p2api.MinerData()
|
||||
}
|
||||
return minerData
|
||||
}
|
||||
|
||||
fillSideBlockResult := func(mainTip *index.MainBlock, sideBlock *index.SideBlock) *index.SideBlock {
|
||||
|
||||
miner := indexDb.GetMiner(sideBlock.Miner)
|
||||
sideBlock.MinerAddress = miner.Address()
|
||||
sideBlock.MinerAlias = miner.Alias()
|
||||
|
||||
mainTipAtHeight := indexDb.GetMainBlockByHeight(sideBlock.MainHeight)
|
||||
if mainTipAtHeight != nil {
|
||||
sideBlock.MinedMainAtHeight = mainTipAtHeight.Id == sideBlock.MainId
|
||||
sideBlock.MainDifficulty = mainTipAtHeight.Difficulty
|
||||
} else {
|
||||
minerData := getMinerData(sideBlock.MainHeight)
|
||||
if minerData.Height == sideBlock.MainHeight {
|
||||
sideBlock.MainDifficulty = minerData.Difficulty.Lo
|
||||
} else if mainTip.Height == sideBlock.MainHeight {
|
||||
sideBlock.MainDifficulty = mainTip.Difficulty
|
||||
}
|
||||
}
|
||||
|
||||
index.QueryIterate(indexDb.GetSideBlocksByUncleOfId(sideBlock.TemplateId), func(_ int, u *index.SideBlock) (stop bool) {
|
||||
sideBlock.Uncles = append(sideBlock.Uncles, index.SideBlockUncleEntry{
|
||||
TemplateId: u.TemplateId,
|
||||
Miner: u.Miner,
|
||||
SideHeight: u.SideHeight,
|
||||
Difficulty: u.Difficulty,
|
||||
})
|
||||
return false
|
||||
})
|
||||
return sideBlock
|
||||
|
||||
}
|
||||
|
||||
go func() {
|
||||
var blocksToReport []*index.SideBlock
|
||||
for range time.Tick(time.Second * 1) {
|
||||
//reuse
|
||||
blocksToReport = blocksToReport[:0]
|
||||
|
||||
func() {
|
||||
sideBlocksLock.RLock()
|
||||
defer sideBlocksLock.RUnlock()
|
||||
mainTip := indexDb.GetMainBlockTip()
|
||||
tip := indexDb.GetSideBlockTip()
|
||||
for cur := tip; cur != nil; cur = indexDb.GetTipSideBlockByTemplateId(cur.ParentTemplateId) {
|
||||
if (cur.EffectiveHeight + p2api.Consensus().ChainWindowSize) < tip.EffectiveHeight {
|
||||
break
|
||||
}
|
||||
var pushedNew bool
|
||||
|
||||
index.QueryIterate(indexDb.GetSideBlocksByUncleOfId(cur.TemplateId), func(_ int, u *index.SideBlock) (stop bool) {
|
||||
if sideBlockBuffer.Insert(u) {
|
||||
//first time seen
|
||||
pushedNew = true
|
||||
blocksToReport = append(blocksToReport, fillSideBlockResult(mainTip, u))
|
||||
}
|
||||
return false
|
||||
})
|
||||
if sideBlockBuffer.Insert(cur) {
|
||||
//first time seen
|
||||
pushedNew = true
|
||||
blocksToReport = append(blocksToReport, fillSideBlockResult(mainTip, cur))
|
||||
}
|
||||
|
||||
if !pushedNew {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
//sort for proper order
|
||||
slices.SortFunc(blocksToReport, func(a, b *index.SideBlock) int {
|
||||
if a.EffectiveHeight < b.EffectiveHeight {
|
||||
return -1
|
||||
} else if a.EffectiveHeight > b.EffectiveHeight {
|
||||
return 1
|
||||
}
|
||||
if a.SideHeight < b.SideHeight {
|
||||
return -1
|
||||
} else if a.SideHeight > b.SideHeight {
|
||||
return 1
|
||||
}
|
||||
//same height, sort by main id
|
||||
return a.MainId.Compare(b.MainId)
|
||||
})
|
||||
|
||||
func() {
|
||||
listenerLock.RLock()
|
||||
defer listenerLock.RUnlock()
|
||||
for _, b := range blocksToReport {
|
||||
buf, err := utils.MarshalJSON(&cmdutils.JSONEvent{
|
||||
Type: cmdutils.JSONEventSideBlock,
|
||||
SideBlock: b,
|
||||
FoundBlock: nil,
|
||||
MainCoinbaseOutputs: nil,
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, l := range listeners {
|
||||
select {
|
||||
case <-l.Context.Done():
|
||||
default:
|
||||
l.Write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
ts := time.Now().Unix()
|
||||
|
||||
// Send webhooks
|
||||
go func(b *index.SideBlock) {
|
||||
q, _ := indexDb.GetMinerWebHooks(b.Miner)
|
||||
index.QueryIterate(q, func(_ int, w *index.MinerWebHook) (stop bool) {
|
||||
if err := cmdutils.SendSideBlock(w, ts, b.MinerAddress, b); err != nil {
|
||||
utils.Logf("WebHook", "Error sending %s webhook to %s: type %s, url %s: %s", cmdutils.JSONEventSideBlock, b.MinerAddress.ToBase58(), w.Type, w.Url, err)
|
||||
}
|
||||
return false
|
||||
})
|
||||
}(b)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
var blocksToReport []*index.FoundBlock
|
||||
var unfoundBlocksToReport []*index.SideBlock
|
||||
for range time.Tick(time.Second * 1) {
|
||||
//reuse
|
||||
blocksToReport = blocksToReport[:0]
|
||||
unfoundBlocksToReport = unfoundBlocksToReport[:0]
|
||||
|
||||
func() {
|
||||
sideBlocksLock.RLock()
|
||||
defer sideBlocksLock.RUnlock()
|
||||
mainTip := indexDb.GetMainBlockTip()
|
||||
for _, m := range foundBlockBuffer.buf {
|
||||
if m != nil && indexDb.GetMainBlockById(m.MainBlock.Id) == nil {
|
||||
//unfound
|
||||
if b := indexDb.GetSideBlockByMainId(m.MainBlock.Id); b != nil {
|
||||
unfoundBlocksToReport = append(unfoundBlocksToReport, fillSideBlockResult(mainTip, indexDb.GetSideBlockByMainId(m.MainBlock.Id)))
|
||||
foundBlockBuffer.Remove(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func() {
|
||||
foundBlocks, _ := indexDb.GetFoundBlocks("", 5)
|
||||
index.QueryIterate(foundBlocks, func(_ int, b *index.FoundBlock) (stop bool) {
|
||||
if foundBlockBuffer.Insert(b) {
|
||||
//first time seen
|
||||
blocksToReport = append(blocksToReport, fillFoundBlockResult(b))
|
||||
}
|
||||
return false
|
||||
})
|
||||
}()
|
||||
}()
|
||||
|
||||
//sort for proper order
|
||||
slices.SortFunc(blocksToReport, func(a, b *index.FoundBlock) int {
|
||||
if a.MainBlock.Height < b.MainBlock.Height {
|
||||
return -1
|
||||
} else if a.MainBlock.Height > b.MainBlock.Height {
|
||||
return 1
|
||||
}
|
||||
if a.EffectiveHeight < b.EffectiveHeight {
|
||||
return -1
|
||||
} else if a.EffectiveHeight > b.EffectiveHeight {
|
||||
return 1
|
||||
}
|
||||
if a.SideHeight < b.SideHeight {
|
||||
return -1
|
||||
} else if a.SideHeight > b.SideHeight {
|
||||
return 1
|
||||
}
|
||||
//same height, sort by main id
|
||||
return a.MainBlock.Id.Compare(b.MainBlock.Id)
|
||||
})
|
||||
|
||||
//sort for proper order
|
||||
slices.SortFunc(unfoundBlocksToReport, func(a, b *index.SideBlock) int {
|
||||
if a.MainHeight < b.MainHeight {
|
||||
return -1
|
||||
} else if a.MainHeight > b.MainHeight {
|
||||
return 1
|
||||
}
|
||||
|
||||
return index.SortSideBlock(a, b)
|
||||
})
|
||||
|
||||
func() {
|
||||
listenerLock.RLock()
|
||||
defer listenerLock.RUnlock()
|
||||
for _, b := range unfoundBlocksToReport {
|
||||
buf, err := utils.MarshalJSON(&cmdutils.JSONEvent{
|
||||
Type: cmdutils.JSONEventOrphanedBlock,
|
||||
SideBlock: b,
|
||||
FoundBlock: nil,
|
||||
MainCoinbaseOutputs: nil,
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, l := range listeners {
|
||||
select {
|
||||
case <-l.Context.Done():
|
||||
default:
|
||||
l.Write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
ts := time.Now().Unix()
|
||||
|
||||
// Send webhooks
|
||||
go func(b *index.SideBlock) {
|
||||
q, _ := indexDb.GetMinerWebHooks(b.Miner)
|
||||
index.QueryIterate(q, func(_ int, w *index.MinerWebHook) (stop bool) {
|
||||
if err := cmdutils.SendOrphanedBlock(w, ts, b.MinerAddress, b); err != nil {
|
||||
utils.Logf("WebHook", "Error sending %s webhook to %s: type %s, url %s: %s", cmdutils.JSONEventOrphanedBlock, b.MinerAddress.ToBase58(), w.Type, w.Url, err)
|
||||
}
|
||||
return false
|
||||
})
|
||||
}(b)
|
||||
}
|
||||
for _, b := range blocksToReport {
|
||||
coinbaseOutputs := func() index.MainCoinbaseOutputs {
|
||||
mainOutputs, err := indexDb.GetMainCoinbaseOutputs(b.MainBlock.CoinbaseId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer mainOutputs.Close()
|
||||
return fillMainCoinbaseOutputs(index.IterateToSliceWithoutPointer[index.MainCoinbaseOutput](mainOutputs))
|
||||
}()
|
||||
|
||||
if len(coinbaseOutputs) == 0 {
|
||||
//report next time
|
||||
foundBlockBuffer.Remove(b)
|
||||
continue
|
||||
}
|
||||
buf, err := utils.MarshalJSON(&cmdutils.JSONEvent{
|
||||
Type: cmdutils.JSONEventFoundBlock,
|
||||
SideBlock: nil,
|
||||
FoundBlock: b,
|
||||
MainCoinbaseOutputs: coinbaseOutputs,
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, l := range listeners {
|
||||
select {
|
||||
case <-l.Context.Done():
|
||||
default:
|
||||
l.Write(buf)
|
||||
}
|
||||
}
|
||||
|
||||
includingHeight := max(b.EffectiveHeight, b.SideHeight)
|
||||
if uint64(b.WindowDepth) > includingHeight {
|
||||
includingHeight = 0
|
||||
} else {
|
||||
includingHeight -= uint64(b.WindowDepth)
|
||||
}
|
||||
|
||||
// Send webhooks on outputs
|
||||
for _, o := range coinbaseOutputs {
|
||||
payout := &index.Payout{
|
||||
Miner: o.Miner,
|
||||
TemplateId: b.MainBlock.SideTemplateId,
|
||||
SideHeight: b.SideHeight,
|
||||
UncleOf: b.UncleOf,
|
||||
MainId: b.MainBlock.Id,
|
||||
MainHeight: b.MainBlock.Height,
|
||||
Timestamp: b.MainBlock.Timestamp,
|
||||
CoinbaseId: b.MainBlock.CoinbaseId,
|
||||
Reward: o.Value,
|
||||
PrivateKey: b.MainBlock.CoinbasePrivateKey,
|
||||
Index: uint64(o.Index),
|
||||
GlobalOutputIndex: o.GlobalOutputIndex,
|
||||
IncludingHeight: includingHeight,
|
||||
}
|
||||
|
||||
addr := o.MinerAddress
|
||||
|
||||
ts := time.Now().Unix()
|
||||
|
||||
// One goroutine per entry
|
||||
go func() {
|
||||
q, _ := indexDb.GetMinerWebHooks(payout.Miner)
|
||||
index.QueryIterate(q, func(_ int, w *index.MinerWebHook) (stop bool) {
|
||||
if err := cmdutils.SendFoundBlock(w, ts, addr, b, coinbaseOutputs); err != nil {
|
||||
utils.Logf("WebHook", "Error sending %s webhook to %s: type %s, url %s: %s", cmdutils.JSONEventFoundBlock, addr.ToBase58(), w.Type, w.Url, err)
|
||||
}
|
||||
|
||||
if err := cmdutils.SendPayout(w, ts, addr, payout); err != nil {
|
||||
utils.Logf("WebHook", "Error sending %s webhook to %s: type %s, url %s: %s", cmdutils.JSONEventPayout, addr.ToBase58(), w.Type, w.Url, err)
|
||||
}
|
||||
return false
|
||||
})
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/httputils
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
require git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
|
||||
require (
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/floatdrop/lru v1.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,12 +0,0 @@
|
|||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
@ -1,80 +0,0 @@
|
|||
package httputils
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func EncodeJson(r *http.Request, writer io.Writer, d any) error {
|
||||
encoder := utils.NewJSONEncoder(writer)
|
||||
if strings.Index(strings.ToLower(r.Header.Get("user-agent")), "mozilla") != -1 {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
return encoder.EncodeWithOption(d, utils.JsonEncodeOptions...)
|
||||
}
|
||||
|
||||
// StreamJsonChan Streams a channel of values into a JSON list via a writer.
|
||||
func StreamJsonChan[T any](r *http.Request, writer io.Writer, stream <-chan T) error {
|
||||
encoder := utils.NewJSONEncoder(writer)
|
||||
if strings.Index(strings.ToLower(r.Header.Get("user-agent")), "mozilla") != -1 {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
// Write start of JSON list
|
||||
_, _ = writer.Write([]byte{'[', 0xa})
|
||||
var count uint64
|
||||
defer func() {
|
||||
// Write end of JSON list
|
||||
_, _ = writer.Write([]byte{0xa, ']'})
|
||||
|
||||
// Empty channel
|
||||
for range stream {
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
for v := range stream {
|
||||
if count > 0 {
|
||||
// Write separator between list fields
|
||||
_, _ = writer.Write([]byte{',', 0xa})
|
||||
}
|
||||
if err := encoder.EncodeWithOption(v, utils.JsonEncodeOptions...); err != nil {
|
||||
return err
|
||||
}
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StreamJsonIterator Streams an iterator of values into a JSON list via a writer.
|
||||
func StreamJsonIterator[T any](r *http.Request, writer io.Writer, next func() (int, *T)) error {
|
||||
encoder := utils.NewJSONEncoder(writer)
|
||||
if strings.Index(strings.ToLower(r.Header.Get("user-agent")), "mozilla") != -1 {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
// Write start of JSON list
|
||||
_, _ = writer.Write([]byte{'[', 0xa})
|
||||
var count uint64
|
||||
defer func() {
|
||||
// Write end of JSON list
|
||||
_, _ = writer.Write([]byte{0xa, ']'})
|
||||
}()
|
||||
|
||||
for {
|
||||
_, v := next()
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
if count > 0 {
|
||||
// Write separator between list fields
|
||||
_, _ = writer.Write([]byte{',', 0xa})
|
||||
}
|
||||
if err := encoder.EncodeWithOption(v, utils.JsonEncodeOptions...); err != nil {
|
||||
return err
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
)
|
||||
|
||||
type FoundBlock struct {
|
||||
MainBlock MainBlock `json:"main_block"`
|
||||
|
||||
SideHeight uint64 `json:"side_height"`
|
||||
Miner uint64 `json:"miner"`
|
||||
UncleOf types.Hash `json:"uncle_of,omitempty"`
|
||||
EffectiveHeight uint64 `json:"effective_height"`
|
||||
WindowDepth uint32 `json:"window_depth"`
|
||||
WindowOutputs uint32 `json:"window_outputs"`
|
||||
TransactionCount uint32 `json:"transaction_count"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
CumulativeDifficulty types.Difficulty `json:"cumulative_difficulty"`
|
||||
Inclusion BlockInclusion `json:"inclusion"`
|
||||
|
||||
// Extra information filled just for JSON purposes
|
||||
MinerAddress *address.Address `json:"miner_address,omitempty"`
|
||||
MinerAlias string `json:"miner_alias,omitempty"`
|
||||
}
|
||||
|
||||
func (b *FoundBlock) ScanFromRow(_ *sidechain.Consensus, row RowScanInterface) error {
|
||||
if err := row.Scan(
|
||||
&b.MainBlock.Id, &b.MainBlock.Height, &b.MainBlock.Timestamp, &b.MainBlock.Reward, &b.MainBlock.CoinbaseId, &b.MainBlock.CoinbasePrivateKey, &b.MainBlock.Difficulty, &b.MainBlock.SideTemplateId,
|
||||
&b.SideHeight, &b.Miner, &b.UncleOf, &b.EffectiveHeight, &b.WindowDepth, &b.WindowOutputs, &b.TransactionCount, &b.Difficulty, &b.CumulativeDifficulty, &b.Inclusion,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
CREATE OR REPLACE FUNCTION count_estimate(query text) RETURNS integer AS $$
|
||||
DECLARE
|
||||
rec record;
|
||||
rows integer;
|
||||
BEGIN
|
||||
FOR rec IN EXECUTE 'EXPLAIN ' || query LOOP
|
||||
rows := substring(rec."QUERY PLAN" FROM ' rows=([[:digit:]]+)');
|
||||
EXIT WHEN rows IS NOT NULL;
|
||||
END LOOP;
|
||||
RETURN rows;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE STRICT;
|
|
@ -1,32 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a
|
||||
github.com/floatdrop/lru v1.3.0
|
||||
github.com/lib/pq v1.10.9
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,46 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
1448
cmd/index/index.go
1448
cmd/index/index.go
|
@ -1,1448 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"github.com/floatdrop/lru"
|
||||
"github.com/lib/pq"
|
||||
"io"
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Index struct {
|
||||
consensus *sidechain.Consensus
|
||||
|
||||
getDifficultyByHeight block.GetDifficultyByHeightFunc
|
||||
getSeedByHeight block.GetSeedByHeightFunc
|
||||
getByTemplateId sidechain.GetByTemplateIdFunc
|
||||
derivationCache sidechain.DerivationCacheInterface
|
||||
blockCache *lru.LRU[types.Hash, *sidechain.PoolBlock]
|
||||
|
||||
handle *sql.DB
|
||||
statements struct {
|
||||
GetMinerById *sql.Stmt
|
||||
GetMinerByAddress *sql.Stmt
|
||||
GetMinerByAlias *sql.Stmt
|
||||
InsertMiner *sql.Stmt
|
||||
TipSideBlock *sql.Stmt
|
||||
TipSideBlocksTemplateId *sql.Stmt
|
||||
InsertOrUpdateSideBlock *sql.Stmt
|
||||
TipMainBlock *sql.Stmt
|
||||
GetMainBlockByHeight *sql.Stmt
|
||||
GetMainBlockById *sql.Stmt
|
||||
GetSideBlockByMainId *sql.Stmt
|
||||
GetSideBlockByUncleId *sql.Stmt
|
||||
}
|
||||
caches struct {
|
||||
minerLock sync.RWMutex
|
||||
miner map[uint64]*Miner
|
||||
}
|
||||
|
||||
views map[string]string
|
||||
}
|
||||
|
||||
//go:embed schema.sql
|
||||
var dbSchema string
|
||||
|
||||
//go:embed functions/*.sql
|
||||
var dbFunctions embed.FS
|
||||
|
||||
func OpenIndex(connStr string, consensus *sidechain.Consensus, difficultyByHeight block.GetDifficultyByHeightFunc, getSeedByHeight block.GetSeedByHeightFunc, getByTemplateId sidechain.GetByTemplateIdFunc) (index *Index, err error) {
|
||||
index = &Index{
|
||||
consensus: consensus,
|
||||
getDifficultyByHeight: difficultyByHeight,
|
||||
getSeedByHeight: getSeedByHeight,
|
||||
getByTemplateId: getByTemplateId,
|
||||
derivationCache: sidechain.NewDerivationLRUCache(),
|
||||
blockCache: lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4)),
|
||||
views: make(map[string]string),
|
||||
}
|
||||
if index.handle, err = sql.Open("postgres", connStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
index.handle.SetMaxIdleConns(8)
|
||||
|
||||
tx, err := index.handle.BeginTx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
viewMatch := regexp.MustCompile("CREATE (MATERIALIZED |)VIEW ([^ \n\t]+?)(_v[0-9]+)? AS\n")
|
||||
immv := regexp.MustCompile("SELECT (create_immv)\\('([^ \n\t']+?)(_v[0-9]+)?',")
|
||||
for _, statement := range strings.Split(dbSchema, ";") {
|
||||
var matches []string
|
||||
if matches = viewMatch.FindStringSubmatch(statement); matches == nil {
|
||||
matches = immv.FindStringSubmatch(statement)
|
||||
}
|
||||
if matches != nil {
|
||||
isMaterialized := matches[1] == "MATERIALIZED "
|
||||
isImmv := matches[1] == "create_immv"
|
||||
viewName := matches[2]
|
||||
fullViewName := viewName
|
||||
//base view for materialized one
|
||||
|
||||
if len(matches[3]) != 0 {
|
||||
fullViewName = viewName + matches[3]
|
||||
}
|
||||
index.views[viewName] = fullViewName
|
||||
if row, err := tx.Query(fmt.Sprintf("SELECT relname, relkind FROM pg_class WHERE relname LIKE '%s%%';", viewName)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
var entries []struct{ n, kind string }
|
||||
var exists bool
|
||||
if err = func() error {
|
||||
defer row.Close()
|
||||
for row.Next() {
|
||||
var n, kind string
|
||||
if err = row.Scan(&n, &kind); err != nil {
|
||||
return err
|
||||
}
|
||||
entries = append(entries, struct{ n, kind string }{n: n, kind: kind})
|
||||
}
|
||||
return row.Err()
|
||||
}(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, e := range entries {
|
||||
if e.kind == "m" && fullViewName != e.n {
|
||||
if _, err := tx.Exec(fmt.Sprintf("DROP MATERIALIZED VIEW %s CASCADE;", e.n)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if e.kind == "v" && fullViewName != e.n {
|
||||
if _, err := tx.Exec(fmt.Sprintf("DROP VIEW %s CASCADE;", e.n)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if e.kind == "r" && fullViewName != e.n {
|
||||
if _, err := tx.Exec(fmt.Sprintf("DROP TABLE %s CASCADE;", e.n)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if e.kind != "i" {
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if _, err := tx.Exec(statement); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Do first refresh
|
||||
if isMaterialized {
|
||||
if _, err := tx.Exec(fmt.Sprintf("REFRESH MATERIALIZED VIEW %s;", fullViewName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if isImmv {
|
||||
if _, err := tx.Exec(fmt.Sprintf("SELECT refresh_immv('%s', true);", fullViewName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if _, err := tx.Exec(statement); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if dir, err := dbFunctions.ReadDir("functions"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, e := range dir {
|
||||
f, err := dbFunctions.Open(path.Join("functions", e.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data []byte
|
||||
func() {
|
||||
defer f.Close()
|
||||
data, err = io.ReadAll(f)
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(string(data)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index.statements.GetMinerById, err = index.handle.Prepare("SELECT " + MinerSelectFields + " FROM miners WHERE id = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if index.statements.GetMinerByAddress, err = index.handle.Prepare("SELECT " + MinerSelectFields + " FROM miners WHERE spend_public_key = $1 AND view_public_key = $2;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if index.statements.GetMinerByAlias, err = index.handle.Prepare("SELECT " + MinerSelectFields + " FROM miners WHERE alias = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if index.statements.InsertMiner, err = index.handle.Prepare("INSERT INTO miners (spend_public_key, view_public_key) VALUES ($1, $2) RETURNING " + MinerSelectFields + ";"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if index.statements.TipSideBlocksTemplateId, err = index.PrepareSideBlocksByQueryStatement("WHERE template_id = $1 AND effective_height = side_height AND inclusion = $2;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index.statements.InsertOrUpdateSideBlock, err = index.handle.Prepare("INSERT INTO side_blocks (" + SideBlockSelectFields + ") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) ON CONFLICT (main_id) DO UPDATE SET uncle_of = $7, effective_height = $8, inclusion = $21;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index.statements.GetMainBlockByHeight, err = index.PrepareMainBlocksByQueryStatement("WHERE height = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index.statements.GetMainBlockById, err = index.PrepareMainBlocksByQueryStatement("WHERE id = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index.statements.GetSideBlockByMainId, err = index.PrepareSideBlocksByQueryStatement("WHERE main_id = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index.statements.GetSideBlockByUncleId, err = index.PrepareSideBlocksByQueryStatement("WHERE uncle_of = $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index.statements.TipSideBlock, err = index.PrepareSideBlocksByQueryStatement("WHERE inclusion = $1 ORDER BY side_height DESC LIMIT 1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index.statements.TipMainBlock, err = index.PrepareMainBlocksByQueryStatement("ORDER BY height DESC LIMIT 1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
index.caches.miner = make(map[uint64]*Miner)
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (i *Index) GetDifficultyByHeight(height uint64) types.Difficulty {
|
||||
if mb := i.GetMainBlockByHeight(height); mb != nil {
|
||||
return types.DifficultyFrom64(mb.Difficulty)
|
||||
}
|
||||
return i.getDifficultyByHeight(height)
|
||||
}
|
||||
|
||||
func (i *Index) GetSeedByHeight(height uint64) types.Hash {
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
if mb := i.GetMainBlockByHeight(seedHeight); mb != nil {
|
||||
return mb.Id
|
||||
}
|
||||
return i.getSeedByHeight(height)
|
||||
}
|
||||
|
||||
func (i *Index) GetByTemplateId(id types.Hash) *sidechain.PoolBlock {
|
||||
if v := i.blockCache.Get(id); v != nil {
|
||||
return *v
|
||||
} else {
|
||||
if b := i.getByTemplateId(id); b != nil {
|
||||
i.blockCache.Set(id, b)
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) CachePoolBlock(b *sidechain.PoolBlock) {
|
||||
i.blockCache.Set(b.SideTemplateId(i.consensus), b)
|
||||
}
|
||||
|
||||
func (i *Index) Consensus() *sidechain.Consensus {
|
||||
return i.consensus
|
||||
}
|
||||
|
||||
func (i *Index) GetMiner(miner uint64) *Miner {
|
||||
if m := func() *Miner {
|
||||
i.caches.minerLock.RLock()
|
||||
defer i.caches.minerLock.RUnlock()
|
||||
return i.caches.miner[miner]
|
||||
}(); m != nil {
|
||||
return m
|
||||
} else if m = i.getMiner(miner); m != nil {
|
||||
i.caches.minerLock.Lock()
|
||||
defer i.caches.minerLock.Unlock()
|
||||
i.caches.miner[miner] = m
|
||||
return m
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) getMiner(miner uint64) *Miner {
|
||||
if rows, err := i.statements.GetMinerById.Query(miner); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
return i.scanMiner(rows)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMinerByAlias(alias string) *Miner {
|
||||
if rows, err := i.statements.GetMinerByAlias.Query(alias); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
return i.scanMiner(rows)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMinerByStringAddress(addr string) *Miner {
|
||||
minerAddr := address.FromBase58(addr)
|
||||
if minerAddr != nil {
|
||||
return i.GetMinerByAddress(minerAddr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetMinerByAddress(addr *address.Address) *Miner {
|
||||
if network, _ := i.consensus.NetworkType.AddressNetwork(); network != addr.Network {
|
||||
return nil
|
||||
}
|
||||
|
||||
spendPub, viewPub := addr.SpendPublicKey().AsBytes(), addr.ViewPublicKey().AsBytes()
|
||||
if rows, err := i.statements.GetMinerByAddress.Query(&spendPub, &viewPub); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
return i.scanMiner(rows)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMinerByPackedAddress(addr address.PackedAddress) *Miner {
|
||||
if rows, err := i.statements.GetMinerByAddress.Query(&addr[0], &addr[1]); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
return i.scanMiner(rows)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetOrCreateMinerByAddress(addr *address.Address) *Miner {
|
||||
if m := i.GetMinerByAddress(addr); m != nil {
|
||||
return m
|
||||
} else {
|
||||
spendPub, viewPub := addr.SpendPublicKey().AsSlice(), addr.ViewPublicKey().AsSlice()
|
||||
if rows, err := i.statements.InsertMiner.Query(&spendPub, &viewPub); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
return i.scanMiner(rows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetOrCreateMinerPackedAddress(addr address.PackedAddress) *Miner {
|
||||
if m := i.GetMinerByPackedAddress(addr); m != nil {
|
||||
return m
|
||||
} else {
|
||||
if rows, err := i.statements.InsertMiner.Query(&addr[0], &addr[1]); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer rows.Close()
|
||||
return i.scanMiner(rows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) SetMinerAlias(minerId uint64, alias string) error {
|
||||
miner := i.GetMiner(minerId)
|
||||
if miner == nil {
|
||||
return nil
|
||||
}
|
||||
if alias == "" {
|
||||
if err := i.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 := i.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
|
||||
}
|
||||
|
||||
func (i *Index) GetView(k string) string {
|
||||
return i.views[k]
|
||||
}
|
||||
|
||||
func (i *Index) Query(query string, callback func(row RowScanInterface) error, params ...any) error {
|
||||
var parentError error
|
||||
if stmt, err := i.handle.Prepare(query); err != nil {
|
||||
parentError = err
|
||||
} else {
|
||||
defer stmt.Close()
|
||||
parentError = i.QueryStatement(stmt, callback, params...)
|
||||
}
|
||||
return parentError
|
||||
}
|
||||
|
||||
func (i *Index) 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 (i *Index) PrepareSideBlocksByQueryStatement(where string) (stmt *sql.Stmt, err error) {
|
||||
return i.handle.Prepare(fmt.Sprintf("SELECT "+SideBlockSelectFields+" FROM side_blocks %s;", where))
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksByQuery(where string, params ...any) (QueryIterator[SideBlock], error) {
|
||||
if stmt, err := i.PrepareSideBlocksByQueryStatement(where); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return i.getSideBlocksByQueryStatement(stmt, params...)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) getSideBlocksByQueryStatement(stmt *sql.Stmt, params ...any) (QueryIterator[SideBlock], error) {
|
||||
if r, err := queryStatement[SideBlock](i, stmt, params...); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksByQueryStatement(stmt *sql.Stmt, params ...any) (QueryIterator[SideBlock], error) {
|
||||
if r, err := queryStatement[SideBlock](i, stmt, params...); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) PrepareMainBlocksByQueryStatement(where string) (stmt *sql.Stmt, err error) {
|
||||
return i.handle.Prepare(fmt.Sprintf("SELECT "+MainBlockSelectFields+" FROM main_blocks %s;", where))
|
||||
}
|
||||
|
||||
func (i *Index) GetShares(limit, minerId uint64, onlyBlocks bool, inclusion BlockInclusion) (QueryIterator[SideBlock], error) {
|
||||
if limit == 0 {
|
||||
if minerId != 0 {
|
||||
if onlyBlocks {
|
||||
return i.GetSideBlocksByQuery("WHERE miner = $1 AND uncle_of IS NULL AND inclusion = $2 ORDER BY side_height DESC;", minerId, inclusion)
|
||||
} else {
|
||||
return i.GetSideBlocksByQuery("WHERE miner = $1 AND inclusion = $2 ORDER BY side_height DESC, timestamp DESC;", minerId, inclusion)
|
||||
}
|
||||
} else {
|
||||
if onlyBlocks {
|
||||
return i.GetSideBlocksByQuery("WHERE uncle_of IS NULL AND inclusion = $1 ORDER BY side_height DESC;", inclusion)
|
||||
} else {
|
||||
return i.GetSideBlocksByQuery("ORDER BY side_height AND inclusion = $1 DESC, timestamp DESC;", inclusion)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if minerId != 0 {
|
||||
if onlyBlocks {
|
||||
return i.GetSideBlocksByQuery("WHERE miner = $1 AND uncle_of IS NULL AND inclusion = $3 ORDER BY side_height DESC LIMIT $2;", minerId, limit, inclusion)
|
||||
} else {
|
||||
return i.GetSideBlocksByQuery("WHERE miner = $1 AND inclusion = $3 ORDER BY side_height DESC, timestamp DESC LIMIT $2;", minerId, limit, inclusion)
|
||||
}
|
||||
} else {
|
||||
if onlyBlocks {
|
||||
return i.GetSideBlocksByQuery("WHERE uncle_of IS NULL AND inclusion = $2 ORDER BY side_height DESC LIMIT $1;", limit, inclusion)
|
||||
} else {
|
||||
return i.GetSideBlocksByQuery("WHERE inclusion = $2 ORDER BY side_height DESC, timestamp DESC LIMIT $1;", limit, inclusion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetFoundBlocks(where string, limit uint64, params ...any) (QueryIterator[FoundBlock], error) {
|
||||
if stmt, err := i.handle.Prepare(fmt.Sprintf("SELECT * FROM "+i.views["found_main_blocks"]+" %s ORDER BY main_height DESC LIMIT %d;", where, limit)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if r, err := queryStatement[FoundBlock](i, stmt, params...); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMainBlocksByQuery(where string, params ...any) (QueryIterator[MainBlock], error) {
|
||||
if stmt, err := i.PrepareMainBlocksByQueryStatement(where); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return i.getMainBlocksByQueryStatement(stmt, params...)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) getMainBlocksByQueryStatement(stmt *sql.Stmt, params ...any) (QueryIterator[MainBlock], error) {
|
||||
if r, err := queryStatement[MainBlock](i, stmt, params...); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMainBlocksByQueryStatement(stmt *sql.Stmt, params ...any) (QueryIterator[MainBlock], error) {
|
||||
if r, err := queryStatement[MainBlock](i, stmt, params...); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMainBlockById(id types.Hash) (b *MainBlock) {
|
||||
if r, err := i.GetMainBlocksByQueryStatement(i.statements.GetMainBlockById, id[:]); err != nil {
|
||||
utils.Print(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Print(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetMainBlockTip() (b *MainBlock) {
|
||||
if r, err := i.GetMainBlocksByQueryStatement(i.statements.TipMainBlock); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetMainBlockByCoinbaseId(id types.Hash) (b *MainBlock) {
|
||||
if r, err := i.GetMainBlocksByQuery("WHERE coinbase_id = $1;", id[:]); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetMainBlockByGlobalOutputIndex(globalOutputIndex uint64) (b *MainBlock) {
|
||||
if r, err := i.GetMainBlocksByQuery("WHERE coinbase_id = (SELECT id FROM main_coinbase_outputs WHERE global_output_index = $1 LIMIT 1);", globalOutputIndex); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetMainBlockByHeight(height uint64) (b *MainBlock) {
|
||||
if r, err := i.GetMainBlocksByQueryStatement(i.statements.GetMainBlockByHeight, height); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlockByMainId(id types.Hash) (b *SideBlock) {
|
||||
if r, err := i.GetSideBlocksByQueryStatement(i.statements.GetSideBlockByMainId, id[:]); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksByTemplateId(id types.Hash) QueryIterator[SideBlock] {
|
||||
if r, err := i.GetSideBlocksByQuery("WHERE template_id = $1;", id[:]); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksByUncleOfId(id types.Hash) QueryIterator[SideBlock] {
|
||||
if r, err := i.GetSideBlocksByQueryStatement(i.statements.GetSideBlockByUncleId, id[:]); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetTipSideBlockByTemplateId(id types.Hash) (b *SideBlock) {
|
||||
if r, err := i.GetSideBlocksByQueryStatement(i.statements.TipSideBlocksTemplateId, id[:], InclusionInVerifiedChain); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksByMainHeight(height uint64) QueryIterator[SideBlock] {
|
||||
if r, err := i.GetSideBlocksByQuery("WHERE main_height = $1;", height); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksByHeight(height uint64) QueryIterator[SideBlock] {
|
||||
if r, err := i.GetSideBlocksByQuery("WHERE side_height = $1;", height); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetTipSideBlockByHeight(height uint64) (b *SideBlock) {
|
||||
if r, err := i.GetSideBlocksByQuery("WHERE side_height = $1 AND effective_height = $2 AND inclusion = $3;", height, height, InclusionInVerifiedChain); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlockTip() (b *SideBlock) {
|
||||
if r, err := i.GetSideBlocksByQueryStatement(i.statements.TipSideBlock, InclusionInVerifiedChain); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
defer r.Close()
|
||||
if _, b = r.Next(); b == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksInPPLNSWindow(tip *SideBlock) QueryIterator[SideBlock] {
|
||||
return i.GetSideBlocksInWindow(tip.SideHeight, uint64(tip.WindowDepth))
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksInWindow(startHeight, windowSize uint64) QueryIterator[SideBlock] {
|
||||
if startHeight < windowSize {
|
||||
windowSize = startHeight
|
||||
}
|
||||
|
||||
if r, err := i.GetSideBlocksByQuery("WHERE effective_height <= $1 AND effective_height > $2 AND inclusion = $3 ORDER BY effective_height DESC, side_height DESC;", startHeight, startHeight-windowSize, InclusionInVerifiedChain); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlocksByMinerIdInWindow(minerId, startHeight, windowSize uint64) QueryIterator[SideBlock] {
|
||||
if startHeight < windowSize {
|
||||
windowSize = startHeight
|
||||
}
|
||||
|
||||
if r, err := i.GetSideBlocksByQuery("WHERE miner = $1 AND effective_height <= $2 AND effective_height > $3 AND inclusion = $4 ORDER BY effective_height DESC, side_height DESC;", minerId, startHeight, startHeight-windowSize, InclusionInVerifiedChain); err != nil {
|
||||
utils.Error(err)
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) InsertOrUpdateSideBlock(b *SideBlock) error {
|
||||
if b.IsTipOfHeight() {
|
||||
if oldBlock := i.GetTipSideBlockByHeight(b.SideHeight); oldBlock != nil {
|
||||
if oldBlock.MainId != b.MainId {
|
||||
//conflict resolution, change other block status
|
||||
if oldBlock.TemplateId == oldBlock.TemplateId {
|
||||
oldBlock.Inclusion = InclusionAlternateInVerifiedChain
|
||||
} else {
|
||||
//mark as orphan if templates don't match. if uncle it will be adjusted later
|
||||
oldBlock.Inclusion = InclusionOrphan
|
||||
}
|
||||
if err := i.InsertOrUpdateSideBlock(oldBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parentId any = &b.ParentTemplateId
|
||||
if b.SideHeight == 0 {
|
||||
parentId = types.ZeroHash[:]
|
||||
}
|
||||
|
||||
return i.QueryStatement(
|
||||
i.statements.InsertOrUpdateSideBlock,
|
||||
nil,
|
||||
&b.MainId,
|
||||
b.MainHeight,
|
||||
&b.TemplateId,
|
||||
b.SideHeight,
|
||||
parentId,
|
||||
b.Miner,
|
||||
&b.UncleOf,
|
||||
b.EffectiveHeight,
|
||||
b.Nonce,
|
||||
b.ExtraNonce,
|
||||
b.Timestamp,
|
||||
b.SoftwareId,
|
||||
b.SoftwareVersion,
|
||||
b.WindowDepth,
|
||||
b.WindowOutputs,
|
||||
b.TransactionCount,
|
||||
b.Difficulty,
|
||||
&b.CumulativeDifficulty,
|
||||
b.PowDifficulty,
|
||||
&b.PowHash,
|
||||
b.Inclusion,
|
||||
)
|
||||
}
|
||||
|
||||
func (i *Index) InsertOrUpdateMainBlock(b *MainBlock) error {
|
||||
if oldBlock := i.GetMainBlockByHeight(b.Height); oldBlock != nil {
|
||||
if oldBlock.Id != b.Id {
|
||||
//conflict resolution
|
||||
if tx, err := i.handle.BeginTx(context.Background(), nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.Exec("DELETE FROM main_coinbase_outputs WHERE id = $1;", oldBlock.CoinbaseId[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = tx.Exec("DELETE FROM main_blocks WHERE id = $1;", oldBlock.Id[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metadataJson, _ := utils.MarshalJSON(b.Metadata)
|
||||
|
||||
if tx, err := i.handle.BeginTx(context.Background(), nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.Exec(
|
||||
"INSERT INTO main_blocks (id, height, timestamp, reward, coinbase_id, difficulty, metadata, side_template_id, coinbase_private_key) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8, $9) ON CONFLICT (id) DO UPDATE SET metadata = $7, side_template_id = $8, coinbase_private_key = $9;",
|
||||
b.Id[:],
|
||||
b.Height,
|
||||
b.Timestamp,
|
||||
b.Reward,
|
||||
b.CoinbaseId[:],
|
||||
b.Difficulty,
|
||||
metadataJson,
|
||||
&b.SideTemplateId,
|
||||
&b.CoinbasePrivateKey,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetPayoutsByMinerId(minerId uint64, limit uint64) (r QueryIterator[Payout], err error) {
|
||||
var stmt *sql.Stmt
|
||||
var params []any
|
||||
|
||||
if limit == 0 {
|
||||
if stmt, err = i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 ORDER BY main_height DESC;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = []any{minerId}
|
||||
} else {
|
||||
if stmt, err = i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 ORDER BY main_height DESC LIMIT $2;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = []any{minerId, limit}
|
||||
}
|
||||
|
||||
if r, err := queryStatement[Payout](i, stmt, params...); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetPayoutsByMinerIdFromHeight(minerId uint64, height uint64) (QueryIterator[Payout], error) {
|
||||
if stmt, err := i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 AND main_height >= $2 ORDER BY main_height DESC;"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if r, err := queryStatement[Payout](i, stmt, minerId, height); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetPayoutsByMinerIdFromTimestamp(minerId uint64, timestamp uint64) (QueryIterator[Payout], error) {
|
||||
if stmt, err := i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 AND timestamp >= $2 ORDER BY main_height DESC;"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if r, err := queryStatement[Payout](i, stmt, minerId, timestamp); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetPayoutsBySideBlock(b *SideBlock) (QueryIterator[Payout], error) {
|
||||
if stmt, err := i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 AND ((side_height >= $2 AND including_height <= $2) OR main_id = $3) ORDER BY main_height DESC;"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if r, err := queryStatement[Payout](i, stmt, b.Miner, b.EffectiveHeight, &b.MainId); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMainCoinbaseOutputs(coinbaseId types.Hash) (QueryIterator[MainCoinbaseOutput], error) {
|
||||
if stmt, err := i.handle.Prepare("SELECT " + MainCoinbaseOutputSelectFields + " FROM main_coinbase_outputs WHERE id = $1 ORDER BY index ASC;"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if r, err := queryStatement[MainCoinbaseOutput](i, stmt, coinbaseId[:]); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMainCoinbaseOutputByIndex(coinbaseId types.Hash, index uint64) (o *MainCoinbaseOutput) {
|
||||
if stmt, err := i.handle.Prepare("SELECT " + MainCoinbaseOutputSelectFields + " FROM main_coinbase_outputs WHERE id = $1 AND index = $2 ORDER BY index ASC;"); err != nil {
|
||||
utils.Error(err)
|
||||
return nil
|
||||
} else {
|
||||
if r, err := queryStatement[MainCoinbaseOutput](i, stmt, coinbaseId[:], index); err != nil {
|
||||
utils.Error(err)
|
||||
return nil
|
||||
} else {
|
||||
defer r.Close()
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
defer r.Close()
|
||||
if _, o = r.Next(); o == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
return o
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMainCoinbaseOutputByGlobalOutputIndex(globalOutputIndex uint64) (o *MainCoinbaseOutput) {
|
||||
if stmt, err := i.handle.Prepare("SELECT " + MainCoinbaseOutputSelectFields + " FROM main_coinbase_outputs WHERE global_output_index = $1 ORDER BY index ASC;"); err != nil {
|
||||
utils.Error(err)
|
||||
return nil
|
||||
} else {
|
||||
if r, err := queryStatement[MainCoinbaseOutput](i, stmt, globalOutputIndex); err != nil {
|
||||
utils.Error(err)
|
||||
return nil
|
||||
} else {
|
||||
defer r.Close()
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
defer r.Close()
|
||||
if _, o = r.Next(); o == nil && r.Err() != nil {
|
||||
utils.Error(r.Err())
|
||||
}
|
||||
return o
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMainLikelySweepTransactions(limit uint64) (r QueryIterator[MainLikelySweepTransaction], err error) {
|
||||
var stmt *sql.Stmt
|
||||
var params []any
|
||||
|
||||
if limit > 0 {
|
||||
if stmt, err = i.handle.Prepare("SELECT " + MainLikelySweepTransactionSelectFields + " FROM main_likely_sweep_transactions ORDER BY timestamp DESC LIMIT $1;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = []any{limit}
|
||||
} else {
|
||||
if stmt, err = i.handle.Prepare("SELECT " + MainLikelySweepTransactionSelectFields + " FROM main_likely_sweep_transactions ORDER BY timestamp DESC;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = []any{}
|
||||
}
|
||||
|
||||
if r, err := queryStatement[MainLikelySweepTransaction](i, stmt, params...); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) GetMainLikelySweepTransactionsByAddress(addr *address.Address, limit uint64) (r QueryIterator[MainLikelySweepTransaction], err error) {
|
||||
var stmt *sql.Stmt
|
||||
var params []any
|
||||
|
||||
spendPub, viewPub := addr.SpendPublicKey().AsSlice(), addr.ViewPublicKey().AsSlice()
|
||||
|
||||
if limit > 0 {
|
||||
if stmt, err = i.handle.Prepare("SELECT " + MainLikelySweepTransactionSelectFields + " FROM main_likely_sweep_transactions WHERE miner_spend_public_key = $1 AND miner_view_public_key = $2 ORDER BY timestamp DESC LIMIT $3;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = []any{&spendPub, &viewPub, limit}
|
||||
} else {
|
||||
if stmt, err = i.handle.Prepare("SELECT " + MainLikelySweepTransactionSelectFields + " FROM main_likely_sweep_transactions WHERE miner_spend_public_key = $1 AND miner_view_public_key = $2 ORDER BY timestamp DESC;"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = []any{&spendPub, &viewPub}
|
||||
}
|
||||
|
||||
if r, err := queryStatement[MainLikelySweepTransaction](i, stmt, params...); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
type TransactionInputQueryResult struct {
|
||||
Input client.TransactionInput `json:"input"`
|
||||
MatchedOutputs []*MatchedOutput `json:"matched_outputs"`
|
||||
}
|
||||
|
||||
type MatchedOutput struct {
|
||||
Coinbase *MainCoinbaseOutput `json:"coinbase,omitempty"`
|
||||
Sweep *MainLikelySweepTransaction `json:"sweep,omitempty"`
|
||||
GlobalOutputIndex uint64 `json:"global_output_index"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
Address *address.Address `json:"address"`
|
||||
}
|
||||
|
||||
type MinimalTransactionInputQueryResult struct {
|
||||
Input client.TransactionInput `json:"input"`
|
||||
MatchedOutputs []*MinimalMatchedOutput `json:"matched_outputs"`
|
||||
}
|
||||
|
||||
type MinimalMatchedOutput struct {
|
||||
Coinbase types.Hash `json:"coinbase,omitempty"`
|
||||
Sweep types.Hash `json:"sweep,omitempty"`
|
||||
GlobalOutputIndex uint64 `json:"global_output_index"`
|
||||
Address *address.Address `json:"address"`
|
||||
}
|
||||
|
||||
type MinimalTransactionInputQueryResults []MinimalTransactionInputQueryResult
|
||||
type TransactionInputQueryResults []TransactionInputQueryResult
|
||||
|
||||
type TransactionInputQueryResultsMatch struct {
|
||||
Address *address.Address `json:"address"`
|
||||
Count uint64 `json:"count"`
|
||||
SweepCount uint64 `json:"sweep_count"`
|
||||
CoinbaseCount uint64 `json:"coinbase_count"`
|
||||
CoinbaseAmount uint64 `json:"coinbase_amount"`
|
||||
}
|
||||
type TransactionInputQueryResultsMatches []TransactionInputQueryResultsMatch
|
||||
|
||||
func (r TransactionInputQueryResults) Match() (result TransactionInputQueryResultsMatches) {
|
||||
//cannot have more than one of same miner outputs valid per input
|
||||
//no miner outputs in whole input doesn't count
|
||||
//cannot take vsame exact miner outputs on different inputs
|
||||
//TODO
|
||||
|
||||
var zeroAddress address.PackedAddress
|
||||
miners := make(map[address.PackedAddress]*TransactionInputQueryResultsMatch)
|
||||
miners[zeroAddress] = &TransactionInputQueryResultsMatch{
|
||||
Address: nil,
|
||||
Count: 0,
|
||||
SweepCount: 0,
|
||||
CoinbaseCount: 0,
|
||||
CoinbaseAmount: 0,
|
||||
}
|
||||
|
||||
//TODO: handle same miner multiple times in same decoy
|
||||
|
||||
for _, matchResult := range r {
|
||||
for _, o := range matchResult.MatchedOutputs {
|
||||
if o != nil {
|
||||
pA := o.Address.ToPackedAddress()
|
||||
if _, ok := miners[pA]; !ok {
|
||||
miners[pA] = &TransactionInputQueryResultsMatch{
|
||||
Address: o.Address,
|
||||
Count: 0,
|
||||
SweepCount: 0,
|
||||
CoinbaseCount: 0,
|
||||
CoinbaseAmount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
if o.Coinbase != nil {
|
||||
miners[pA].CoinbaseCount++
|
||||
miners[pA].CoinbaseAmount += o.Coinbase.Value
|
||||
} else if o.Sweep != nil {
|
||||
miners[pA].SweepCount++
|
||||
}
|
||||
miners[pA].Count++
|
||||
} else {
|
||||
miners[zeroAddress].Count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = make([]TransactionInputQueryResultsMatch, 0, len(miners))
|
||||
|
||||
for _, v := range miners {
|
||||
result = append(result, *v)
|
||||
}
|
||||
|
||||
slices.SortFunc(result, func(a, b TransactionInputQueryResultsMatch) int {
|
||||
return int(b.Count) - int(a.Count)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (i *Index) QueryTransactionInputs(inputs []client.TransactionInput) TransactionInputQueryResults {
|
||||
result := make(TransactionInputQueryResults, len(inputs))
|
||||
for index, input := range inputs {
|
||||
result[index].Input = input
|
||||
result[index].MatchedOutputs = make([]*MatchedOutput, len(input.KeyOffsets))
|
||||
if input.Amount != 0 {
|
||||
continue
|
||||
}
|
||||
result[index].MatchedOutputs = i.QueryGlobalOutputIndices(input.KeyOffsets)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (i *Index) QueryGlobalOutputIndices(indices []uint64) []*MatchedOutput {
|
||||
result := make([]*MatchedOutput, len(indices))
|
||||
|
||||
if err := i.Query("SELECT "+MainCoinbaseOutputSelectFields+" FROM main_coinbase_outputs WHERE global_output_index = ANY($1) ORDER BY index ASC;", func(row RowScanInterface) error {
|
||||
var o MainCoinbaseOutput
|
||||
if err := o.ScanFromRow(i.Consensus(), row); err != nil {
|
||||
return err
|
||||
}
|
||||
if index := slices.Index(indices, o.GlobalOutputIndex); index != -1 {
|
||||
result[index] = &MatchedOutput{
|
||||
Coinbase: &o,
|
||||
Sweep: nil,
|
||||
GlobalOutputIndex: o.GlobalOutputIndex,
|
||||
Address: i.GetMiner(o.Miner).Address(),
|
||||
}
|
||||
if mb := i.GetMainBlockByCoinbaseId(o.Id); mb != nil {
|
||||
result[index].Timestamp = mb.Timestamp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, pq.Array(indices)); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := i.Query("SELECT "+MainLikelySweepTransactionSelectFields+" FROM main_likely_sweep_transactions WHERE $1::bigint[] && global_output_indices ORDER BY timestamp ASC;", func(row RowScanInterface) error {
|
||||
var tx MainLikelySweepTransaction
|
||||
if err := tx.ScanFromRow(i.Consensus(), row); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, globalOutputIndex := range tx.GlobalOutputIndices {
|
||||
// fill all possible indices
|
||||
if index := slices.Index(indices, globalOutputIndex); index != -1 {
|
||||
if result[index] == nil {
|
||||
result[index] = &MatchedOutput{
|
||||
Coinbase: nil,
|
||||
Sweep: &tx,
|
||||
GlobalOutputIndex: globalOutputIndex,
|
||||
Timestamp: tx.Timestamp,
|
||||
Address: tx.Address,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, pq.Array(indices)); err != nil {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (i *Index) GetMainLikelySweepTransactionBySpendingGlobalOutputIndices(globalOutputIndices ...uint64) [][]*MainLikelySweepTransaction {
|
||||
entries := make([][]*MainLikelySweepTransaction, len(globalOutputIndices))
|
||||
if err := i.Query("SELECT "+MainLikelySweepTransactionSelectFields+" FROM main_likely_sweep_transactions WHERE $1::bigint[] && spending_output_indices ORDER BY timestamp ASC;", func(row RowScanInterface) error {
|
||||
var tx MainLikelySweepTransaction
|
||||
if err := tx.ScanFromRow(i.Consensus(), row); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, globalOutputIndex := range tx.SpendingOutputIndices {
|
||||
// fill all possible indices
|
||||
if index := slices.Index(globalOutputIndices, globalOutputIndex); index != -1 {
|
||||
entries[index] = append(entries[index], &tx)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, pq.Array(globalOutputIndices)); err != nil {
|
||||
return nil
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func (i *Index) GetMainLikelySweepTransactionByGlobalOutputIndices(globalOutputIndices ...uint64) []*MainLikelySweepTransaction {
|
||||
entries := make([]*MainLikelySweepTransaction, len(globalOutputIndices))
|
||||
if err := i.Query("SELECT "+MainLikelySweepTransactionSelectFields+" FROM main_likely_sweep_transactions WHERE $1::bigint[] && global_output_indices ORDER BY timestamp ASC;", func(row RowScanInterface) error {
|
||||
var tx MainLikelySweepTransaction
|
||||
if err := tx.ScanFromRow(i.Consensus(), row); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, globalOutputIndex := range tx.GlobalOutputIndices {
|
||||
// fill all possible indices
|
||||
if index := slices.Index(globalOutputIndices, globalOutputIndex); index != -1 {
|
||||
if entries[index] == nil {
|
||||
entries[index] = &tx
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, pq.Array(globalOutputIndices)); err != nil {
|
||||
return nil
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func (i *Index) InsertOrUpdateMainLikelySweepTransaction(t *MainLikelySweepTransaction) error {
|
||||
|
||||
resultJson, _ := utils.MarshalJSON(t.Result)
|
||||
matchJson, _ := utils.MarshalJSON(t.Match)
|
||||
spendPub, viewPub := t.Address.SpendPublicKey().AsSlice(), t.Address.ViewPublicKey().AsSlice()
|
||||
|
||||
if _, err := i.handle.Exec(
|
||||
"INSERT INTO main_likely_sweep_transactions (id, timestamp, result, match, value, spending_output_indices, global_output_indices, input_count, input_decoy_count, miner_count, other_miners_count, no_miner_count, miner_ratio, other_miners_ratio, no_miner_ratio, miner_spend_public_key, miner_view_public_key) VALUES ($1, $2, $3, $4, $5, $6::bigint[], $7::bigint[], $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) ON CONFLICT (id) DO UPDATE SET result = $3, match = $4, value = $5, miner_count = $10, other_miners_count = $11, no_miner_count = $12, miner_ratio = $13, other_miners_ratio = $14, no_miner_ratio = $15, miner_spend_public_key = $16, miner_view_public_key = $17;",
|
||||
t.Id[:],
|
||||
t.Timestamp,
|
||||
resultJson,
|
||||
matchJson,
|
||||
t.Value,
|
||||
pq.Array(t.SpendingOutputIndices),
|
||||
pq.Array(t.GlobalOutputIndices),
|
||||
t.InputCount,
|
||||
t.InputDecoyCount,
|
||||
t.MinerCount,
|
||||
t.OtherMinersCount,
|
||||
t.NoMinerCount,
|
||||
t.MinerRatio,
|
||||
t.OtherMinersRatio,
|
||||
t.NoMinerRatio,
|
||||
&spendPub,
|
||||
&viewPub,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetMainCoinbaseOutputByMinerId(coinbaseId types.Hash, minerId uint64) *MainCoinbaseOutput {
|
||||
var output MainCoinbaseOutput
|
||||
if err := i.Query("SELECT "+MainCoinbaseOutputSelectFields+" FROM main_coinbase_outputs WHERE id = $1 AND miner = $2 ORDER BY index DESC;", func(row RowScanInterface) error {
|
||||
if err := output.ScanFromRow(i.Consensus(), row); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, coinbaseId[:], minerId); err != nil {
|
||||
return nil
|
||||
}
|
||||
if output.Id == types.ZeroHash {
|
||||
return nil
|
||||
}
|
||||
return &output
|
||||
}
|
||||
|
||||
func (i *Index) InsertOrUpdateMainCoinbaseOutputs(outputs MainCoinbaseOutputs) error {
|
||||
if len(outputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
for ix, o := range outputs[1:] {
|
||||
if outputs[ix].Id != o.Id {
|
||||
return errors.New("differing coinbase ids")
|
||||
}
|
||||
}
|
||||
|
||||
if tx, err := i.handle.BeginTx(context.Background(), nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer tx.Rollback()
|
||||
for _, o := range outputs {
|
||||
if _, err := tx.Exec(
|
||||
"INSERT INTO main_coinbase_outputs (id, index, global_output_index, miner, value) VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING;",
|
||||
o.Id[:],
|
||||
o.Index,
|
||||
o.GlobalOutputIndex,
|
||||
o.Miner,
|
||||
o.Value,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) Close() error {
|
||||
//cleanup statements
|
||||
v := reflect.ValueOf(i.statements)
|
||||
for ix := 0; ix < v.NumField(); ix++ {
|
||||
if stmt, ok := v.Field(ix).Interface().(*sql.Stmt); ok && stmt != nil {
|
||||
//v.Field(i).Elem().Set(reflect.ValueOf((*sql.Stmt)(nil)))
|
||||
stmt.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return i.handle.Close()
|
||||
}
|
||||
|
||||
func (i *Index) scanMiner(rows *sql.Rows) *Miner {
|
||||
if rows.Next() {
|
||||
m := &Miner{}
|
||||
if m.ScanFromRow(i.Consensus(), rows) == nil {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetSideBlockFromPoolBlock(b *sidechain.PoolBlock, inclusion BlockInclusion) (tip *SideBlock, uncles []*SideBlock, err error) {
|
||||
if err = i.preProcessPoolBlock(b); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tip = &SideBlock{}
|
||||
if err = tip.FromPoolBlock(i, b, i.GetSeedByHeight); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tip.EffectiveHeight = tip.SideHeight
|
||||
tip.Inclusion = inclusion
|
||||
|
||||
if bottomHeight, err := sidechain.BlocksInPPLNSWindow(b, i.consensus, i.GetDifficultyByHeight, i.GetByTemplateId, func(b *sidechain.PoolBlock, weight types.Difficulty) {
|
||||
|
||||
}); err != nil {
|
||||
// unknown
|
||||
tip.WindowDepth = 0
|
||||
} else {
|
||||
tip.WindowDepth = uint32(tip.SideHeight - bottomHeight + 1)
|
||||
}
|
||||
|
||||
for _, u := range b.Side.Uncles {
|
||||
uncleBlock := i.GetTipSideBlockByTemplateId(u)
|
||||
uncle := i.GetByTemplateId(u)
|
||||
if err = i.preProcessPoolBlock(uncle); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if uncleBlock == nil && uncle != nil {
|
||||
uncleBlock = &SideBlock{}
|
||||
if err = uncleBlock.FromPoolBlock(i, uncle, i.GetSeedByHeight); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if tip.Inclusion == InclusionOrphan {
|
||||
uncleBlock.Inclusion = InclusionOrphan
|
||||
} else if tip.Inclusion == InclusionInVerifiedChain || tip.Inclusion == InclusionAlternateInVerifiedChain {
|
||||
uncleBlock.Inclusion = InclusionInVerifiedChain
|
||||
}
|
||||
}
|
||||
if uncleBlock == nil || uncle == nil {
|
||||
return nil, nil, errors.New("nil uncle")
|
||||
}
|
||||
if tip.Inclusion == InclusionInVerifiedChain || tip.Inclusion == InclusionAlternateInVerifiedChain {
|
||||
uncleBlock.UncleOf = tip.TemplateId
|
||||
uncleBlock.EffectiveHeight = tip.EffectiveHeight
|
||||
uncleBlock.Inclusion = InclusionInVerifiedChain
|
||||
}
|
||||
|
||||
if uncleBottomHeight, err := sidechain.BlocksInPPLNSWindow(uncle, i.consensus, i.GetDifficultyByHeight, i.GetByTemplateId, func(b *sidechain.PoolBlock, weight types.Difficulty) {
|
||||
|
||||
}); err != nil {
|
||||
// unknown
|
||||
uncleBlock.WindowDepth = 0
|
||||
} else {
|
||||
uncleBlock.WindowDepth = uint32(uncleBlock.SideHeight - uncleBottomHeight + 1)
|
||||
}
|
||||
uncles = append(uncles, uncleBlock)
|
||||
}
|
||||
|
||||
return tip, uncles, nil
|
||||
}
|
||||
|
||||
func (i *Index) preProcessPoolBlock(b *sidechain.PoolBlock) error {
|
||||
if b == nil {
|
||||
return errors.New("nil block")
|
||||
}
|
||||
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 := i.GetByTemplateId(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(i.consensus.ChainWindowSize * 2)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := b.PreProcessBlock(i.consensus, i.derivationCache, preAllocatedShares, i.GetDifficultyByHeight, i.GetByTemplateId)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *Index) DerivationCache() sidechain.DerivationCacheInterface {
|
||||
return i.derivationCache
|
||||
}
|
||||
|
||||
func (i *Index) InsertOrUpdatePoolBlock(b *sidechain.PoolBlock, inclusion BlockInclusion) error {
|
||||
sideBlock, sideUncles, err := i.GetSideBlockFromPoolBlock(b, inclusion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := i.InsertOrUpdateSideBlock(sideBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, sideUncle := range sideUncles {
|
||||
if err := i.InsertOrUpdateSideBlock(sideUncle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Index) GetMinerWebHooks(minerId uint64) (QueryIterator[MinerWebHook], error) {
|
||||
if stmt, err := i.handle.Prepare("SELECT miner, type, url, settings FROM miner_webhooks WHERE miner = $1;"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if r, err := queryStatement[MinerWebHook](i, stmt, minerId); err != nil {
|
||||
defer stmt.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
r.closer = func() {
|
||||
stmt.Close()
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) DeleteMinerWebHook(minerId uint64, hookType WebHookType) error {
|
||||
return i.Query("DELETE FROM miner_webhooks WHERE miner = $1 AND type = $2", func(row RowScanInterface) error {
|
||||
return nil
|
||||
}, minerId, hookType)
|
||||
}
|
||||
|
||||
func (i *Index) InsertOrUpdateMinerWebHook(w *MinerWebHook) error {
|
||||
metadataJson, _ := utils.MarshalJSON(w.Settings)
|
||||
|
||||
if w.Settings == nil {
|
||||
metadataJson = []byte{'{', '}'}
|
||||
}
|
||||
|
||||
if tx, err := i.handle.BeginTx(context.Background(), nil); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.Exec(
|
||||
"INSERT INTO miner_webhooks (miner, type, url, settings) VALUES ($1, $2, $3, $4::jsonb) ON CONFLICT (miner, type) DO UPDATE SET url = $3, settings = $4;",
|
||||
w.Miner,
|
||||
w.Type,
|
||||
w.Url,
|
||||
metadataJson,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package index
|
||||
|
||||
type IterateFunction[K, V any] func(key K, value V) (stop bool)
|
||||
|
||||
type IteratorFunction[K, V any] func(f IterateFunction[K, V]) (complete bool)
|
||||
|
||||
type Iterator[K, V any] interface {
|
||||
All(f IterateFunction[K, V]) (complete bool)
|
||||
}
|
||||
|
||||
func IteratorToIterator[K, V any](i Iterator[K, V], _ ...error) Iterator[K, V] {
|
||||
return i
|
||||
}
|
||||
|
||||
func IterateToSlice[T any](i Iterator[int, T], _ ...error) (s []T) {
|
||||
i.All(func(key int, value T) (stop bool) {
|
||||
s = append(s, value)
|
||||
return false
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
func IterateToSliceWithoutPointer[T any](i Iterator[int, *T], _ ...error) (s []T) {
|
||||
i.All(func(key int, value *T) (stop bool) {
|
||||
s = append(s, *value)
|
||||
return false
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
func SliceIterate[S ~[]T, T any](s S, f IterateFunction[int, T]) (complete bool) {
|
||||
for i, v := range s {
|
||||
if f(i, v) {
|
||||
return (len(s) - 1) == i
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func ChanIterate[T any](c <-chan T, f IterateFunction[int, T]) (complete bool) {
|
||||
var i int
|
||||
for {
|
||||
v, more := <-c
|
||||
if !more {
|
||||
return true
|
||||
}
|
||||
if f(i, v) {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func ChanConsume[T any](c <-chan T) {
|
||||
for range c {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func ChanToSlice[S ~[]T, T any](c <-chan T) (s S) {
|
||||
s = make(S, 0, len(c))
|
||||
for v := range c {
|
||||
s = append(s, v)
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
)
|
||||
|
||||
const MainBlockSelectFields = "id, height, timestamp, reward, coinbase_id, difficulty, metadata, side_template_id, coinbase_private_key"
|
||||
|
||||
type MainBlock struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Height uint64 `json:"height"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
Reward uint64 `json:"reward"`
|
||||
CoinbaseId types.Hash `json:"coinbase_id"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
|
||||
// Metadata should be jsonb blob, can be NULL. metadata such as pool ownership, links to other p2pool networks, and other interesting data
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
|
||||
// sidechain data for blocks we own
|
||||
// SideTemplateId can be NULL
|
||||
SideTemplateId types.Hash `json:"side_template_id,omitempty"`
|
||||
// CoinbasePrivateKey private key for coinbase outputs we own (all 0x00 = not known, but should have one)
|
||||
CoinbasePrivateKey crypto.PrivateKeyBytes `json:"coinbase_private_key,omitempty"`
|
||||
}
|
||||
|
||||
func (b *MainBlock) GetMetadata(key string) any {
|
||||
return b.Metadata[key]
|
||||
}
|
||||
|
||||
func (b *MainBlock) SetMetadata(key string, v any) {
|
||||
b.Metadata[key] = v
|
||||
}
|
||||
|
||||
func (b *MainBlock) ScanFromRow(_ *sidechain.Consensus, row RowScanInterface) error {
|
||||
var metadataBuf []byte
|
||||
b.Metadata = make(map[string]any)
|
||||
if err := row.Scan(&b.Id, &b.Height, &b.Timestamp, &b.Reward, &b.CoinbaseId, &b.Difficulty, &metadataBuf, &b.SideTemplateId, &b.CoinbasePrivateKey); err != nil {
|
||||
return err
|
||||
} else if err = utils.UnmarshalJSON(metadataBuf, &b.Metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
)
|
||||
|
||||
type MainCoinbaseOutputs []MainCoinbaseOutput
|
||||
|
||||
const MainCoinbaseOutputSelectFields = "id, index, global_output_index, miner, value"
|
||||
|
||||
type MainCoinbaseOutput struct {
|
||||
// Id coinbase id
|
||||
Id types.Hash `json:"id"`
|
||||
// Index transaction output index
|
||||
Index uint32 `json:"index"`
|
||||
// Monero global output idx
|
||||
GlobalOutputIndex uint64 `json:"global_output_index"`
|
||||
|
||||
// Miner owner of the output
|
||||
Miner uint64 `json:"miner"`
|
||||
|
||||
Value uint64 `json:"value"`
|
||||
|
||||
// Extra information filled just for JSON purposes
|
||||
|
||||
MinerAddress *address.Address `json:"miner_address,omitempty"`
|
||||
MinerAlias string `json:"miner_alias,omitempty"`
|
||||
}
|
||||
|
||||
func (o *MainCoinbaseOutput) ScanFromRow(_ *sidechain.Consensus, row RowScanInterface) error {
|
||||
if err := row.Scan(&o.Id, &o.Index, &o.GlobalOutputIndex, &o.Miner, &o.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
const MainLikelySweepTransactionSelectFields = "id, timestamp, result, match, value, spending_output_indices, global_output_indices, input_count, input_decoy_count, miner_count, other_miners_count, no_miner_count, miner_ratio, other_miners_ratio, no_miner_ratio, miner_spend_public_key, miner_view_public_key"
|
||||
|
||||
type MainLikelySweepTransaction struct {
|
||||
// Id coinbase id
|
||||
Id types.Hash `json:"id"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
Result MinimalTransactionInputQueryResults `json:"result"`
|
||||
Match []TransactionInputQueryResultsMatch `json:"match"`
|
||||
Value uint64 `json:"value"`
|
||||
SpendingOutputIndices []uint64 `json:"spending_output_indices"`
|
||||
GlobalOutputIndices []uint64 `json:"global_output_indices"`
|
||||
|
||||
InputCount int `json:"input_count"`
|
||||
InputDecoyCount int `json:"input_decoy_count"`
|
||||
|
||||
MinerCount int `json:"miner_count"`
|
||||
OtherMinersCount int `json:"other_miners_count"`
|
||||
NoMinerCount int `json:"no_miner_count"`
|
||||
|
||||
MinerRatio float32 `json:"miner_ratio"`
|
||||
OtherMinersRatio float32 `json:"other_miners_ratio"`
|
||||
NoMinerRatio float32 `json:"no_miner_ratio"`
|
||||
|
||||
Address *address.Address `json:"address"`
|
||||
}
|
||||
|
||||
func (t *MainLikelySweepTransaction) ScanFromRow(consensus *sidechain.Consensus, row RowScanInterface) error {
|
||||
var spendPub, viewPub crypto.PublicKeyBytes
|
||||
var resultBuf, matchBuf []byte
|
||||
var spendingOutputIndices, globalOutputIndices pq.Int64Array
|
||||
if err := row.Scan(&t.Id, &t.Timestamp, &resultBuf, &matchBuf, &t.Value, &spendingOutputIndices, &globalOutputIndices, &t.InputCount, &t.InputDecoyCount, &t.MinerCount, &t.OtherMinersCount, &t.NoMinerCount, &t.MinerRatio, &t.OtherMinersRatio, &t.NoMinerRatio, &spendPub, &viewPub); err != nil {
|
||||
return err
|
||||
} else if err = utils.UnmarshalJSON(resultBuf, &t.Result); err != nil {
|
||||
return err
|
||||
} else if err = utils.UnmarshalJSON(resultBuf, &t.Match); err != nil {
|
||||
return err
|
||||
}
|
||||
t.SpendingOutputIndices = make([]uint64, len(spendingOutputIndices))
|
||||
for j, ix := range spendingOutputIndices {
|
||||
t.SpendingOutputIndices[j] = uint64(ix)
|
||||
}
|
||||
t.GlobalOutputIndices = make([]uint64, len(globalOutputIndices))
|
||||
for j, ix := range globalOutputIndices {
|
||||
t.GlobalOutputIndices[j] = uint64(ix)
|
||||
}
|
||||
|
||||
network, err := consensus.NetworkType.AddressNetwork()
|
||||
if err != nil {
|
||||
return errors.New("unknown network type")
|
||||
}
|
||||
|
||||
t.Address = address.FromRawAddress(network, &spendPub, &viewPub)
|
||||
return nil
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
)
|
||||
|
||||
const MinerSelectFields = "id, alias, spend_public_key, view_public_key"
|
||||
|
||||
type Miner struct {
|
||||
id uint64
|
||||
addr address.Address
|
||||
alias sql.NullString
|
||||
}
|
||||
|
||||
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() *address.Address {
|
||||
return &m.addr
|
||||
}
|
||||
|
||||
func (m *Miner) ScanFromRow(consensus *sidechain.Consensus, row RowScanInterface) error {
|
||||
var spendPub, viewPub crypto.PublicKeyBytes
|
||||
if err := row.Scan(&m.id, &m.alias, &spendPub, &viewPub); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
network, err := consensus.NetworkType.AddressNetwork()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.addr = *address.FromRawAddress(network, &spendPub, &viewPub)
|
||||
return nil
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MinerWebHook struct {
|
||||
Miner uint64 `json:"miner"`
|
||||
Type WebHookType `json:"type"`
|
||||
Url string `json:"url"`
|
||||
Settings map[string]string `json:"settings"`
|
||||
Consensus *sidechain.Consensus `json:"-"`
|
||||
}
|
||||
|
||||
type WebHookType string
|
||||
|
||||
const (
|
||||
WebHookSlack WebHookType = "slack"
|
||||
WebHookDiscord WebHookType = "discord"
|
||||
WebHookTelegram WebHookType = "telegram"
|
||||
WebHookMatrixHookshot WebHookType = "matrix-hookshot"
|
||||
WebHookCustom WebHookType = "custom"
|
||||
)
|
||||
|
||||
var disallowedCustomHookHosts = []string{
|
||||
"hooks.slack.com",
|
||||
"discord.com",
|
||||
"api.telegram.org",
|
||||
}
|
||||
|
||||
var disallowedCustomHookPorts = []string{}
|
||||
|
||||
func (w *MinerWebHook) ScanFromRow(consensus *sidechain.Consensus, row RowScanInterface) error {
|
||||
var settingsBuf []byte
|
||||
|
||||
w.Consensus = consensus
|
||||
w.Settings = make(map[string]string)
|
||||
|
||||
if err := row.Scan(&w.Miner, &w.Type, &w.Url, &settingsBuf); err != nil {
|
||||
return err
|
||||
} else if err = utils.UnmarshalJSON(settingsBuf, &w.Settings); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *MinerWebHook) Verify() error {
|
||||
uri, err := url.Parse(w.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch w.Type {
|
||||
case WebHookSlack:
|
||||
if uri.Scheme != "https" {
|
||||
return errors.New("invalid URL scheme, expected https")
|
||||
}
|
||||
if uri.Host != "hooks.slack.com" {
|
||||
return errors.New("invalid hook host, expected hooks.slack.com")
|
||||
}
|
||||
if uri.Port() != "" {
|
||||
return errors.New("unexpected port")
|
||||
}
|
||||
if !strings.HasPrefix(uri.Path, "/services/") {
|
||||
return errors.New("invalid hook path start")
|
||||
}
|
||||
case WebHookDiscord:
|
||||
if uri.Scheme != "https" {
|
||||
return errors.New("invalid URL scheme, expected https")
|
||||
}
|
||||
if uri.Host != "discord.com" {
|
||||
return errors.New("invalid hook host, expected discord.com")
|
||||
}
|
||||
if uri.Port() != "" {
|
||||
return errors.New("unexpected port")
|
||||
}
|
||||
if !strings.HasPrefix(uri.Path, "/api/webhooks/") {
|
||||
return errors.New("invalid hook path start")
|
||||
}
|
||||
case WebHookTelegram:
|
||||
if uri.Scheme != "https" {
|
||||
return errors.New("invalid URL scheme, expected https")
|
||||
}
|
||||
if uri.Host != "api.telegram.org" {
|
||||
return errors.New("invalid hook host, expected api.telegram.org")
|
||||
}
|
||||
if uri.Port() != "" {
|
||||
return errors.New("unexpected port")
|
||||
}
|
||||
if !strings.HasPrefix(uri.Path, "/bot") {
|
||||
return errors.New("invalid hook path start")
|
||||
}
|
||||
if !strings.HasSuffix(uri.Path, "/sendMessage") {
|
||||
return errors.New("invalid hook path end")
|
||||
}
|
||||
case WebHookMatrixHookshot:
|
||||
if uri.Scheme != "https" {
|
||||
return errors.New("invalid URL scheme, expected https")
|
||||
}
|
||||
if slices.Contains(disallowedCustomHookHosts, strings.ToLower(uri.Hostname())) {
|
||||
return errors.New("disallowed hook host")
|
||||
}
|
||||
if uri.Port() != "" {
|
||||
return errors.New("unexpected port")
|
||||
}
|
||||
case WebHookCustom:
|
||||
if uri.Scheme != "https" && uri.Scheme != "http" {
|
||||
return errors.New("invalid URL scheme, expected http or https")
|
||||
}
|
||||
if slices.Contains(disallowedCustomHookHosts, strings.ToLower(uri.Hostname())) {
|
||||
return errors.New("disallowed hook host")
|
||||
}
|
||||
if slices.Contains(disallowedCustomHookPorts, strings.ToLower(uri.Port())) {
|
||||
return errors.New("disallowed hook port")
|
||||
}
|
||||
default:
|
||||
return errors.New("unsupported hook type")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
)
|
||||
|
||||
type Payout struct {
|
||||
Miner uint64 `json:"miner"`
|
||||
TemplateId types.Hash `json:"template_id"`
|
||||
SideHeight uint64 `json:"side_height"`
|
||||
UncleOf types.Hash `json:"uncle_of,omitempty"`
|
||||
MainId types.Hash `json:"main_id"`
|
||||
MainHeight uint64 `json:"main_height"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
CoinbaseId types.Hash `json:"coinbase_id"`
|
||||
Reward uint64 `json:"coinbase_reward"`
|
||||
PrivateKey crypto.PrivateKeyBytes `json:"coinbase_private_key"`
|
||||
Index uint64 `json:"coinbase_output_index"`
|
||||
GlobalOutputIndex uint64 `json:"global_output_index"`
|
||||
IncludingHeight uint64 `json:"including_height"`
|
||||
}
|
||||
|
||||
func (p *Payout) ScanFromRow(_ *sidechain.Consensus, row RowScanInterface) error {
|
||||
if err := row.Scan(&p.Miner, &p.MainId, &p.MainHeight, &p.Timestamp, &p.CoinbaseId, &p.PrivateKey, &p.TemplateId, &p.SideHeight, &p.UncleOf, &p.Reward, &p.Index, &p.GlobalOutputIndex, &p.IncludingHeight); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
)
|
||||
|
||||
type RowScanInterface interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
type Scannable interface {
|
||||
ScanFromRow(consensus *sidechain.Consensus, row RowScanInterface) error
|
||||
}
|
||||
|
||||
type QueryIterator[V any] interface {
|
||||
All(f IterateFunction[int, *V]) (complete bool)
|
||||
Next() (int, *V)
|
||||
Close()
|
||||
Err() error
|
||||
}
|
||||
|
||||
func QueryIterate[V any](i QueryIterator[V], f IterateFunction[int, *V]) {
|
||||
if i == nil {
|
||||
return
|
||||
}
|
||||
defer i.Close()
|
||||
i.All(f)
|
||||
|
||||
if i.Err() != nil {
|
||||
panic(i.Err())
|
||||
}
|
||||
}
|
||||
|
||||
func QueryIterateToSlice[T any](i QueryIterator[T], err ...error) (s []*T) {
|
||||
if len(err) > 0 {
|
||||
if err[0] != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
QueryIterate(i, func(key int, value *T) (stop bool) {
|
||||
s = append(s, value)
|
||||
return false
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
func QueryFirstResult[T any](i QueryIterator[T], err ...error) (v *T) {
|
||||
if len(err) > 0 {
|
||||
if err[0] != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
QueryIterate(i, func(_ int, value *T) (stop bool) {
|
||||
v = value
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func QueryHasResults[T any](i QueryIterator[T], err ...error) bool {
|
||||
if len(err) > 0 {
|
||||
if err[0] != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var hasValue bool
|
||||
QueryIterate(i, func(key int, value *T) (stop bool) {
|
||||
if value != nil {
|
||||
hasValue = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
return hasValue
|
||||
}
|
||||
|
||||
// queryStatement Queries a provided sql.Stmt and returns a QueryIterator
|
||||
// After results are read QueryIterator must be closed/freed
|
||||
func queryStatement[V any](index *Index, stmt *sql.Stmt, params ...any) (*QueryResult[V], error) {
|
||||
var testV *V
|
||||
if _, ok := any(testV).(Scannable); !ok {
|
||||
return nil, errors.New("unsupported type")
|
||||
}
|
||||
|
||||
if rows, err := stmt.Query(params...); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &QueryResult[V]{
|
||||
consensus: index.consensus,
|
||||
rows: rows,
|
||||
}, err
|
||||
}
|
||||
}
|
||||
|
||||
type FakeQueryResult[V any] struct {
|
||||
NextFunction func() (int, *V)
|
||||
}
|
||||
|
||||
func (r *FakeQueryResult[V]) All(f IterateFunction[int, *V]) (complete bool) {
|
||||
for {
|
||||
if i, v := r.Next(); v == nil {
|
||||
return true
|
||||
} else {
|
||||
if f(i, v) {
|
||||
// do not allow resuming
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FakeQueryResult[V]) Next() (int, *V) {
|
||||
return r.NextFunction()
|
||||
}
|
||||
|
||||
func (r *FakeQueryResult[V]) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FakeQueryResult[V]) Close() {
|
||||
|
||||
}
|
||||
|
||||
type QueryResult[V any] struct {
|
||||
consensus *sidechain.Consensus
|
||||
rows *sql.Rows
|
||||
closer func()
|
||||
i int
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *QueryResult[V]) All(f IterateFunction[int, *V]) (complete bool) {
|
||||
for {
|
||||
if i, v := r.Next(); v == nil {
|
||||
return true
|
||||
} else {
|
||||
if f(i, v) {
|
||||
// do not allow resuming
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *QueryResult[V]) Next() (int, *V) {
|
||||
if r.rows.Next() {
|
||||
var v V
|
||||
if r.err = any(&v).(Scannable).ScanFromRow(r.consensus, r.rows); r.err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
r.i++
|
||||
return r.i - 1, &v
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (r *QueryResult[V]) Err() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *QueryResult[V]) Close() {
|
||||
if r.closer != nil {
|
||||
r.closer()
|
||||
r.closer = nil
|
||||
}
|
||||
if r.rows != nil {
|
||||
r.rows.Close()
|
||||
r.rows = nil
|
||||
}
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS miners (
|
||||
id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
alias varchar UNIQUE DEFAULT NULL,
|
||||
spend_public_key bytea NOT NULL,
|
||||
view_public_key bytea NOT NULL,
|
||||
UNIQUE (spend_public_key, view_public_key)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS miner_webhooks (
|
||||
miner bigint NOT NULL,
|
||||
type varchar NOT NULL,
|
||||
url varchar NOT NULL,
|
||||
settings jsonb NOT NULL DEFAULT '{}', -- settings to know when to trigger or other required settings for the method
|
||||
UNIQUE (miner, type),
|
||||
FOREIGN KEY (miner) REFERENCES miners (id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS miner_webhooks_miner_idx ON miner_webhooks (miner);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS side_blocks (
|
||||
main_id bytea PRIMARY KEY, -- mainchain id, on Monero network
|
||||
main_height bigint NOT NULL, -- mainchain height
|
||||
|
||||
template_id bytea NOT NULL, -- sidechain template id. Note multiple blocks can exist per template id, see inclusion
|
||||
side_height bigint NOT NULL, -- sidechain height
|
||||
parent_template_id bytea NOT NULL, -- previous sidechain template id
|
||||
|
||||
miner bigint NOT NULL, -- miner who contributed the block
|
||||
|
||||
-- uncle inclusion information
|
||||
uncle_of bytea DEFAULT NULL, -- has been included under this parent block template id as an uncle. Can change after insert
|
||||
effective_height bigint NOT NULL, -- has been included under this parent block height as an uncle, or is this height. Can change after insert
|
||||
|
||||
-- nonce data
|
||||
nonce bigint NOT NULL, -- nonce on block header. requires bigint for unsigned int32
|
||||
extra_nonce bigint NOT NULL, -- nonce on coinbase transaction extra data. requires bigint for unsigned int32
|
||||
|
||||
-- other not indexed data
|
||||
timestamp bigint NOT NULL, -- mainchain timestamp
|
||||
software_id bigint NOT NULL, -- Software used to generate this template. requires bigint for unsigned int32
|
||||
software_version bigint NOT NULL, -- Software version used to generate this template. requires bigint for unsigned int32
|
||||
window_depth int NOT NULL, -- PPLNS window depth, in blocks including this one
|
||||
window_outputs int NOT NULL, -- number of outputs on coinbase transaction
|
||||
transaction_count int NOT NULL, -- number of transactions included in the template
|
||||
|
||||
difficulty bigint NOT NULL, -- sidechain difficulty at height
|
||||
cumulative_difficulty bytea NOT NULL, -- sidechain cumulative difficulty at height, binary
|
||||
pow_difficulty bigint NOT NULL, -- difficulty of pow_hash
|
||||
pow_hash bytea NOT NULL, -- result of PoW function as a hash (all 0x00 = not known)
|
||||
|
||||
inclusion int NOT NULL DEFAULT 1, -- how the block is included. Can change after insert:
|
||||
-- 0 = orphan (was not included in-verified-chain)
|
||||
-- 1 = in-verified-chain (uncle or main)
|
||||
-- 2 = alternate in-verified-chain (uncle or main), for example when duplicate nonce happens
|
||||
-- Higher values might specify forks or other custom additions
|
||||
|
||||
UNIQUE (template_id, nonce, extra_nonce), -- main id can only change when nonce / extra nonce is adjusted, as template_id hash does not include them
|
||||
FOREIGN KEY (miner) REFERENCES miners (id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS side_blocks_miner_idx ON side_blocks (miner);
|
||||
CREATE INDEX IF NOT EXISTS side_blocks_template_id_idx ON side_blocks (template_id);
|
||||
CREATE INDEX IF NOT EXISTS side_blocks_main_height_idx ON side_blocks (main_height);
|
||||
CREATE INDEX IF NOT EXISTS side_blocks_side_height_idx ON side_blocks (side_height);
|
||||
CREATE INDEX IF NOT EXISTS side_blocks_parent_template_id_idx ON side_blocks (parent_template_id);
|
||||
CREATE INDEX IF NOT EXISTS side_blocks_uncle_of_idx ON side_blocks (uncle_of);
|
||||
CREATE INDEX IF NOT EXISTS side_blocks_effective_height_idx ON side_blocks (effective_height);
|
||||
|
||||
-- CLUSTER VERBOSE side_blocks USING side_blocks_effective_height_idx;
|
||||
|
||||
-- Cannot have non-unique constraints
|
||||
-- ALTER TABLE side_blocks ADD CONSTRAINT fk_side_blocks_uncle_of FOREIGN KEY (uncle_of) REFERENCES side_blocks (template_id);
|
||||
-- ALTER TABLE side_blocks ADD CONSTRAINT fk_side_blocks_effective_height FOREIGN KEY (effective_height) REFERENCES side_blocks (side_height);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS main_blocks (
|
||||
id bytea PRIMARY KEY,
|
||||
height bigint UNIQUE NOT NULL,
|
||||
timestamp bigint NOT NULL, -- timestamp as set in block
|
||||
reward bigint NOT NULL,
|
||||
coinbase_id bytea UNIQUE NOT NULL,
|
||||
difficulty bigint NOT NULL, -- mainchain difficulty at height
|
||||
metadata jsonb NOT NULL DEFAULT '{}', -- metadata such as pool ownership, links to other p2pool networks, and other interesting data
|
||||
-- sidechain data for blocks we own
|
||||
side_template_id bytea UNIQUE DEFAULT NULL,
|
||||
coinbase_private_key bytea DEFAULT NULL -- private key for coinbase outputs (all 0x00 = not known, but should have one)
|
||||
|
||||
-- Cannot have non-unique constraints
|
||||
-- FOREIGN KEY (side_template_id) REFERENCES side_blocks (template_id)
|
||||
);
|
||||
|
||||
-- CLUSTER VERBOSE main_blocks USING main_blocks_height_key;
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS main_coinbase_outputs (
|
||||
id bytea NOT NULL, -- coinbase id
|
||||
index int NOT NULL, -- transaction output index
|
||||
global_output_index bigint UNIQUE NOT NULL, -- Monero global output idx
|
||||
miner bigint NOT NULL, -- owner of the output
|
||||
value bigint NOT NULL,
|
||||
PRIMARY KEY (id, index),
|
||||
FOREIGN KEY (id) REFERENCES main_blocks (coinbase_id),
|
||||
FOREIGN KEY (miner) REFERENCES miners (id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS main_coinbase_outputs_id_idx ON main_coinbase_outputs (id);
|
||||
CREATE INDEX IF NOT EXISTS main_coinbase_outputs_miner_idx ON main_coinbase_outputs (miner);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS main_likely_sweep_transactions (
|
||||
id bytea PRIMARY KEY NOT NULL, -- transaction id
|
||||
timestamp bigint NOT NULL, -- when the transaction was made / included in block
|
||||
result jsonb NOT NULL, -- MinimalTransactionInputQueryResults
|
||||
match jsonb NOT NULL, -- TransactionInputQueryResultsMatch
|
||||
value bigint NOT NULL,
|
||||
spending_output_indices bigint[] NOT NULL, -- global output indices consumed by this transaction (including decoys)
|
||||
global_output_indices bigint[] NOT NULL, -- global output indices produced by this transaction
|
||||
|
||||
input_count integer NOT NULL, -- count of inputs
|
||||
input_decoy_count integer NOT NULL, -- count of decoys per input
|
||||
|
||||
miner_count integer NOT NULL,
|
||||
other_miners_count integer NOT NULL,
|
||||
no_miner_count integer NOT NULL,
|
||||
|
||||
miner_ratio real NOT NULL,
|
||||
other_miners_ratio real NOT NULL,
|
||||
no_miner_ratio real NOT NULL,
|
||||
|
||||
miner_spend_public_key bytea NOT NULL, -- plausible owner of the transaction
|
||||
miner_view_public_key bytea NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS main_likely_sweep_transactions_miner_idx ON main_likely_sweep_transactions (miner_spend_public_key, miner_view_public_key);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS main_likely_sweep_transactions_spending_output_indexes_idx ON main_likely_sweep_transactions USING GIN (spending_output_indices);
|
||||
CREATE INDEX IF NOT EXISTS main_likely_sweep_transactions_global_output_indexes_idx ON main_likely_sweep_transactions USING GIN (global_output_indices);
|
||||
CREATE INDEX IF NOT EXISTS main_likely_sweep_transactions_timestamp_idx ON main_likely_sweep_transactions (timestamp);
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
||||
|
||||
|
||||
-- Views
|
||||
CREATE EXTENSION IF NOT EXISTS pg_ivm;
|
||||
|
||||
SELECT create_immv('found_main_blocks_v4', $$
|
||||
SELECT
|
||||
m.id AS main_id,
|
||||
m.height AS main_height,
|
||||
m.timestamp AS timestamp,
|
||||
m.reward AS reward,
|
||||
m.coinbase_id AS coinbase_id,
|
||||
m.coinbase_private_key AS coinbase_private_key,
|
||||
m.difficulty AS main_difficulty,
|
||||
m.side_template_id AS template_id,
|
||||
s.side_height AS side_height,
|
||||
s.miner AS miner,
|
||||
s.uncle_of AS uncle_of,
|
||||
s.effective_height AS effective_height,
|
||||
s.window_depth AS window_depth,
|
||||
s.window_outputs AS window_outputs,
|
||||
s.transaction_count AS transaction_count,
|
||||
s.difficulty AS side_difficulty,
|
||||
s.cumulative_difficulty AS side_cumulative_difficulty,
|
||||
s.inclusion AS side_inclusion
|
||||
FROM
|
||||
(SELECT * FROM main_blocks WHERE side_template_id IS NOT NULL) AS m
|
||||
JOIN
|
||||
(SELECT * FROM side_blocks) AS s ON s.main_id = m.id
|
||||
$$);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS found_main_blocks_main_id_idx ON found_main_blocks_v4 (main_id);
|
||||
CREATE INDEX IF NOT EXISTS found_main_blocks_miner_idx ON found_main_blocks_v4 (miner);
|
||||
CREATE INDEX IF NOT EXISTS found_main_blocks_side_height_idx ON found_main_blocks_v4 (side_height);
|
||||
|
||||
SELECT create_immv('payouts_v4', $$
|
||||
SELECT
|
||||
o.miner AS miner,
|
||||
m.id AS main_id,
|
||||
m.height AS main_height,
|
||||
m.timestamp AS timestamp,
|
||||
m.coinbase_id AS coinbase_id,
|
||||
m.coinbase_private_key AS coinbase_private_key,
|
||||
m.side_template_id AS template_id,
|
||||
s.side_height AS side_height,
|
||||
s.uncle_of AS uncle_of,
|
||||
o.value AS value,
|
||||
o.index AS index,
|
||||
o.global_output_index AS global_output_index,
|
||||
s.including_height AS including_height
|
||||
FROM
|
||||
(SELECT id, value, index, global_output_index, miner FROM main_coinbase_outputs) AS o
|
||||
JOIN
|
||||
(SELECT id, height, timestamp, side_template_id, coinbase_id, coinbase_private_key FROM main_blocks) AS m ON m.coinbase_id = o.id
|
||||
JOIN
|
||||
(SELECT template_id, main_id, side_height, uncle_of, GREATEST(0, GREATEST(effective_height, side_height) - window_depth) AS including_height FROM side_blocks) AS s ON s.main_id = m.id
|
||||
$$);
|
||||
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS found_main_blocks_global_output_index_idx ON payouts_v4 (global_output_index);
|
||||
CREATE INDEX IF NOT EXISTS payouts_miner_idx ON payouts_v4 (miner);
|
||||
CREATE INDEX IF NOT EXISTS payouts_main_id_idx ON payouts_v4 (main_id);
|
||||
CREATE INDEX IF NOT EXISTS payouts_side_height_idx ON payouts_v4 (side_height);
|
||||
CREATE INDEX IF NOT EXISTS payouts_main_height_idx ON payouts_v4 (main_height);
|
|
@ -1,241 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"slices"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type BlockInclusion int
|
||||
|
||||
const (
|
||||
// InclusionOrphan orphan (was not included in-verified-chain)
|
||||
InclusionOrphan = BlockInclusion(iota)
|
||||
// InclusionInVerifiedChain in-verified-chain (uncle or main)
|
||||
InclusionInVerifiedChain
|
||||
// InclusionAlternateInVerifiedChain alternate in-verified-chain (uncle or main), for example when duplicate nonce happens
|
||||
InclusionAlternateInVerifiedChain
|
||||
|
||||
InclusionCount
|
||||
)
|
||||
|
||||
const SideBlockSelectFields = "main_id, main_height, template_id, side_height, parent_template_id, miner, uncle_of, effective_height, nonce, extra_nonce, timestamp, software_id, software_version, window_depth, window_outputs, transaction_count, difficulty, cumulative_difficulty, pow_difficulty, pow_hash, inclusion"
|
||||
|
||||
type SideBlock struct {
|
||||
// MainId mainchain id, on Monero network
|
||||
MainId types.Hash `json:"main_id"`
|
||||
MainHeight uint64 `json:"main_height"`
|
||||
|
||||
// TemplateId -- sidechain template id. Note multiple blocks can exist per template id, see Inclusion
|
||||
TemplateId types.Hash `json:"template_id"`
|
||||
SideHeight uint64 `json:"side_height"`
|
||||
// ParentTemplateId previous sidechain template id
|
||||
ParentTemplateId types.Hash `json:"parent_template_id"`
|
||||
|
||||
// Miner internal id of the miner who contributed the block
|
||||
Miner uint64 `json:"miner"`
|
||||
|
||||
// Uncle inclusion information
|
||||
|
||||
// UncleOf has been included under this parent block TemplateId as an uncle
|
||||
UncleOf types.Hash `json:"uncle_of,omitempty"`
|
||||
// EffectiveHeight has been included under this parent block height as an uncle, or is this height
|
||||
EffectiveHeight uint64 `json:"effective_height"`
|
||||
|
||||
// Nonce data
|
||||
Nonce uint32 `json:"nonce"`
|
||||
ExtraNonce uint32 `json:"extra_nonce"`
|
||||
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
SoftwareId p2pooltypes.SoftwareId `json:"software_id"`
|
||||
SoftwareVersion p2pooltypes.SoftwareVersion `json:"software_version"`
|
||||
// WindowDepth PPLNS window depth, in blocks including this one
|
||||
WindowDepth uint32 `json:"window_depth"`
|
||||
WindowOutputs uint32 `json:"window_outputs"`
|
||||
|
||||
// Difficulty sidechain difficulty at height
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
CumulativeDifficulty types.Difficulty `json:"cumulative_difficulty"`
|
||||
PowDifficulty uint64 `json:"pow_difficulty"`
|
||||
// PowHash result of PoW function as a hash (all 0x00 = not known)
|
||||
PowHash types.Hash `json:"pow_hash"`
|
||||
|
||||
Inclusion BlockInclusion `json:"inclusion"`
|
||||
|
||||
TransactionCount uint32 `json:"transaction_count"`
|
||||
|
||||
// Extra information filled just for JSON purposes
|
||||
|
||||
MinedMainAtHeight bool `json:"mined_main_at_height,omitempty"`
|
||||
MinerAddress *address.Address `json:"miner_address,omitempty"`
|
||||
MinerAlias string `json:"miner_alias,omitempty"`
|
||||
Uncles []SideBlockUncleEntry `json:"uncles,omitempty"`
|
||||
MainDifficulty uint64 `json:"main_difficulty,omitempty"`
|
||||
}
|
||||
|
||||
type SideBlockUncleEntry struct {
|
||||
TemplateId types.Hash `json:"template_id"`
|
||||
Miner uint64 `json:"miner"`
|
||||
SideHeight uint64 `json:"side_height"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
}
|
||||
|
||||
// FromPoolBlock block needs to be pre-processed for ids to be correct
|
||||
// These fields need to be filled by caller to match needs:
|
||||
// SideBlock.UncleOf
|
||||
// SideBlock.EffectiveHeight
|
||||
// SideBlock.WindowDepth
|
||||
// SideBlock.Inclusion
|
||||
func (b *SideBlock) FromPoolBlock(i *Index, block *sidechain.PoolBlock, getSeedByHeight mainblock.GetSeedByHeightFunc) error {
|
||||
b.MainId = block.MainId()
|
||||
b.TemplateId = block.SideTemplateId(i.consensus)
|
||||
|
||||
if b.MainId == types.ZeroHash {
|
||||
return errors.New("invalid main id")
|
||||
}
|
||||
if b.TemplateId == types.ZeroHash || bytes.Compare(b.TemplateId[:], block.CoinbaseExtra(sidechain.SideTemplateId)) != 0 {
|
||||
return errors.New("invalid template id")
|
||||
}
|
||||
b.MainHeight = block.Main.Coinbase.GenHeight
|
||||
b.SideHeight = block.Side.Height
|
||||
b.ParentTemplateId = block.Side.Parent
|
||||
b.Miner = i.GetOrCreateMinerPackedAddress(block.GetAddress()).id
|
||||
b.Nonce = block.Main.Nonce
|
||||
b.ExtraNonce = block.ExtraNonce()
|
||||
b.Timestamp = block.Main.Timestamp
|
||||
b.SoftwareId = block.Side.ExtraBuffer.SoftwareId
|
||||
b.SoftwareVersion = block.Side.ExtraBuffer.SoftwareVersion
|
||||
b.WindowOutputs = uint32(len(block.Main.Coinbase.Outputs))
|
||||
b.TransactionCount = uint32(len(block.Main.Transactions))
|
||||
b.Difficulty = block.Side.Difficulty.Lo
|
||||
b.CumulativeDifficulty = block.Side.CumulativeDifficulty
|
||||
b.PowHash = block.PowHash(i.Consensus().GetHasher(), getSeedByHeight)
|
||||
if b.PowHash == types.ZeroHash {
|
||||
return errors.New("invalid pow hash")
|
||||
}
|
||||
b.PowDifficulty = types.DifficultyFromPoW(b.PowHash).Lo
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *SideBlock) SetUncleOf(block *SideBlock) error {
|
||||
if block == nil {
|
||||
b.UncleOf = types.ZeroHash
|
||||
b.EffectiveHeight = b.SideHeight
|
||||
return nil
|
||||
}
|
||||
if block.IsUncle() {
|
||||
return errors.New("parent cannot be uncle")
|
||||
}
|
||||
if block.SideHeight <= b.SideHeight {
|
||||
return errors.New("parent side height cannot be lower or equal")
|
||||
}
|
||||
b.UncleOf = block.TemplateId
|
||||
b.EffectiveHeight = block.EffectiveHeight
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsTipOfHeight whether this block is considered to be the "main" block within this height, not an uncle, or alternate
|
||||
func (b *SideBlock) IsTipOfHeight() bool {
|
||||
return b.SideHeight == b.EffectiveHeight && b.Inclusion == InclusionInVerifiedChain
|
||||
}
|
||||
|
||||
func (b *SideBlock) FullId() sidechain.FullId {
|
||||
var buf sidechain.FullId
|
||||
copy(buf[:], b.TemplateId[:])
|
||||
binary.LittleEndian.PutUint32(buf[types.HashSize:], b.Nonce)
|
||||
binary.LittleEndian.PutUint32(buf[types.HashSize+unsafe.Sizeof(b.Nonce):], b.ExtraNonce)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (b *SideBlock) IsUncle() bool {
|
||||
return b.SideHeight != b.EffectiveHeight && b.UncleOf != types.ZeroHash
|
||||
}
|
||||
|
||||
func (b *SideBlock) IsOrphan() bool {
|
||||
return b.Inclusion == InclusionOrphan
|
||||
}
|
||||
|
||||
func (b *SideBlock) ScanFromRow(_ *sidechain.Consensus, row RowScanInterface) error {
|
||||
if err := row.Scan(&b.MainId, &b.MainHeight, &b.TemplateId, &b.SideHeight, &b.ParentTemplateId, &b.Miner, &b.UncleOf, &b.EffectiveHeight, &b.Nonce, &b.ExtraNonce, &b.Timestamp, &b.SoftwareId, &b.SoftwareVersion, &b.WindowDepth, &b.WindowOutputs, &b.TransactionCount, &b.Difficulty, &b.CumulativeDifficulty, &b.PowDifficulty, &b.PowHash, &b.Inclusion); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//Utilities to be used by JSON decoded blocks
|
||||
|
||||
func (b *SideBlock) Weight(tipHeight, windowSize, consensusUnclePenalty uint64) (weight, parentWeight uint64) {
|
||||
if (tipHeight - b.SideHeight) >= windowSize {
|
||||
return 0, 0
|
||||
}
|
||||
if b.IsUncle() {
|
||||
unclePenalty := types.DifficultyFrom64(b.Difficulty).Mul64(consensusUnclePenalty).Div64(100)
|
||||
uncleWeight := b.Difficulty - unclePenalty.Lo
|
||||
|
||||
return uncleWeight, unclePenalty.Lo
|
||||
} else {
|
||||
weight = b.Difficulty
|
||||
for _, u := range b.Uncles {
|
||||
if (tipHeight - u.SideHeight) >= windowSize {
|
||||
continue
|
||||
}
|
||||
weight += types.DifficultyFrom64(u.Difficulty).Mul64(consensusUnclePenalty).Div64(100).Lo
|
||||
}
|
||||
return weight, 0
|
||||
}
|
||||
}
|
||||
|
||||
func HashRatioSideBlocks(shares []*SideBlock, latest *SideBlock, consensusUnclePenalty uint64) (hashrate uint64, ratio float64, weight, total types.Difficulty) {
|
||||
if len(shares) == 0 || latest == nil {
|
||||
return 0, 0, types.ZeroDifficulty, types.ZeroDifficulty
|
||||
}
|
||||
|
||||
shares = slices.Clone(shares)
|
||||
slices.SortFunc(shares, SortSideBlock)
|
||||
|
||||
var totalWeight types.Difficulty
|
||||
for _, s := range shares {
|
||||
w, _ := s.Weight(s.SideHeight, uint64(s.WindowDepth), consensusUnclePenalty)
|
||||
totalWeight = totalWeight.Add64(w)
|
||||
}
|
||||
|
||||
cumWeight := latest.CumulativeDifficulty.Sub(shares[0].CumulativeDifficulty)
|
||||
|
||||
return totalWeight.Div64(latest.Timestamp - shares[0].Timestamp).Lo,
|
||||
totalWeight.Float64() / cumWeight.Float64(),
|
||||
totalWeight,
|
||||
cumWeight
|
||||
}
|
||||
|
||||
func SortSideBlock(a, b *SideBlock) int {
|
||||
if a == b {
|
||||
return 0
|
||||
}
|
||||
|
||||
if a == nil {
|
||||
return -1
|
||||
} else if b == nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
if a.EffectiveHeight < b.EffectiveHeight {
|
||||
return -1
|
||||
} else if a.EffectiveHeight > b.EffectiveHeight {
|
||||
return 1
|
||||
}
|
||||
if a.SideHeight < b.SideHeight {
|
||||
return -1
|
||||
} else if a.SideHeight > b.SideHeight {
|
||||
return 1
|
||||
}
|
||||
//same height, sort by main id
|
||||
return a.MainId.Compare(b.MainId)
|
||||
}
|
|
@ -1,447 +0,0 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
||||
"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/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"git.gammaspectra.live/P2Pool/sha3"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type GetByTemplateIdFunc func(h types.Hash) *SideBlock
|
||||
type GetUnclesByTemplateIdFunc func(h types.Hash) QueryIterator[SideBlock]
|
||||
type SideBlockWindowAddWeightFunc func(b *SideBlock, weight types.Difficulty)
|
||||
|
||||
type SideBlockWindowSlot struct {
|
||||
Block *SideBlock
|
||||
// Uncles that count for the window weight
|
||||
Uncles []*SideBlock
|
||||
}
|
||||
|
||||
// IterateSideBlocksInPPLNSWindow
|
||||
// Copy of sidechain.IterateBlocksInPPLNSWindow
|
||||
func IterateSideBlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consensus, difficultyByHeight block.GetDifficultyByHeightFunc, getByTemplateId GetByTemplateIdFunc, getUnclesByTemplateId GetUnclesByTemplateIdFunc, addWeightFunc SideBlockWindowAddWeightFunc, slotFunc func(slot SideBlockWindowSlot)) error {
|
||||
|
||||
cur := tip
|
||||
|
||||
var blockDepth uint64
|
||||
|
||||
var mainchainDiff types.Difficulty
|
||||
|
||||
if tip.ParentTemplateId != types.ZeroHash {
|
||||
seedHeight := randomx.SeedHeight(tip.MainHeight)
|
||||
mainchainDiff = difficultyByHeight(seedHeight)
|
||||
if mainchainDiff == types.ZeroDifficulty {
|
||||
return fmt.Errorf("couldn't get mainchain difficulty for height = %d", seedHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic PPLNS window starting from v2
|
||||
// Limit PPLNS weight to 2x of the Monero difficulty (max 2 blocks per PPLNS window on average)
|
||||
sidechainVersion := sidechain.P2PoolShareVersion(consensus, tip.Timestamp)
|
||||
|
||||
maxPplnsWeight := types.MaxDifficulty
|
||||
|
||||
if sidechainVersion > sidechain.ShareVersion_V1 {
|
||||
maxPplnsWeight = mainchainDiff.Mul64(2)
|
||||
}
|
||||
|
||||
var pplnsWeight types.Difficulty
|
||||
|
||||
for {
|
||||
curEntry := SideBlockWindowSlot{
|
||||
Block: cur,
|
||||
}
|
||||
curWeight := types.DifficultyFrom64(cur.Difficulty)
|
||||
|
||||
QueryIterate(getUnclesByTemplateId(cur.TemplateId), func(_ int, uncle *SideBlock) (stop bool) {
|
||||
//Needs to be added regardless - for other consumers
|
||||
if !slices.ContainsFunc(curEntry.Uncles, func(sideBlock *SideBlock) bool {
|
||||
return sideBlock.TemplateId == uncle.TemplateId
|
||||
}) {
|
||||
curEntry.Uncles = append(curEntry.Uncles, uncle)
|
||||
}
|
||||
|
||||
// Skip uncles which are already out of PPLNS window
|
||||
if (tip.SideHeight - uncle.SideHeight) >= consensus.ChainWindowSize {
|
||||
return false
|
||||
}
|
||||
|
||||
// Take some % of uncle's weight into this share
|
||||
uncleWeight, unclePenalty := consensus.ApplyUnclePenalty(types.DifficultyFrom64(uncle.Difficulty))
|
||||
newPplnsWeight := pplnsWeight.Add(uncleWeight)
|
||||
|
||||
// Skip uncles that push PPLNS weight above the limit
|
||||
if newPplnsWeight.Cmp(maxPplnsWeight) > 0 {
|
||||
return false
|
||||
}
|
||||
curWeight = curWeight.Add(unclePenalty)
|
||||
|
||||
if addWeightFunc != nil {
|
||||
addWeightFunc(uncle, uncleWeight)
|
||||
}
|
||||
|
||||
pplnsWeight = newPplnsWeight
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
// Always add non-uncle shares even if PPLNS weight goes above the limit
|
||||
slotFunc(curEntry)
|
||||
|
||||
if addWeightFunc != nil {
|
||||
addWeightFunc(cur, curWeight)
|
||||
}
|
||||
|
||||
pplnsWeight = pplnsWeight.Add(curWeight)
|
||||
|
||||
// One non-uncle share can go above the limit, but it will also guarantee that "shares" is never empty
|
||||
if pplnsWeight.Cmp(maxPplnsWeight) > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
blockDepth++
|
||||
|
||||
if blockDepth >= consensus.ChainWindowSize {
|
||||
break
|
||||
}
|
||||
|
||||
// Reached the genesis block so we're done
|
||||
if cur.SideHeight == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
parentId := cur.ParentTemplateId
|
||||
cur = getByTemplateId(parentId)
|
||||
|
||||
if cur == nil {
|
||||
return fmt.Errorf("could not find parent %s", parentId.String())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlocksInPPLNSWindow
|
||||
// Copy of sidechain.BlocksInPPLNSWindow
|
||||
func BlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consensus, difficultyByHeight block.GetDifficultyByHeightFunc, getByTemplateId GetByTemplateIdFunc, getUnclesByTemplateId GetUnclesByTemplateIdFunc, addWeightFunc SideBlockWindowAddWeightFunc) (bottomHeight uint64, err error) {
|
||||
|
||||
cur := tip
|
||||
|
||||
var blockDepth uint64
|
||||
|
||||
var mainchainDiff types.Difficulty
|
||||
|
||||
if tip.ParentTemplateId != types.ZeroHash {
|
||||
seedHeight := randomx.SeedHeight(tip.MainHeight)
|
||||
mainchainDiff = difficultyByHeight(seedHeight)
|
||||
if mainchainDiff == types.ZeroDifficulty {
|
||||
return 0, fmt.Errorf("couldn't get mainchain difficulty for height = %d", seedHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic PPLNS window starting from v2
|
||||
// Limit PPLNS weight to 2x of the Monero difficulty (max 2 blocks per PPLNS window on average)
|
||||
sidechainVersion := sidechain.P2PoolShareVersion(consensus, tip.Timestamp)
|
||||
|
||||
maxPplnsWeight := types.MaxDifficulty
|
||||
|
||||
if sidechainVersion > sidechain.ShareVersion_V1 {
|
||||
maxPplnsWeight = mainchainDiff.Mul64(2)
|
||||
}
|
||||
|
||||
var pplnsWeight types.Difficulty
|
||||
|
||||
for {
|
||||
curEntry := SideBlockWindowSlot{
|
||||
Block: cur,
|
||||
}
|
||||
curWeight := types.DifficultyFrom64(cur.Difficulty)
|
||||
|
||||
QueryIterate(getUnclesByTemplateId(cur.TemplateId), func(_ int, uncle *SideBlock) (stop bool) {
|
||||
//Needs to be added regardless - for other consumers
|
||||
if !slices.ContainsFunc(curEntry.Uncles, func(sideBlock *SideBlock) bool {
|
||||
return sideBlock.TemplateId == uncle.TemplateId
|
||||
}) {
|
||||
curEntry.Uncles = append(curEntry.Uncles, uncle)
|
||||
}
|
||||
|
||||
// Skip uncles which are already out of PPLNS window
|
||||
if (tip.SideHeight - uncle.SideHeight) >= consensus.ChainWindowSize {
|
||||
return false
|
||||
}
|
||||
|
||||
// Take some % of uncle's weight into this share
|
||||
uncleWeight, unclePenalty := consensus.ApplyUnclePenalty(types.DifficultyFrom64(uncle.Difficulty))
|
||||
newPplnsWeight := pplnsWeight.Add(uncleWeight)
|
||||
|
||||
// Skip uncles that push PPLNS weight above the limit
|
||||
if newPplnsWeight.Cmp(maxPplnsWeight) > 0 {
|
||||
return false
|
||||
}
|
||||
curWeight = curWeight.Add(unclePenalty)
|
||||
|
||||
if addWeightFunc != nil {
|
||||
addWeightFunc(uncle, uncleWeight)
|
||||
}
|
||||
|
||||
pplnsWeight = newPplnsWeight
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
// Always add non-uncle shares even if PPLNS weight goes above the limit
|
||||
bottomHeight = cur.SideHeight
|
||||
|
||||
if addWeightFunc != nil {
|
||||
addWeightFunc(cur, curWeight)
|
||||
}
|
||||
|
||||
pplnsWeight = pplnsWeight.Add(curWeight)
|
||||
|
||||
// One non-uncle share can go above the limit, but it will also guarantee that "shares" is never empty
|
||||
if pplnsWeight.Cmp(maxPplnsWeight) > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
blockDepth++
|
||||
|
||||
if blockDepth >= consensus.ChainWindowSize {
|
||||
break
|
||||
}
|
||||
|
||||
// Reached the genesis block so we're done
|
||||
if cur.SideHeight == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
parentId := cur.ParentTemplateId
|
||||
cur = getByTemplateId(parentId)
|
||||
|
||||
if cur == nil {
|
||||
return 0, fmt.Errorf("could not find parent %s", parentId.String())
|
||||
}
|
||||
}
|
||||
|
||||
return bottomHeight, nil
|
||||
}
|
||||
|
||||
func IterateSideBlocksInPPLNSWindowFast(indexDb *Index, tip *SideBlock, difficultyByHeight block.GetDifficultyByHeightFunc, addWeightFunc SideBlockWindowAddWeightFunc, slotFunc func(slot SideBlockWindowSlot)) error {
|
||||
if tip == nil {
|
||||
return errors.New("nil tip")
|
||||
}
|
||||
window := QueryIterateToSlice(indexDb.GetSideBlocksInPPLNSWindow(tip))
|
||||
if len(window) == 0 {
|
||||
return errors.New("nil window")
|
||||
}
|
||||
|
||||
var hintIndex int
|
||||
|
||||
getByTemplateIdFull := func(h types.Hash) *SideBlock {
|
||||
if i := slices.IndexFunc(window, func(e *SideBlock) bool {
|
||||
return e.TemplateId == h
|
||||
}); i != -1 {
|
||||
hintIndex = i
|
||||
return window[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
getByTemplateId := func(h types.Hash) *SideBlock {
|
||||
//fast lookup first
|
||||
if i := slices.IndexFunc(window[hintIndex:], func(e *SideBlock) bool {
|
||||
return e.TemplateId == h
|
||||
}); i != -1 {
|
||||
hintIndex += i
|
||||
return window[hintIndex]
|
||||
}
|
||||
return getByTemplateIdFull(h)
|
||||
}
|
||||
getUnclesOf := func(h types.Hash) QueryIterator[SideBlock] {
|
||||
parentEffectiveHeight := window[hintIndex].EffectiveHeight
|
||||
if window[hintIndex].TemplateId != h {
|
||||
parentEffectiveHeight = 0
|
||||
}
|
||||
|
||||
startIndex := 0
|
||||
|
||||
return &FakeQueryResult[SideBlock]{
|
||||
NextFunction: func() (int, *SideBlock) {
|
||||
for _, b := range window[startIndex+hintIndex:] {
|
||||
if b.UncleOf == h {
|
||||
startIndex++
|
||||
return startIndex, b
|
||||
}
|
||||
startIndex++
|
||||
|
||||
if parentEffectiveHeight != 0 && b.EffectiveHeight < parentEffectiveHeight {
|
||||
//early exit
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return IterateSideBlocksInPPLNSWindow(tip, indexDb.Consensus(), difficultyByHeight, getByTemplateId, getUnclesOf, addWeightFunc, slotFunc)
|
||||
}
|
||||
|
||||
func BlocksInPPLNSWindowFast(indexDb *Index, tip *SideBlock, difficultyByHeight block.GetDifficultyByHeightFunc, addWeightFunc SideBlockWindowAddWeightFunc) (bottomHeight uint64, err error) {
|
||||
if tip == nil {
|
||||
return 0, errors.New("nil tip")
|
||||
}
|
||||
window := QueryIterateToSlice(indexDb.GetSideBlocksInPPLNSWindow(tip))
|
||||
if len(window) == 0 {
|
||||
return 0, errors.New("nil window")
|
||||
}
|
||||
|
||||
var hintIndex int
|
||||
|
||||
getByTemplateIdFull := func(h types.Hash) *SideBlock {
|
||||
if i := slices.IndexFunc(window, func(e *SideBlock) bool {
|
||||
return e.TemplateId == h
|
||||
}); i != -1 {
|
||||
hintIndex = i
|
||||
return window[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
getByTemplateId := func(h types.Hash) *SideBlock {
|
||||
//fast lookup first
|
||||
if i := slices.IndexFunc(window[hintIndex:], func(e *SideBlock) bool {
|
||||
return e.TemplateId == h
|
||||
}); i != -1 {
|
||||
hintIndex += i
|
||||
return window[hintIndex]
|
||||
}
|
||||
return getByTemplateIdFull(h)
|
||||
}
|
||||
getUnclesOf := func(h types.Hash) QueryIterator[SideBlock] {
|
||||
parentEffectiveHeight := window[hintIndex].EffectiveHeight
|
||||
if window[hintIndex].TemplateId != h {
|
||||
parentEffectiveHeight = 0
|
||||
}
|
||||
|
||||
startIndex := 0
|
||||
|
||||
return &FakeQueryResult[SideBlock]{
|
||||
NextFunction: func() (int, *SideBlock) {
|
||||
for _, b := range window[startIndex+hintIndex:] {
|
||||
if b.UncleOf == h {
|
||||
startIndex++
|
||||
return startIndex, b
|
||||
}
|
||||
startIndex++
|
||||
|
||||
if parentEffectiveHeight != 0 && b.EffectiveHeight < parentEffectiveHeight {
|
||||
//early exit
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return BlocksInPPLNSWindow(tip, indexDb.Consensus(), difficultyByHeight, getByTemplateId, getUnclesOf, addWeightFunc)
|
||||
}
|
||||
|
||||
// GetSharesOrdered
|
||||
// Copy of sidechain.GetSharesOrdered
|
||||
func GetSharesOrdered(indexDb *Index, tip *SideBlock, difficultyByHeight block.GetDifficultyByHeightFunc, getByTemplateId GetByTemplateIdFunc, getUnclesByTemplateId GetUnclesByTemplateIdFunc, preAllocatedShares sidechain.Shares) (shares sidechain.Shares, bottomHeight uint64) {
|
||||
index := 0
|
||||
l := len(preAllocatedShares)
|
||||
|
||||
if bottomHeight, err := BlocksInPPLNSWindow(tip, indexDb.Consensus(), difficultyByHeight, getByTemplateId, getUnclesByTemplateId, func(b *SideBlock, weight types.Difficulty) {
|
||||
addr := indexDb.GetMiner(b.Miner).Address().ToPackedAddress()
|
||||
if index < l {
|
||||
preAllocatedShares[index].Address = addr
|
||||
|
||||
preAllocatedShares[index].Weight = weight
|
||||
} else {
|
||||
preAllocatedShares = append(preAllocatedShares, &sidechain.Share{
|
||||
Address: addr,
|
||||
Weight: weight,
|
||||
})
|
||||
}
|
||||
index++
|
||||
}); err != nil {
|
||||
return nil, 0
|
||||
} else {
|
||||
shares = preAllocatedShares[:index]
|
||||
|
||||
//remove dupes
|
||||
shares = shares.Compact()
|
||||
|
||||
return shares, bottomHeight
|
||||
}
|
||||
}
|
||||
|
||||
// GetShares
|
||||
// Copy of sidechain.GetShares
|
||||
func GetShares(indexDb *Index, tip *SideBlock, coinbasePrivateKeySeed types.Hash, difficultyByHeight block.GetDifficultyByHeightFunc, getByTemplateId GetByTemplateIdFunc, getUnclesByTemplateId GetUnclesByTemplateIdFunc, preAllocatedShares sidechain.Shares) (shares sidechain.Shares, bottomHeight uint64) {
|
||||
shares, bottomHeight = GetSharesOrdered(indexDb, tip, difficultyByHeight, getByTemplateId, getUnclesByTemplateId, preAllocatedShares)
|
||||
if shares == nil {
|
||||
return
|
||||
}
|
||||
|
||||
//Shuffle shares
|
||||
sidechain.ShuffleShares(shares, sidechain.P2PoolShareVersion(indexDb.Consensus(), tip.Timestamp), coinbasePrivateKeySeed)
|
||||
|
||||
return shares, bottomHeight
|
||||
}
|
||||
|
||||
// CalculateOutputs
|
||||
// Copy of sidechain.CalculateOutputs
|
||||
func CalculateOutputs(indexDb *Index, block *SideBlock, transactionOutputType uint8, totalReward uint64, coinbasePrivateKey crypto.PrivateKey, coinbasePrivateKeySeed types.Hash, difficultyByHeight block.GetDifficultyByHeightFunc, getByTemplateId GetByTemplateIdFunc, getUnclesByTemplateId GetUnclesByTemplateIdFunc, derivationCache sidechain.DerivationCacheInterface, preAllocatedShares sidechain.Shares, preAllocatedRewards []uint64) (outputs transaction.Outputs, bottomHeight uint64) {
|
||||
tmpShares, bottomHeight := GetShares(indexDb, block, coinbasePrivateKeySeed, difficultyByHeight, getByTemplateId, getUnclesByTemplateId, preAllocatedShares)
|
||||
if preAllocatedRewards == nil {
|
||||
preAllocatedRewards = make([]uint64, 0, len(tmpShares))
|
||||
}
|
||||
tmpRewards := sidechain.SplitReward(preAllocatedRewards, totalReward, tmpShares)
|
||||
|
||||
if tmpShares == nil || tmpRewards == nil || len(tmpRewards) != len(tmpShares) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
n := uint64(len(tmpShares))
|
||||
|
||||
outputs = make(transaction.Outputs, n)
|
||||
|
||||
txType := transactionOutputType
|
||||
|
||||
txPrivateKeySlice := coinbasePrivateKey.AsSlice()
|
||||
txPrivateKeyScalar := coinbasePrivateKey.AsScalar()
|
||||
|
||||
var hashers []*sha3.HasherState
|
||||
|
||||
defer func() {
|
||||
for _, h := range hashers {
|
||||
crypto.PutKeccak256Hasher(h)
|
||||
}
|
||||
}()
|
||||
|
||||
utils.SplitWork(-2, n, func(workIndex uint64, workerIndex int) error {
|
||||
output := transaction.Output{
|
||||
Index: workIndex,
|
||||
Type: txType,
|
||||
}
|
||||
output.Reward = tmpRewards[output.Index]
|
||||
output.EphemeralPublicKey, output.ViewTag = derivationCache.GetEphemeralPublicKey(&tmpShares[output.Index].Address, txPrivateKeySlice, txPrivateKeyScalar, output.Index, hashers[workerIndex])
|
||||
|
||||
outputs[output.Index] = output
|
||||
|
||||
return nil
|
||||
}, func(routines, routineIndex int) error {
|
||||
hashers = append(hashers, crypto.GetKeccak256Hasher())
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
return outputs, bottomHeight
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/legacytoarchive
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0 => ../../p2pool/cache
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0
|
||||
github.com/floatdrop/lru v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,47 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,153 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/archive"
|
||||
"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"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
func main() {
|
||||
inputConsensus := flag.String("consensus", "config.json", "Input config.json consensus file")
|
||||
inputFolder := flag.String("input", "", "Input legacy api folder with raw / failed blocks")
|
||||
outputArchive := flag.String("output", "", "Output path for archive database")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
cf, err := os.ReadFile(*inputConsensus)
|
||||
|
||||
consensus, err := sidechain.NewConsensusFromJSON(cf)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
archiveCache, err := archive.NewCache(*outputArchive, consensus, func(height uint64) types.Difficulty {
|
||||
return types.ZeroDifficulty
|
||||
})
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer archiveCache.Close()
|
||||
|
||||
processed := make(map[types.Hash]bool)
|
||||
|
||||
totalStored := 0
|
||||
|
||||
derivationCache := sidechain.NewDerivationLRUCache()
|
||||
|
||||
blockCache := lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4 * 60))
|
||||
|
||||
loadBlock := func(id types.Hash) *sidechain.PoolBlock {
|
||||
n := id.String()
|
||||
fPath := path.Join(*inputFolder, "blocks", n[:1], n)
|
||||
if buf, err := os.ReadFile(fPath); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
if hexBuf, err := hex.DecodeString(string(buf)); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
if block, err := sidechain.NewShareFromExportedBytes(hexBuf, consensus, derivationCache); err != nil {
|
||||
utils.Error("", "error decoding block %s, %s", id.String(), err)
|
||||
} else {
|
||||
block.Depth.Store(math.MaxUint64)
|
||||
return block
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
getByTemplateId := func(h types.Hash) *sidechain.PoolBlock {
|
||||
if v := blockCache.Get(h); v == nil {
|
||||
if b := loadBlock(h); b != nil {
|
||||
b.Depth.Store(math.MaxUint64)
|
||||
blockCache.Set(h, b)
|
||||
return b
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return *v
|
||||
}
|
||||
}
|
||||
|
||||
var storeBlock func(k types.Hash, b *sidechain.PoolBlock, depth uint64)
|
||||
storeBlock = func(k types.Hash, b *sidechain.PoolBlock, depth uint64) {
|
||||
if b == nil || processed[k] {
|
||||
return
|
||||
}
|
||||
if depth >= consensus.ChainWindowSize*4*30 { //avoid infinite memory growth
|
||||
return
|
||||
}
|
||||
if parent := getByTemplateId(b.Side.Parent); parent != nil {
|
||||
storeBlock(b.Side.Parent, parent, depth+1)
|
||||
topDepth := parent.Depth.Load()
|
||||
b.FillTransactionParentIndices(parent)
|
||||
if topDepth == math.MaxUint64 {
|
||||
b.Depth.Store(consensus.ChainWindowSize * 2)
|
||||
} else if topDepth == 0 {
|
||||
b.Depth.Store(0)
|
||||
} else {
|
||||
b.Depth.Store(topDepth - 1)
|
||||
}
|
||||
} else {
|
||||
b.Depth.Store(math.MaxUint64)
|
||||
}
|
||||
archiveCache.Store(b)
|
||||
totalStored++
|
||||
processed[k] = true
|
||||
}
|
||||
|
||||
for i := 0; i <= 0xf; i++ {
|
||||
n := hex.EncodeToString([]byte{byte(i)})
|
||||
dPath := path.Join(*inputFolder, "blocks", n[1:])
|
||||
utils.Logf("", "Reading directory %s", dPath)
|
||||
if dir, err := os.ReadDir(dPath); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
for _, e := range dir {
|
||||
h, _ := hex.DecodeString(e.Name())
|
||||
id := types.HashFromBytes(h)
|
||||
bb := getByTemplateId(id)
|
||||
if bb != nil && !archiveCache.ExistsByMainId(bb.MainId()) {
|
||||
storeBlock(id, bb, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i <= 0xf; i++ {
|
||||
n := hex.EncodeToString([]byte{byte(i)})
|
||||
dPath := path.Join(*inputFolder, "failed_blocks", n[1:])
|
||||
if dir, err := os.ReadDir(dPath); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
for _, e := range dir {
|
||||
fPath := path.Join(dPath, e.Name())
|
||||
utils.Logf("", "Processing %s", fPath)
|
||||
if buf, err := os.ReadFile(path.Join(dPath, e.Name())); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
if hexBuf, err := hex.DecodeString(string(buf)); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
if block, err := sidechain.NewShareFromExportedBytes(hexBuf, consensus, derivationCache); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
block.Depth.Store(math.MaxUint64)
|
||||
archiveCache.Store(block)
|
||||
totalStored++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Logf("", "total stored %d", totalStored)
|
||||
}
|
|
@ -1,892 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/httputils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/p2p"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getServerMux(instance *P2Pool) *mux.Router {
|
||||
|
||||
serveMux := mux.NewRouter()
|
||||
|
||||
archiveCache := instance.AddressableCache()
|
||||
|
||||
// ================================= Peering section =================================
|
||||
serveMux.HandleFunc("/server/peers", func(writer http.ResponseWriter, request *http.Request) {
|
||||
clients := instance.Server().Clients()
|
||||
result := make([]p2pooltypes.P2PoolServerPeerResult, 0, len(clients))
|
||||
for _, c := range clients {
|
||||
if !c.IsGood() {
|
||||
continue
|
||||
}
|
||||
result = append(result, p2pooltypes.P2PoolServerPeerResult{
|
||||
Incoming: c.IsIncomingConnection,
|
||||
Address: c.AddressPort.Addr().String(),
|
||||
SoftwareId: c.VersionInformation.SoftwareId.String(),
|
||||
SoftwareVersion: c.VersionInformation.SoftwareVersion.String(),
|
||||
ProtocolVersion: c.VersionInformation.Protocol.String(),
|
||||
ConnectionTime: uint64(c.ConnectionTime.Unix()),
|
||||
ListenPort: c.ListenPort.Load(),
|
||||
Latency: uint64(time.Duration(c.PingDuration.Load()).Milliseconds()),
|
||||
PeerId: c.PeerId.Load(),
|
||||
})
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/server/connection_check/{addrPort:.+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
addrPort, err := netip.ParseAddrPort(mux.Vars(request)["addrPort"])
|
||||
if err != nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
buf, _ := utils.MarshalJSON(struct {
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Error: err.Error(),
|
||||
})
|
||||
_, _ = writer.Write(buf)
|
||||
return
|
||||
}
|
||||
|
||||
var client *p2p.Client
|
||||
var alreadyConnected bool
|
||||
isBanned, banEntry := instance.Server().IsBanned(addrPort.Addr())
|
||||
for _, c := range instance.Server().Clients() {
|
||||
if c.AddressPort.Addr().Compare(addrPort.Addr()) == 0 && uint16(c.ListenPort.Load()) == addrPort.Port() {
|
||||
client = c
|
||||
alreadyConnected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if client == nil {
|
||||
if client, err = instance.Server().DirectConnect(addrPort); err != nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
buf, _ := utils.MarshalJSON(struct {
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Error: err.Error(),
|
||||
})
|
||||
_, _ = writer.Write(buf)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if client == nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
buf, _ := utils.MarshalJSON(struct {
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Error: "could not find client",
|
||||
})
|
||||
_, _ = writer.Write(buf)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
if client.Closed.Load() || (client.IsGood() && client.PingDuration.Load() > 0 && client.LastKnownTip.Load() != nil) {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
|
||||
banError := ""
|
||||
errorStr := ""
|
||||
if err := client.BanError(); err != nil {
|
||||
errorStr = err.Error()
|
||||
}
|
||||
|
||||
if banEntry != nil && banEntry.Error != nil {
|
||||
banError = banEntry.Error.Error()
|
||||
}
|
||||
|
||||
info := p2pooltypes.P2PoolConnectionCheckInformation[*sidechain.PoolBlock]{
|
||||
Address: client.AddressPort.Addr().Unmap().String(),
|
||||
Port: client.AddressPort.Port(),
|
||||
ListenPort: uint16(client.ListenPort.Load()),
|
||||
PeerId: client.PeerId.Load(),
|
||||
SoftwareId: client.VersionInformation.SoftwareId.String(),
|
||||
SoftwareVersion: client.VersionInformation.SoftwareVersion.String(),
|
||||
ProtocolVersion: client.VersionInformation.Protocol.String(),
|
||||
ConnectionTime: uint64(client.ConnectionTime.Unix()),
|
||||
Latency: uint64(time.Duration(client.PingDuration.Load()).Milliseconds()),
|
||||
Incoming: client.IsIncomingConnection,
|
||||
BroadcastTime: client.LastBroadcastTimestamp.Load(),
|
||||
BroadcastHeight: client.BroadcastMaxHeight.Load(),
|
||||
Tip: client.LastKnownTip.Load(),
|
||||
Closed: client.Closed.Load(),
|
||||
AlreadyConnected: alreadyConnected,
|
||||
HandshakeComplete: client.HandshakeComplete.Load(),
|
||||
LastActive: client.LastActiveTimestamp.Load(),
|
||||
Banned: isBanned,
|
||||
Error: errorStr,
|
||||
BanError: banError,
|
||||
}
|
||||
|
||||
if isBanned {
|
||||
client.Close()
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, info)
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/server/peerlist", func(writer http.ResponseWriter, request *http.Request) {
|
||||
peers := instance.Server().PeerList()
|
||||
var result []byte
|
||||
for _, c := range peers {
|
||||
result = append(result, []byte(c.AddressPort.String()+"\n")...)
|
||||
}
|
||||
writer.Header().Set("Content-Type", "text/plain")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write(result)
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/server/status", func(writer http.ResponseWriter, request *http.Request) {
|
||||
result := p2pooltypes.P2PoolServerStatusResult{
|
||||
PeerId: instance.Server().PeerId(),
|
||||
SoftwareId: instance.Server().VersionInformation().SoftwareId.String(),
|
||||
SoftwareVersion: instance.Server().VersionInformation().SoftwareVersion.String(),
|
||||
ProtocolVersion: instance.Server().VersionInformation().Protocol.String(),
|
||||
ListenPort: instance.Server().ListenPort(),
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
})
|
||||
|
||||
// ================================= MainChain section =================================
|
||||
serveMux.HandleFunc("/mainchain/header_by_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
|
||||
result := instance.GetMinimalBlockHeaderByHeight(height)
|
||||
if result == nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
}
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/mainchain/difficulty_by_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
|
||||
result := instance.GetDifficultyByHeight(height)
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/mainchain/header_by_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if id, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
|
||||
result := instance.GetMinimalBlockHeaderByHash(id)
|
||||
if result == nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
}
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/mainchain/miner_data", func(writer http.ResponseWriter, request *http.Request) {
|
||||
result := instance.GetMinerDataTip()
|
||||
if result == nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/mainchain/tip", func(writer http.ResponseWriter, request *http.Request) {
|
||||
minerData := instance.GetMinerDataTip()
|
||||
if minerData == nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
result := instance.GetMinimalBlockHeaderByHeight(minerData.Height - 1)
|
||||
if result == nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
}
|
||||
})
|
||||
|
||||
// ================================= SideChain section =================================
|
||||
serveMux.HandleFunc("/sidechain/consensus", func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, instance.Consensus())
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/sidechain/blocks_by_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
|
||||
result := make([]p2pooltypes.P2PoolBinaryBlockResult, 0, 1)
|
||||
for _, b := range instance.SideChain().GetPoolBlocksByHeight(height) {
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("[]"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/sidechain/block_by_template_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if templateId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
|
||||
var result p2pooltypes.P2PoolBinaryBlockResult
|
||||
if b := instance.SideChain().GetPoolBlockByTemplateId(templateId); b != nil {
|
||||
result.Version = int(b.ShareVersion())
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Error = err.Error()
|
||||
} else {
|
||||
result.Blob = blob
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("[]"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/sidechain/state/short", func(writer http.ResponseWriter, request *http.Request) {
|
||||
tip := instance.SideChain().GetChainTip()
|
||||
if tip != nil {
|
||||
tipId := instance.SideChain().GetChainTip().SideTemplateId(instance.Consensus())
|
||||
chain, uncles := instance.SideChain().GetPoolBlocksFromTip(tipId)
|
||||
result := p2pooltypes.P2PoolSideChainStateResult{
|
||||
TipHeight: tip.Side.Height,
|
||||
TipId: tipId,
|
||||
Chain: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(chain)),
|
||||
Uncles: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(uncles)),
|
||||
}
|
||||
for _, b := range chain {
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, b := range uncles {
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/sidechain/state/tip", func(writer http.ResponseWriter, request *http.Request) {
|
||||
tip := instance.SideChain().GetChainTip()
|
||||
if tip != nil {
|
||||
tipId := instance.SideChain().GetChainTip().SideTemplateId(instance.Consensus())
|
||||
chain, uncles := instance.SideChain().GetPoolBlocksFromTip(tipId)
|
||||
result := p2pooltypes.P2PoolSideChainStateResult{
|
||||
TipHeight: tip.Side.Height,
|
||||
TipId: tipId,
|
||||
Chain: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(chain)),
|
||||
Uncles: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(uncles)),
|
||||
}
|
||||
for _, b := range chain {
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, b := range uncles {
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/sidechain/state/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if templateId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
|
||||
tip := instance.SideChain().GetPoolBlockByTemplateId(templateId)
|
||||
if tip != nil {
|
||||
tipId := tip.SideTemplateId(instance.Consensus())
|
||||
chain, uncles := instance.SideChain().GetPoolBlocksFromTip(tipId)
|
||||
result := p2pooltypes.P2PoolSideChainStateResult{
|
||||
TipHeight: tip.Side.Height,
|
||||
TipId: tipId,
|
||||
Chain: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(chain)),
|
||||
Uncles: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, len(uncles)),
|
||||
}
|
||||
for _, b := range chain {
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, b := range uncles {
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/sidechain/tip", func(writer http.ResponseWriter, request *http.Request) {
|
||||
var result p2pooltypes.P2PoolBinaryBlockResult
|
||||
if b := instance.SideChain().GetChainTip(); b != nil {
|
||||
result.Version = int(b.ShareVersion())
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Error = err.Error()
|
||||
} else {
|
||||
result.Blob = blob
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
})
|
||||
serveMux.HandleFunc("/sidechain/status", func(writer http.ResponseWriter, request *http.Request) {
|
||||
result := p2pooltypes.P2PoolSideChainStatusResult{
|
||||
Synchronized: instance.SideChain().PreCalcFinished(),
|
||||
Blocks: instance.SideChain().GetPoolBlockCount(),
|
||||
}
|
||||
tip := instance.SideChain().GetHighestKnownTip()
|
||||
|
||||
if tip != nil {
|
||||
result.Height = tip.Side.Height
|
||||
result.Id = tip.SideTemplateId(instance.Consensus())
|
||||
result.Difficulty = tip.Side.Difficulty
|
||||
result.CumulativeDifficulty = tip.Side.CumulativeDifficulty
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
})
|
||||
|
||||
preAllocatedSharesPool := sidechain.NewPreAllocatedSharesPool(instance.Consensus().ChainWindowSize * 2)
|
||||
|
||||
// ================================= Archive section =================================
|
||||
if archiveCache != nil {
|
||||
serveMux.HandleFunc("/archive/window_from_template_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if templateId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
|
||||
tpls := archiveCache.LoadByTemplateId(templateId)
|
||||
if len(tpls) > 0 {
|
||||
tip := tpls[0]
|
||||
|
||||
mainDifficulty := instance.GetDifficultyByHeight(randomx.SeedHeight(tip.Main.Coinbase.GenHeight))
|
||||
|
||||
expectedBlocks := int(mainDifficulty.Mul64(2).Div(tip.Side.Difficulty).Lo) + 20
|
||||
if expectedBlocks < 100 {
|
||||
expectedBlocks = 100
|
||||
}
|
||||
if expectedBlocks > int(instance.Consensus().ChainWindowSize) {
|
||||
expectedBlocks = int(instance.Consensus().ChainWindowSize)
|
||||
}
|
||||
if tip.ShareVersion() == sidechain.ShareVersion_V1 {
|
||||
expectedBlocks = int(instance.Consensus().ChainWindowSize)
|
||||
}
|
||||
|
||||
var windowLock sync.RWMutex
|
||||
window := make(sidechain.UniquePoolBlockSlice, 0, expectedBlocks)
|
||||
window = append(window, tip)
|
||||
|
||||
getByTemplateId := func(id types.Hash) *sidechain.PoolBlock {
|
||||
if b := func() *sidechain.PoolBlock {
|
||||
windowLock.RLock()
|
||||
defer windowLock.RUnlock()
|
||||
if b := window.Get(id); b != nil {
|
||||
return b
|
||||
}
|
||||
return nil
|
||||
}(); b != nil {
|
||||
return b
|
||||
}
|
||||
windowLock.Lock()
|
||||
defer windowLock.Unlock()
|
||||
if bs := archiveCache.LoadByTemplateId(id); len(bs) > 0 {
|
||||
window = append(window, bs[0])
|
||||
return bs[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
preAllocatedShares := preAllocatedSharesPool.Get()
|
||||
defer preAllocatedSharesPool.Put(preAllocatedShares)
|
||||
|
||||
if _, err = tip.PreProcessBlock(instance.Consensus(), instance.SideChain().DerivationCache(), preAllocatedShares, instance.GetDifficultyByHeight, getByTemplateId); err == nil {
|
||||
result := p2pooltypes.P2PoolSideChainStateResult{
|
||||
TipHeight: tip.Side.Height,
|
||||
TipId: tip.SideTemplateId(instance.Consensus()),
|
||||
Chain: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, expectedBlocks),
|
||||
Uncles: make([]p2pooltypes.P2PoolBinaryBlockResult, 0, expectedBlocks/5),
|
||||
}
|
||||
|
||||
var topError error
|
||||
|
||||
topError = sidechain.IterateBlocksInPPLNSWindow(tip, instance.Consensus(), instance.GetDifficultyByHeight, getByTemplateId, nil, func(e sidechain.PoolBlockWindowSlot) {
|
||||
if _, err = e.Block.PreProcessBlock(instance.Consensus(), instance.SideChain().DerivationCache(), preAllocatedShares, instance.GetDifficultyByHeight, getByTemplateId); err != nil {
|
||||
topError = err
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(e.Block.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else if blob, err := e.Block.AppendBinaryFlags(make([]byte, 0, e.Block.BufferLength()), false, false); err != nil {
|
||||
topError = err
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(e.Block.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result.Chain = append(result.Chain, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(e.Block.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
|
||||
for _, u := range e.Uncles {
|
||||
if _, err = u.PreProcessBlock(instance.Consensus(), instance.SideChain().DerivationCache(), preAllocatedShares, instance.GetDifficultyByHeight, getByTemplateId); err != nil {
|
||||
topError = err
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(u.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else if blob, err := u.AppendBinaryFlags(make([]byte, 0, u.BufferLength()), false, false); err != nil {
|
||||
topError = err
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(u.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result.Uncles = append(result.Uncles, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(u.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if topError == nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
})
|
||||
serveMux.HandleFunc("/archive/store_alternate", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if buf, err := io.ReadAll(request.Body); err != nil {
|
||||
return
|
||||
} else {
|
||||
b := &sidechain.PoolBlock{}
|
||||
|
||||
if err = b.UnmarshalBinary(instance.Consensus(), instance.SideChain().DerivationCache(), buf); err != nil {
|
||||
return
|
||||
}
|
||||
if b.NeedsPreProcess() {
|
||||
return
|
||||
}
|
||||
templateId := b.SideTemplateId(instance.Consensus())
|
||||
if bytes.Compare(b.CoinbaseExtra(sidechain.SideTemplateId), templateId[:]) != 0 {
|
||||
return
|
||||
}
|
||||
if archiveCache.LoadByMainId(b.MainId()) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
existingBlock := archiveCache.LoadByTemplateId(templateId)
|
||||
|
||||
if len(existingBlock) == 0 {
|
||||
return
|
||||
}
|
||||
tempData, _ := existingBlock[0].MarshalBinary()
|
||||
|
||||
newBlock := &sidechain.PoolBlock{}
|
||||
if err = newBlock.UnmarshalBinary(instance.Consensus(), instance.SideChain().DerivationCache(), tempData); err != nil {
|
||||
return
|
||||
}
|
||||
//set extra nonce and nonce
|
||||
newBlock.Main.Coinbase.Extra[1] = b.Main.Coinbase.Extra[1]
|
||||
newBlock.Main.Nonce = b.Main.Nonce
|
||||
newBlock.Depth.Store(math.MaxUint64)
|
||||
|
||||
if !newBlock.IsProofHigherThanDifficulty(instance.Consensus().GetHasher(), func(height uint64) (hash types.Hash) {
|
||||
seedHeight := randomx.SeedHeight(height)
|
||||
if h := instance.GetMinimalBlockHeaderByHeight(seedHeight); h != nil {
|
||||
return h.Id
|
||||
} else {
|
||||
return types.ZeroHash
|
||||
}
|
||||
}) {
|
||||
return
|
||||
}
|
||||
|
||||
//finally store alternate
|
||||
archiveCache.Store(newBlock)
|
||||
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/archive/blocks_by_template_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if templateId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
|
||||
result := make([]p2pooltypes.P2PoolBinaryBlockResult, 0, 1)
|
||||
for _, b := range archiveCache.LoadByTemplateId(templateId) {
|
||||
if err := archiveCache.ProcessBlock(b); err != nil {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("[]"))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/archive/blocks_by_side_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
|
||||
result := make([]p2pooltypes.P2PoolBinaryBlockResult, 0, 1)
|
||||
for _, b := range archiveCache.LoadBySideChainHeight(height) {
|
||||
if err := archiveCache.ProcessBlock(b); err != nil {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("[]"))
|
||||
}
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/archive/light_blocks_by_main_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
|
||||
if blocks := archiveCache.LoadByMainChainHeight(height); len(blocks) > 0 {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, blocks)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
if blocks == nil {
|
||||
blocks = make(sidechain.UniquePoolBlockSlice, 0)
|
||||
}
|
||||
_ = cmdutils.EncodeJson(request, writer, blocks)
|
||||
}
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/archive/light_blocks_by_side_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
|
||||
if blocks := archiveCache.LoadBySideChainHeight(height); len(blocks) > 0 {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, blocks)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
if blocks == nil {
|
||||
blocks = make(sidechain.UniquePoolBlockSlice, 0)
|
||||
}
|
||||
_ = cmdutils.EncodeJson(request, writer, blocks)
|
||||
}
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/archive/light_blocks_by_template_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if mainId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
|
||||
if blocks := archiveCache.LoadByTemplateId(mainId); len(blocks) > 0 {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, blocks)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
if blocks == nil {
|
||||
blocks = make(sidechain.UniquePoolBlockSlice, 0)
|
||||
}
|
||||
_ = cmdutils.EncodeJson(request, writer, blocks)
|
||||
}
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/archive/light_block_by_main_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
params := request.URL.Query()
|
||||
|
||||
var templateIdHint types.Hash
|
||||
if params.Has("templateIdHint") {
|
||||
if h, err := types.HashFromString(params.Get("templateIdHint")); err == nil {
|
||||
templateIdHint = h
|
||||
}
|
||||
}
|
||||
|
||||
if mainId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
|
||||
var b *sidechain.PoolBlock
|
||||
if templateIdHint != types.ZeroHash {
|
||||
// Fast lookup on sidechain
|
||||
if b = instance.SideChain().GetPoolBlockByTemplateId(templateIdHint); b == nil || b.MainId() != mainId {
|
||||
b = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
if b == nil {
|
||||
b = archiveCache.LoadByMainId(mainId)
|
||||
}
|
||||
|
||||
if b != nil {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, b)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = writer.Write([]byte("{}"))
|
||||
}
|
||||
})
|
||||
|
||||
serveMux.HandleFunc("/archive/block_by_main_id/{id:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
params := request.URL.Query()
|
||||
|
||||
var templateIdHint types.Hash
|
||||
if params.Has("templateIdHint") {
|
||||
if h, err := types.HashFromString(params.Get("templateIdHint")); err == nil {
|
||||
templateIdHint = h
|
||||
}
|
||||
}
|
||||
|
||||
if mainId, err := types.HashFromString(mux.Vars(request)["id"]); err == nil {
|
||||
var result p2pooltypes.P2PoolBinaryBlockResult
|
||||
var b *sidechain.PoolBlock
|
||||
if templateIdHint != types.ZeroHash {
|
||||
// Fast lookup on sidechain
|
||||
if b = instance.SideChain().GetPoolBlockByTemplateId(templateIdHint); b == nil || b.MainId() != mainId {
|
||||
b = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
if b == nil {
|
||||
b = archiveCache.LoadByMainId(mainId)
|
||||
}
|
||||
|
||||
if b != nil {
|
||||
result.Version = int(b.ShareVersion())
|
||||
if err := archiveCache.ProcessBlock(b); err != nil {
|
||||
result.Error = err.Error()
|
||||
} else {
|
||||
if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result.Error = err.Error()
|
||||
} else {
|
||||
result.Blob = blob
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte(""))
|
||||
}
|
||||
})
|
||||
serveMux.HandleFunc("/archive/blocks_by_main_height/{height:[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if height, err := strconv.ParseUint(mux.Vars(request)["height"], 10, 0); err == nil {
|
||||
result := make([]p2pooltypes.P2PoolBinaryBlockResult, 0, 1)
|
||||
for _, b := range archiveCache.LoadByMainChainHeight(height) {
|
||||
if err := archiveCache.ProcessBlock(b); err != nil {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else if blob, err := b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), false, false); err != nil {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Error: err.Error(),
|
||||
})
|
||||
} else {
|
||||
result = append(result, p2pooltypes.P2PoolBinaryBlockResult{
|
||||
Version: int(b.ShareVersion()),
|
||||
Blob: blob,
|
||||
})
|
||||
}
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_ = cmdutils.EncodeJson(request, writer, result)
|
||||
} else {
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte("[]"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return serveMux
|
||||
}
|
Binary file not shown.
|
@ -1,43 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/p2pool
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/httputils v0.0.0 => ../httputils
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0 => ../../p2pool/cache
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/httputils v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/floatdrop/lru v1.3.0 // indirect
|
||||
github.com/go-zeromq/goczmq/v4 v4.2.2 // indirect
|
||||
github.com/go-zeromq/zmq4 v0.16.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,56 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/go-zeromq/goczmq/v4 v4.2.2 h1:HAJN+i+3NW55ijMJJhk7oWxHKXgAuSBkoFfvr8bYj4U=
|
||||
github.com/go-zeromq/goczmq/v4 v4.2.2/go.mod h1:Sm/lxrfxP/Oxqs0tnHD6WAhwkWrx+S+1MRrKzcxoaYE=
|
||||
github.com/go-zeromq/zmq4 v0.16.0 h1:D6oIPWSdkY/4DJu4tBUmo28P3WRq4F4Ji4/iQ/fJHc0=
|
||||
github.com/go-zeromq/zmq4 v0.16.0/go.mod h1:8c3aXloJBRPba1AqWMJK4vypniM+yC+JKqi8KpRaDFc=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,318 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/p2p"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
currentConsensus := sidechain.ConsensusDefault
|
||||
|
||||
//monerod related
|
||||
moneroHost := flag.String("host", "127.0.0.1", "IP address of your Monero node")
|
||||
moneroRpcPort := flag.Uint("rpc-port", 18081, "monerod RPC API port number")
|
||||
moneroZmqPort := flag.Uint("zmq-port", 18083, "monerod ZMQ pub port number")
|
||||
|
||||
// consensus related
|
||||
consensusConfigFile := flag.String("consensus-config", "", "Name of the p2pool consensus config file")
|
||||
useMiniSidechain := flag.Bool("mini", false, "Connect to p2pool-mini sidechain. Note that it will also change default p2p port.")
|
||||
|
||||
//p2p peering related
|
||||
p2pListen := flag.String("p2p", fmt.Sprintf("0.0.0.0:%d", currentConsensus.DefaultPort()), "IP:port for p2p server to listen on.")
|
||||
p2pExternalPort := flag.Uint64("p2p-external-port", 0, "Port number that your router uses for mapping to your local p2p port. Use it if you are behind a NAT and still want to accept incoming connections")
|
||||
outPeers := flag.Uint64("out-peers", 10, "Maximum number of outgoing connections for p2p server (any value between 10 and 450)")
|
||||
inPeers := flag.Uint64("in-peers", 10, "Maximum number of incoming connections for p2p server (any value between 10 and 450)")
|
||||
addPeers := flag.String("addpeers", "", "Comma-separated list of IP:port of other p2pool nodes to connect to")
|
||||
addSelf := flag.Bool("add-self-peer", false, "Adds itself to the peer list regularly, based on found local interfaces for IPv4/IPv6")
|
||||
peerList := flag.String("peer-list", "p2pool_peers.txt", "Either a path or an URL to obtain peer lists from. If it is a path, new peers will be saved to this path. Set to empty to disable")
|
||||
|
||||
//other settings
|
||||
lightMode := flag.Bool("light-mode", false, "Don't allocate RandomX dataset, saves 2GB of RAM")
|
||||
noDns := flag.Bool("no-dns", false, "Disable DNS queries, use only IP addresses to connect to peers (seed node DNS will be unavailable too)")
|
||||
memoryLimitInGiB := flag.Uint64("memory-limit", 0, "Memory limit for go managed sections in GiB, set 0 to disable")
|
||||
|
||||
apiBind := flag.String("api-bind", "", "Bind to this address to serve blocks, and other utility methods. If -archive is specified, serve archived blocks.")
|
||||
createArchive := flag.String("archive", "", "If specified, create an archive store of sidechain blocks on this path.")
|
||||
blockCache := flag.String("block-cache", "p2pool.cache", "Block cache for faster startups. Set to empty to disable")
|
||||
|
||||
//testing settings
|
||||
doDebug := flag.Bool("debug", false, "Log more details. Default false")
|
||||
ipv6Only := flag.Bool("ipv6-only", false, "Use only IPv6. Default false")
|
||||
|
||||
debugListen := flag.String("debug-listen", "", "Provide a bind address and port to expose a pprof HTTP API on it.")
|
||||
|
||||
//TODO extend verbosity to debug flag
|
||||
flag.Parse()
|
||||
|
||||
if *doDebug {
|
||||
utils.LogFile = true
|
||||
}
|
||||
|
||||
if buildInfo, _ := debug.ReadBuildInfo(); buildInfo != nil {
|
||||
utils.Logf("P2Pool", "Consensus Software %s %s (go version %s)", types.CurrentSoftwareId, types.CurrentSoftwareVersion, buildInfo.GoVersion)
|
||||
} else {
|
||||
utils.Logf("P2Pool", "Consensus Software %s %s (go version %s)", types.CurrentSoftwareId, types.CurrentSoftwareVersion, runtime.Version())
|
||||
}
|
||||
|
||||
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
|
||||
|
||||
debug.SetTraceback("all")
|
||||
if *memoryLimitInGiB != 0 {
|
||||
debug.SetMemoryLimit(int64(*memoryLimitInGiB) * 1024 * 1024 * 1024)
|
||||
}
|
||||
|
||||
settings := make(map[string]string)
|
||||
settings["listen"] = *p2pListen
|
||||
|
||||
changeConsensus := func(newConsensus *sidechain.Consensus) {
|
||||
oldListen := netip.MustParseAddrPort(settings["listen"])
|
||||
// if default exists, change port to match
|
||||
if settings["listen"] == fmt.Sprintf("%s:%d", oldListen.Addr().String(), currentConsensus.DefaultPort()) {
|
||||
settings["listen"] = fmt.Sprintf("%s:%d", oldListen.Addr().String(), newConsensus.DefaultPort())
|
||||
}
|
||||
currentConsensus = newConsensus
|
||||
}
|
||||
|
||||
settings["rpc-url"] = fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort)
|
||||
settings["zmq-url"] = fmt.Sprintf("tcp://%s:%d", *moneroHost, *moneroZmqPort)
|
||||
if *useMiniSidechain {
|
||||
changeConsensus(sidechain.ConsensusMini)
|
||||
}
|
||||
|
||||
if *consensusConfigFile != "" {
|
||||
consensusData, err := os.ReadFile(*consensusConfigFile)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
if newConsensus, err := sidechain.NewConsensusFromJSON(consensusData); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
changeConsensus(newConsensus)
|
||||
}
|
||||
}
|
||||
|
||||
settings["out-peers"] = strconv.FormatUint(*outPeers, 10)
|
||||
settings["in-peers"] = strconv.FormatUint(*inPeers, 10)
|
||||
settings["external-port"] = strconv.FormatUint(*p2pExternalPort, 10)
|
||||
|
||||
if *ipv6Only {
|
||||
settings["ipv6-only"] = "true"
|
||||
}
|
||||
|
||||
if *blockCache != "" {
|
||||
settings["cache"] = *blockCache
|
||||
}
|
||||
|
||||
if *createArchive != "" {
|
||||
settings["archive"] = *createArchive
|
||||
}
|
||||
|
||||
if !*lightMode {
|
||||
settings["full-mode"] = "true"
|
||||
}
|
||||
|
||||
if instance, err := NewP2Pool(currentConsensus, settings); err != nil {
|
||||
utils.Fatalf("Could not start p2pool: %s", err)
|
||||
} else {
|
||||
defer instance.Close(nil)
|
||||
|
||||
if *apiBind != "" {
|
||||
|
||||
serveMux := getServerMux(instance)
|
||||
|
||||
server := &http.Server{
|
||||
Addr: *apiBind,
|
||||
ReadTimeout: time.Second * 2,
|
||||
Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method != "GET" && request.Method != "HEAD" && request.Method != "POST" {
|
||||
writer.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
utils.Logf("API", "Handling %s %s", request.Method, request.URL.String())
|
||||
|
||||
serveMux.ServeHTTP(writer, request)
|
||||
}),
|
||||
}
|
||||
|
||||
go func() {
|
||||
//TODO: context/wait
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
if *debugListen != "" {
|
||||
go func() {
|
||||
if err := http.ListenAndServe(*debugListen, nil); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var connectList []netip.AddrPort
|
||||
for _, peerAddr := range strings.Split(*addPeers, ",") {
|
||||
if peerAddr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
//TODO: dns resolution of hosts
|
||||
|
||||
if addrPort, err := netip.ParseAddrPort(peerAddr); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
instance.Server().AddToPeerList(addrPort)
|
||||
connectList = append(connectList, addrPort)
|
||||
}
|
||||
}
|
||||
|
||||
if !*noDns && currentConsensus.SeedNode() != "" {
|
||||
utils.Logf("P2Pool", "Loading seed peers from %s", currentConsensus.SeedNode())
|
||||
records, _ := net.LookupTXT(currentConsensus.SeedNode())
|
||||
if len(records) > 0 {
|
||||
for _, r := range records {
|
||||
for _, e := range strings.Split(r, ",") {
|
||||
if seedNodeAddr, err := netip.ParseAddrPort(e); err == nil {
|
||||
instance.Server().AddToPeerList(seedNodeAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ips, _ := net.LookupIP(currentConsensus.SeedNode())
|
||||
for _, seedNodeIp := range ips {
|
||||
seedNodeAddr := netip.AddrPortFrom(netip.MustParseAddr(seedNodeIp.String()), currentConsensus.DefaultPort())
|
||||
instance.Server().AddToPeerList(seedNodeAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *peerList != "" {
|
||||
utils.Logf("P2Pool", "Loading peers from %s", *peerList)
|
||||
if len(*peerList) > 4 && (*peerList)[:4] == "http" {
|
||||
func() {
|
||||
r, err := http.DefaultClient.Get(*peerList)
|
||||
if err == nil {
|
||||
defer r.Body.Close()
|
||||
scanner := bufio.NewScanner(r.Body)
|
||||
for scanner.Scan() {
|
||||
if addrPort, err := netip.ParseAddrPort(strings.TrimSpace(scanner.Text())); err == nil {
|
||||
instance.Server().AddToPeerList(addrPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
func() {
|
||||
f, err := os.Open(*peerList)
|
||||
if err == nil {
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
if addrPort, err := netip.ParseAddrPort(strings.TrimSpace(scanner.Text())); err == nil {
|
||||
instance.Server().AddToPeerList(addrPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
contents := make([]byte, 0, 4096)
|
||||
for range utils.ContextTick(instance.Context(), time.Minute*1) {
|
||||
contents = contents[:0]
|
||||
peerListEntries := instance.Server().PeerList()
|
||||
slices.SortFunc(peerListEntries, func(a, b *p2p.PeerListEntry) int {
|
||||
return a.AddressPort.Compare(b.AddressPort)
|
||||
})
|
||||
for _, addrPort := range peerListEntries {
|
||||
contents = append(contents, []byte(addrPort.AddressPort.String())...)
|
||||
contents = append(contents, '\n')
|
||||
}
|
||||
if err := os.WriteFile(*peerList, contents, 0644); err != nil {
|
||||
utils.Errorf("P2Pool", "error writing peer list %s: %s", *peerList, err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
if *addSelf {
|
||||
if addrs, err := utils.GetOutboundIPv6(); err != nil {
|
||||
utils.Errorf("P2Pool", "Could not get interface ipv6 addresses: %s", err)
|
||||
} else {
|
||||
for _, addr := range addrs {
|
||||
if addr.Is6() {
|
||||
//use own port directly?
|
||||
instance.Server().AddToPeerList(netip.AddrPortFrom(addr, instance.Server().ListenPort()))
|
||||
} else {
|
||||
instance.Server().AddToPeerList(netip.AddrPortFrom(addr, instance.Server().ExternalListenPort()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
for !instance.Started() {
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, addrPort := range connectList {
|
||||
wg.Add(1)
|
||||
go func(addrPort netip.AddrPort) {
|
||||
defer wg.Done()
|
||||
if err := instance.Server().Connect(addrPort); err != nil {
|
||||
utils.Errorf("error connecting to initial peer %s: %s", addrPort.String(), err.Error())
|
||||
}
|
||||
}(addrPort)
|
||||
}
|
||||
wg.Wait()
|
||||
instance.Server().UpdateClientConnections()
|
||||
}()
|
||||
|
||||
sigHandler := make(chan os.Signal, 1)
|
||||
signal.Notify(sigHandler, syscall.SIGINT)
|
||||
go func() {
|
||||
for s := range sigHandler {
|
||||
if s == syscall.SIGKILL || s == syscall.SIGINT {
|
||||
instance.Close(nil)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := instance.Run(); err != nil {
|
||||
instance.Close(err)
|
||||
if instance.Started() {
|
||||
instance.WaitUntilClosed()
|
||||
}
|
||||
if closeError := instance.CloseError(); closeError != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,627 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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/client/zmq"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/archive"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/legacy"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/mainchain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/mempool"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/p2p"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EventListener struct {
|
||||
ListenerId uint64
|
||||
Tip func(tip *sidechain.PoolBlock)
|
||||
Broadcast func(b *sidechain.PoolBlock)
|
||||
Found func(data *sidechain.ChainMain, b *sidechain.PoolBlock)
|
||||
MainData func(data *sidechain.ChainMain)
|
||||
MinerData func(data *p2pooltypes.MinerData)
|
||||
MempoolData func(data mempool.Mempool)
|
||||
}
|
||||
|
||||
type P2Pool struct {
|
||||
consensus *sidechain.Consensus
|
||||
sidechain *sidechain.SideChain
|
||||
mainchain *mainchain.MainChain
|
||||
archive cache.AddressableCache
|
||||
cache cache.HeapCache
|
||||
server *p2p.Server
|
||||
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
|
||||
rpcClient *client.Client
|
||||
zmqClient *zmq.Client
|
||||
|
||||
recentSubmittedBlocks *utils.CircularBuffer[types.Hash]
|
||||
|
||||
listenersLock sync.RWMutex
|
||||
listeners []EventListener
|
||||
nextListenerId uint64
|
||||
|
||||
started atomic.Bool
|
||||
closeError error
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
// AddListener Registers listener to several events produced centrally.
|
||||
// Note that you should process events called as fast as possible, or spawn a new goroutine to not block
|
||||
func (p *P2Pool) AddListener(tip func(tip *sidechain.PoolBlock), broadcast func(b *sidechain.PoolBlock), found func(data *sidechain.ChainMain, b *sidechain.PoolBlock), mainData func(data *sidechain.ChainMain), minerData func(data *p2pooltypes.MinerData), mempoolData func(data mempool.Mempool)) (listenerId uint64) {
|
||||
p.listenersLock.Lock()
|
||||
p.listenersLock.Unlock()
|
||||
|
||||
listenerId = p.nextListenerId
|
||||
p.nextListenerId++
|
||||
p.listeners = append(p.listeners, EventListener{
|
||||
ListenerId: listenerId,
|
||||
Tip: tip,
|
||||
Broadcast: broadcast,
|
||||
Found: found,
|
||||
MainData: mainData,
|
||||
MinerData: minerData,
|
||||
MempoolData: mempoolData,
|
||||
})
|
||||
return listenerId
|
||||
}
|
||||
|
||||
func (p *P2Pool) RemoveListener(listenerId uint64) bool {
|
||||
p.listenersLock.Lock()
|
||||
p.listenersLock.Unlock()
|
||||
if i := slices.IndexFunc(p.listeners, func(listener EventListener) bool {
|
||||
return listener.ListenerId == listenerId
|
||||
}); i != -1 {
|
||||
p.listeners = slices.Delete(p.listeners, i, i+1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *P2Pool) GetBlob(key []byte) (blob []byte, err error) {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
func (p *P2Pool) SetBlob(key, blob []byte) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P2Pool) RemoveBlob(key []byte) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P2Pool) AddressableCache() cache.AddressableCache {
|
||||
return p.archive
|
||||
}
|
||||
|
||||
func (p *P2Pool) Cache() cache.Cache {
|
||||
return p.cache
|
||||
}
|
||||
|
||||
func (p *P2Pool) CloseError() error {
|
||||
return p.closeError
|
||||
}
|
||||
|
||||
func (p *P2Pool) WaitUntilClosed() {
|
||||
<-p.closed
|
||||
}
|
||||
|
||||
func (p *P2Pool) Close(err error) {
|
||||
started := p.started.Swap(false)
|
||||
|
||||
if started {
|
||||
p.closeError = err
|
||||
}
|
||||
|
||||
p.ctxCancel()
|
||||
_ = p.zmqClient.Close()
|
||||
|
||||
if p.server != nil {
|
||||
p.server.Close()
|
||||
}
|
||||
if p.cache != nil {
|
||||
p.cache.Close()
|
||||
}
|
||||
if p.archive != nil {
|
||||
p.archive.Close()
|
||||
}
|
||||
|
||||
if started && err != nil {
|
||||
close(p.closed)
|
||||
utils.Panicf("[P2Pool] Closed due to error %s", err)
|
||||
} else if started {
|
||||
close(p.closed)
|
||||
utils.Logf("P2Pool", "Closed")
|
||||
} else if err != nil {
|
||||
utils.Logf("P2Pool", "Received extra error during closing %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func NewP2Pool(consensus *sidechain.Consensus, settings map[string]string) (*P2Pool, error) {
|
||||
|
||||
if settings["full-mode"] == "true" {
|
||||
if err := consensus.InitHasher(2, randomx.FlagSecure, randomx.FlagFullMemory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := consensus.InitHasher(2, randomx.FlagSecure); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pool := &P2Pool{
|
||||
consensus: consensus,
|
||||
recentSubmittedBlocks: utils.NewCircularBuffer[types.Hash](8),
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
var err error
|
||||
|
||||
pool.ctx, pool.ctxCancel = context.WithCancel(context.Background())
|
||||
|
||||
listenAddress := fmt.Sprintf("0.0.0.0:%d", pool.Consensus().DefaultPort())
|
||||
|
||||
if pool.rpcClient, err = client.NewClient(settings["rpc-url"]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool.zmqClient = zmq.NewClient(settings["zmq-url"], zmq.TopicFullChainMain, zmq.TopicFullMinerData, zmq.TopicMinimalTxPoolAdd)
|
||||
|
||||
pool.sidechain = sidechain.NewSideChain(pool)
|
||||
|
||||
pool.mainchain = mainchain.NewMainChain(pool.sidechain, pool)
|
||||
|
||||
if pool.mainchain == nil {
|
||||
return nil, errors.New("could not create MainChain")
|
||||
}
|
||||
|
||||
if archivePath, ok := settings["archive"]; ok {
|
||||
if pool.archive, err = archive.NewCache(archivePath, pool.consensus, pool.GetDifficultyByHeight); err != nil {
|
||||
return nil, fmt.Errorf("could not create cache: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if cachePath, ok := settings["cache"]; ok {
|
||||
if pool.cache, err = legacy.NewCache(consensus, cachePath); err != nil {
|
||||
return nil, fmt.Errorf("could not create cache: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if addr, ok := settings["listen"]; ok {
|
||||
listenAddress = addr
|
||||
}
|
||||
|
||||
maxOutgoingPeers := uint64(10)
|
||||
if outgoingPeers, ok := settings["out-peers"]; ok {
|
||||
maxOutgoingPeers, _ = strconv.ParseUint(outgoingPeers, 10, 0)
|
||||
}
|
||||
|
||||
maxIncomingPeers := uint64(450)
|
||||
if incomingPeers, ok := settings["in-peers"]; ok {
|
||||
maxIncomingPeers, _ = strconv.ParseUint(incomingPeers, 10, 0)
|
||||
}
|
||||
|
||||
externalListenPort := uint64(0)
|
||||
if externalPort, ok := settings["external-port"]; ok {
|
||||
externalListenPort, _ = strconv.ParseUint(externalPort, 10, 0)
|
||||
}
|
||||
|
||||
useIPv4, useIPv6 := true, true
|
||||
if b, ok := settings["ipv6-only"]; ok && b == "true" {
|
||||
useIPv4 = false
|
||||
useIPv6 = true
|
||||
}
|
||||
|
||||
if pool.server, err = p2p.NewServer(pool, listenAddress, uint16(externalListenPort), uint32(maxOutgoingPeers), uint32(maxIncomingPeers), useIPv4, useIPv6, pool.ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (p *P2Pool) GetChainMainByHash(hash types.Hash) *sidechain.ChainMain {
|
||||
return p.mainchain.GetChainMainByHash(hash)
|
||||
}
|
||||
|
||||
func (p *P2Pool) GetChainMainByHeight(height uint64) *sidechain.ChainMain {
|
||||
return p.mainchain.GetChainMainByHeight(height)
|
||||
}
|
||||
|
||||
func (p *P2Pool) GetChainMainTip() *sidechain.ChainMain {
|
||||
return p.mainchain.GetChainMainTip()
|
||||
}
|
||||
|
||||
func (p *P2Pool) GetMinerDataTip() *p2pooltypes.MinerData {
|
||||
return p.mainchain.GetMinerDataTip()
|
||||
}
|
||||
|
||||
// GetMinimalBlockHeaderByHeight Only Id / Height / Timestamp are assured
|
||||
func (p *P2Pool) GetMinimalBlockHeaderByHeight(height uint64) *block.Header {
|
||||
lowerThanTip := height <= p.mainchain.GetChainMainTip().Height
|
||||
if chainMain := p.mainchain.GetChainMainByHeight(height); chainMain != nil && chainMain.Id != types.ZeroHash {
|
||||
prev := p.mainchain.GetChainMainByHeight(height - 1)
|
||||
if prev != nil {
|
||||
return &block.Header{
|
||||
Timestamp: chainMain.Timestamp,
|
||||
Height: chainMain.Height,
|
||||
Reward: chainMain.Reward,
|
||||
Difficulty: chainMain.Difficulty,
|
||||
Id: chainMain.Id,
|
||||
PreviousId: prev.Id,
|
||||
}
|
||||
} else {
|
||||
return &block.Header{
|
||||
Timestamp: chainMain.Timestamp,
|
||||
Height: chainMain.Height,
|
||||
Reward: chainMain.Reward,
|
||||
Difficulty: chainMain.Difficulty,
|
||||
Id: chainMain.Id,
|
||||
}
|
||||
}
|
||||
} else if lowerThanTip {
|
||||
if header, err := p.ClientRPC().GetBlockHeaderByHeight(height, p.ctx); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeader.PrevHash)
|
||||
h, _ := types.HashFromString(header.BlockHeader.Hash)
|
||||
blockHeader := &block.Header{
|
||||
MajorVersion: uint8(header.BlockHeader.MajorVersion),
|
||||
MinorVersion: uint8(header.BlockHeader.MinorVersion),
|
||||
Timestamp: uint64(header.BlockHeader.Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.BlockHeader.Height,
|
||||
Nonce: uint32(header.BlockHeader.Nonce),
|
||||
Reward: header.BlockHeader.Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(header.BlockHeader.Difficulty),
|
||||
}
|
||||
return blockHeader
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (p *P2Pool) GetDifficultyByHeight(height uint64) types.Difficulty {
|
||||
lowerThanTip := height <= p.mainchain.GetChainMainTip().Height
|
||||
if chainMain := p.mainchain.GetChainMainByHeight(height); chainMain != nil && chainMain.Difficulty != types.ZeroDifficulty {
|
||||
return chainMain.Difficulty
|
||||
} else if lowerThanTip {
|
||||
//TODO cache
|
||||
if header, err := p.ClientRPC().GetBlockHeaderByHeight(height, p.ctx); err != nil {
|
||||
return types.ZeroDifficulty
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.BlockHeader.PrevHash)
|
||||
h, _ := types.HashFromString(header.BlockHeader.Hash)
|
||||
blockHeader := &block.Header{
|
||||
MajorVersion: uint8(header.BlockHeader.MajorVersion),
|
||||
MinorVersion: uint8(header.BlockHeader.MinorVersion),
|
||||
Timestamp: uint64(header.BlockHeader.Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.BlockHeader.Height,
|
||||
Nonce: uint32(header.BlockHeader.Nonce),
|
||||
Reward: header.BlockHeader.Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(header.BlockHeader.Difficulty),
|
||||
}
|
||||
return blockHeader.Difficulty
|
||||
}
|
||||
}
|
||||
|
||||
return types.ZeroDifficulty
|
||||
}
|
||||
|
||||
// GetMinimalBlockHeaderByHash Only Id / Height / Timestamp are assured
|
||||
func (p *P2Pool) GetMinimalBlockHeaderByHash(hash types.Hash) *block.Header {
|
||||
if chainMain := p.mainchain.GetChainMainByHash(hash); chainMain != nil && chainMain.Id != types.ZeroHash {
|
||||
return &block.Header{
|
||||
Timestamp: chainMain.Timestamp,
|
||||
Height: chainMain.Height,
|
||||
Reward: chainMain.Reward,
|
||||
Difficulty: chainMain.Difficulty,
|
||||
Id: chainMain.Id,
|
||||
}
|
||||
} else {
|
||||
if header, err := p.ClientRPC().GetBlockHeaderByHash(hash, p.ctx); err != nil || header == nil {
|
||||
return nil
|
||||
} else {
|
||||
prevHash, _ := types.HashFromString(header.PrevHash)
|
||||
h, _ := types.HashFromString(header.Hash)
|
||||
blockHeader := &block.Header{
|
||||
MajorVersion: uint8(header.MajorVersion),
|
||||
MinorVersion: uint8(header.MinorVersion),
|
||||
Timestamp: uint64(header.Timestamp),
|
||||
PreviousId: prevHash,
|
||||
Height: header.Height,
|
||||
Nonce: uint32(header.Nonce),
|
||||
Reward: header.Reward,
|
||||
Id: h,
|
||||
Difficulty: types.DifficultyFrom64(header.Difficulty),
|
||||
}
|
||||
return blockHeader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) ClientRPC() *client.Client {
|
||||
return p.rpcClient
|
||||
}
|
||||
|
||||
func (p *P2Pool) ClientZMQ() *zmq.Client {
|
||||
return p.zmqClient
|
||||
}
|
||||
|
||||
func (p *P2Pool) Context() context.Context {
|
||||
return p.ctx
|
||||
}
|
||||
|
||||
func (p *P2Pool) SideChain() *sidechain.SideChain {
|
||||
return p.sidechain
|
||||
}
|
||||
|
||||
func (p *P2Pool) MainChain() *mainchain.MainChain {
|
||||
return p.mainchain
|
||||
}
|
||||
|
||||
func (p *P2Pool) Server() *p2p.Server {
|
||||
return p.server
|
||||
}
|
||||
|
||||
func (p *P2Pool) Run() (err error) {
|
||||
|
||||
if err = p.getInfo(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.getVersion(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.getInfo(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = p.getMinerData(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
//TODO: redo listen
|
||||
if err := p.mainchain.Listen(); err != nil {
|
||||
p.Close(err)
|
||||
}
|
||||
}()
|
||||
|
||||
//TODO: move peer list loading here
|
||||
|
||||
if p.cache != nil {
|
||||
p.cache.LoadAll(p.Server())
|
||||
}
|
||||
|
||||
p.started.Store(true)
|
||||
|
||||
if err = p.Server().Listen(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P2Pool) getInfo() error {
|
||||
if info, err := p.ClientRPC().GetInfo(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if info.BusySyncing {
|
||||
utils.Logf("P2Pool", "monerod is busy syncing, trying again in 5 seconds")
|
||||
time.Sleep(time.Second * 5)
|
||||
return p.getInfo()
|
||||
} else if !info.Synchronized {
|
||||
utils.Logf("P2Pool", "monerod is not synchronized, trying again in 5 seconds")
|
||||
time.Sleep(time.Second * 5)
|
||||
return p.getInfo()
|
||||
}
|
||||
|
||||
networkType := sidechain.NetworkInvalid
|
||||
if info.Mainnet {
|
||||
networkType = sidechain.NetworkMainnet
|
||||
} else if info.Testnet {
|
||||
networkType = sidechain.NetworkTestnet
|
||||
} else if info.Stagenet {
|
||||
networkType = sidechain.NetworkStagenet
|
||||
}
|
||||
|
||||
if p.sidechain.Consensus().NetworkType != networkType {
|
||||
return fmt.Errorf("monerod is on %d, but you're mining to a %d sidechain", networkType, p.sidechain.Consensus().NetworkType)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P2Pool) getVersion() error {
|
||||
if version, err := p.ClientRPC().GetVersion(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if version.Version < monero.RequiredMoneroVersion {
|
||||
return fmt.Errorf("monerod RPC v%d.%d is incompatible, update to RPC >= v%d.%d (Monero %s or newer)", version.Version>>16, version.Version&0xffff, monero.RequiredMajor, monero.RequiredMinor, monero.RequiredMoneroString)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *P2Pool) getMinerData() error {
|
||||
if minerData, err := p.ClientRPC().GetMinerData(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
prevId, _ := types.HashFromString(minerData.PrevId)
|
||||
seedHash, _ := types.HashFromString(minerData.SeedHash)
|
||||
diff, _ := types.DifficultyFromString(minerData.Difficulty)
|
||||
|
||||
data := &p2pooltypes.MinerData{
|
||||
MajorVersion: minerData.MajorVersion,
|
||||
Height: minerData.Height,
|
||||
PrevId: prevId,
|
||||
SeedHash: seedHash,
|
||||
Difficulty: diff,
|
||||
MedianWeight: minerData.MedianWeight,
|
||||
AlreadyGeneratedCoins: minerData.AlreadyGeneratedCoins,
|
||||
MedianTimestamp: minerData.MedianTimestamp,
|
||||
TimeReceived: time.Now(),
|
||||
}
|
||||
data.TxBacklog = make(mempool.Mempool, len(minerData.TxBacklog))
|
||||
for i, e := range minerData.TxBacklog {
|
||||
txId, _ := types.HashFromString(e.Id)
|
||||
|
||||
data.TxBacklog[i] = &mempool.MempoolEntry{
|
||||
Id: txId,
|
||||
BlobSize: e.BlobSize,
|
||||
Weight: e.Weight,
|
||||
Fee: e.Fee,
|
||||
}
|
||||
}
|
||||
p.mainchain.HandleMinerData(data)
|
||||
|
||||
return p.mainchain.DownloadBlockHeaders(minerData.Height)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) Consensus() *sidechain.Consensus {
|
||||
return p.consensus
|
||||
}
|
||||
|
||||
func (p *P2Pool) UpdateMainData(data *sidechain.ChainMain) {
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
for i := range p.listeners {
|
||||
if p.listeners[i].MainData != nil {
|
||||
p.listeners[i].MainData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) UpdateMempoolData(data mempool.Mempool) {
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
for i := range p.listeners {
|
||||
if p.listeners[i].MempoolData != nil {
|
||||
p.listeners[i].MempoolData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) UpdateMinerData(data *p2pooltypes.MinerData) {
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
for i := range p.listeners {
|
||||
if p.listeners[i].MinerData != nil {
|
||||
p.listeners[i].MinerData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) UpdateBlockFound(data *sidechain.ChainMain, block *sidechain.PoolBlock) {
|
||||
utils.Logf("P2Pool", "BLOCK FOUND: main chain block at height %d, id %s was mined by this p2pool", data.Height, data.Id)
|
||||
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
for i := range p.listeners {
|
||||
if p.listeners[i].Found != nil {
|
||||
p.listeners[i].Found(data, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) SubmitBlock(b *block.Block) {
|
||||
|
||||
go func() {
|
||||
//do not submit multiple monero blocks to monerod
|
||||
if !p.recentSubmittedBlocks.PushUnique(b.Id()) {
|
||||
return
|
||||
}
|
||||
|
||||
if blob, err := b.MarshalBinary(); err == nil {
|
||||
var templateId types.Hash
|
||||
var extraNonce uint32
|
||||
if t := b.Coinbase.Extra.GetTag(transaction.TxExtraTagMergeMining); t != nil {
|
||||
templateId = types.HashFromBytes(t.Data)
|
||||
}
|
||||
if t := b.Coinbase.Extra.GetTag(transaction.TxExtraTagNonce); t != nil {
|
||||
extraNonce = binary.LittleEndian.Uint32(t.Data)
|
||||
}
|
||||
utils.Logf("P2Pool", "submit_block: height = %d, template id = %s, nonce = %d, extra_nonce = %d, blob = %d bytes", b.Coinbase.GenHeight, templateId.String(), b.Nonce, extraNonce, len(blob))
|
||||
|
||||
if result, err := p.ClientRPC().SubmitBlock(blob); err != nil {
|
||||
utils.Logf("P2Pool", "submit_block: daemon returned error: %s, height = %d, template id = %s, nonce = %d, extra_nonce = %d", err, b.Coinbase.GenHeight, templateId.String(), b.Nonce, extraNonce)
|
||||
} else {
|
||||
if result.Status == "OK" {
|
||||
utils.Logf("P2Pool", "submit_block: BLOCK ACCEPTED at height = %d, template id = %s", b.Coinbase.GenHeight, templateId.String())
|
||||
} else {
|
||||
utils.Logf("P2Pool", "submit_block: daemon sent unrecognizable reply: %s, height = %d, template id = %s, nonce = %d, extra_nonce = %d", result.Status, b.Coinbase.GenHeight, templateId.String(), b.Nonce, extraNonce)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *P2Pool) Store(block *sidechain.PoolBlock) {
|
||||
if p.cache != nil {
|
||||
p.cache.Store(block)
|
||||
}
|
||||
if p.archive != nil {
|
||||
p.archive.Store(block)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) ClearCachedBlocks() {
|
||||
p.server.ClearCachedBlocks()
|
||||
}
|
||||
|
||||
func (p *P2Pool) Started() bool {
|
||||
return p.started.Load()
|
||||
}
|
||||
|
||||
func (p *P2Pool) UpdateTip(tip *sidechain.PoolBlock) {
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
for i := range p.listeners {
|
||||
if p.listeners[i].Tip != nil {
|
||||
p.listeners[i].Tip(tip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *P2Pool) Broadcast(block *sidechain.PoolBlock) {
|
||||
minerData := p.GetMinerDataTip()
|
||||
if (block.Main.Coinbase.GenHeight)+2 < minerData.Height {
|
||||
utils.Logf("P2Pool", "Trying to broadcast a stale block %s (mainchain height %d, current height is %d)", block.SideTemplateId(p.consensus), block.Main.Coinbase.GenHeight, minerData.Height)
|
||||
return
|
||||
}
|
||||
|
||||
if block.Main.Coinbase.GenHeight > (minerData.Height + 2) {
|
||||
utils.Logf("P2Pool", "Trying to broadcast a block %s ahead on mainchain (mainchain height %d, current height is %d)", block.SideTemplateId(p.consensus), block.Main.Coinbase.GenHeight, minerData.Height)
|
||||
return
|
||||
}
|
||||
|
||||
p.server.Broadcast(block)
|
||||
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
for i := range p.listeners {
|
||||
if p.listeners[i].Broadcast != nil {
|
||||
p.listeners[i].Broadcast(block)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/readcache
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0 => ../../p2pool/cache
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/floatdrop/lru v1.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,44 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,67 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache/legacy"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type loadee struct {
|
||||
c *sidechain.Consensus
|
||||
cb func(block *sidechain.PoolBlock)
|
||||
}
|
||||
|
||||
func (l *loadee) Consensus() *sidechain.Consensus {
|
||||
return l.c
|
||||
}
|
||||
|
||||
func (l *loadee) AddCachedBlock(block *sidechain.PoolBlock) {
|
||||
l.cb(block)
|
||||
}
|
||||
|
||||
func main() {
|
||||
inputConsensus := flag.String("consensus", "config.json", "Input config.json consensus file")
|
||||
inputFile := flag.String("input", "p2pool.cache", "Input p2pool.cache path")
|
||||
outputFolder := flag.String("output", "shares", "Output path for extracted shares")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
cf, err := os.ReadFile(*inputConsensus)
|
||||
|
||||
consensus, err := sidechain.NewConsensusFromJSON(cf)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
cache, err := legacy.NewCache(consensus, *inputFile)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer cache.Close()
|
||||
|
||||
l := &loadee{
|
||||
c: consensus,
|
||||
cb: func(block *sidechain.PoolBlock) {
|
||||
expectedBlockId := types.HashFromBytes(block.CoinbaseExtra(sidechain.SideTemplateId))
|
||||
calculatedBlockId := block.SideTemplateId(consensus)
|
||||
|
||||
if expectedBlockId != calculatedBlockId {
|
||||
utils.Errorf("", "block height %d, template id %s, expected %s", block.Side.Height, calculatedBlockId, expectedBlockId)
|
||||
} else {
|
||||
blob, err := block.MarshalBinary()
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
utils.Logf("", "block height %d, template id %s, version %d", block.Side.Height, calculatedBlockId, block.ShareVersion())
|
||||
|
||||
_ = os.WriteFile(path.Join(*outputFolder, expectedBlockId.String()+".raw"), blob, 0664)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cache.LoadAll(l)
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/recoverpoolblock
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0 => ../../p2pool/cache
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/cache v0.0.0
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a
|
||||
github.com/floatdrop/lru v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,47 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,517 +0,0 @@
|
|||
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"
|
||||
"git.gammaspectra.live/P2Pool/sha3"
|
||||
"github.com/floatdrop/lru"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"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 {
|
||||
utils.Panic(err)
|
||||
}
|
||||
inputTemplateData, err := os.ReadFile(*inputTemplate)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
jsonTemplate, err := JSONFromTemplate(inputTemplateData)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
consensus, err := sidechain.NewConsensusFromJSON(cf)
|
||||
if err != nil {
|
||||
utils.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 {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer archiveCache.Close()
|
||||
|
||||
blockCache := lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4))
|
||||
|
||||
derivationCache := sidechain.NewDerivationLRUCache()
|
||||
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 {
|
||||
utils.Errorf("", "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 {
|
||||
utils.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
|
||||
}
|
||||
|
||||
utils.Logf("", "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,
|
||||
VarInt: 0,
|
||||
Data: types.Bytes(keyPair.PublicKey.AsSlice()),
|
||||
},
|
||||
transaction.ExtraTag{
|
||||
Tag: transaction.TxExtraTagNonce,
|
||||
VarInt: 4,
|
||||
HasVarInt: true,
|
||||
Data: make(types.Bytes, 4), //TODO: expand nonce size as needed
|
||||
},
|
||||
transaction.ExtraTag{
|
||||
Tag: transaction.TxExtraTagMergeMining,
|
||||
VarInt: 32,
|
||||
HasVarInt: true,
|
||||
Data: v2.Id[:],
|
||||
},
|
||||
},
|
||||
ExtraBaseRCT: 0,
|
||||
},
|
||||
Transactions: nil,
|
||||
},
|
||||
Side: sidechain.SideData{
|
||||
PublicKey: v2.Wallet.ToPackedAddress(),
|
||||
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
|
||||
},
|
||||
}
|
||||
poolBlock.CachedShareVersion = poolBlock.CalculateShareVersion(consensus)
|
||||
}
|
||||
|
||||
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 {
|
||||
utils.Logf("", "not deterministic private key")
|
||||
}
|
||||
}
|
||||
|
||||
currentOutputs, _ := sidechain.CalculateOutputs(poolBlock, consensus, getDifficultyByHeight, getByTemplateIdDirect, derivationCache, sidechain.PreAllocateShares(consensus.ChainWindowSize*2), make([]uint64, consensus.ChainWindowSize*2))
|
||||
|
||||
if currentOutputs == nil {
|
||||
utils.Panicf("could not calculate outputs blob")
|
||||
}
|
||||
poolBlock.Main.Coinbase.Outputs = currentOutputs
|
||||
|
||||
if blob, err := currentOutputs.MarshalBinary(); err != nil {
|
||||
utils.Panic(err)
|
||||
} else {
|
||||
poolBlock.Main.Coinbase.OutputsBlobSize = uint64(len(blob))
|
||||
}
|
||||
utils.Logf("", "expected main id %s, template id %s, coinbase id %s", expectedMainId, expectedTemplateId, expectedCoinbaseId)
|
||||
|
||||
rctHash := crypto.Keccak256([]byte{0})
|
||||
type partialBlobWork struct {
|
||||
Hashers [2]*sha3.HasherState
|
||||
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 {
|
||||
utils.Logf("", "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].VarInt = uint64(nonceSize)
|
||||
tx.Extra[1].Data = make([]byte, nonceSize)
|
||||
|
||||
buf, _ := tx.MarshalBinary()
|
||||
|
||||
coinbases[routineIndex] = &partialBlobWork{
|
||||
Hashers: [2]*sha3.HasherState{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
|
||||
}, 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)
|
||||
}
|
||||
|
||||
utils.Logf("", "found extra nonce %d, size %d", foundExtraNonce.Load(), foundExtraNonceSize.Load())
|
||||
poolBlock.Main.Coinbase.Extra[1].VarInt = 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 {
|
||||
utils.Panic()
|
||||
}
|
||||
|
||||
utils.Logf("", "got coinbase id %s", poolBlock.Main.Coinbase.CalculateId())
|
||||
minerTxBlob, _ = poolBlock.Main.Coinbase.MarshalBinary()
|
||||
utils.Logf("", "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
|
||||
|
||||
utils.Logf("", "collected transaction candidates: %d", len(collectedTransactions))
|
||||
|
||||
if totalTxWeight+minerTxWeight <= medianWeight {
|
||||
//special case
|
||||
for solution := range collectedTransactions.PerfectSum(deltaReward) {
|
||||
utils.Logf("", "got %d, %d (%s)", solution.Weight(), solution.Fees(), utils.XMRUnits(solution.Fees()))
|
||||
}
|
||||
} else {
|
||||
//sort in preference order
|
||||
pickedTxs := collectedTransactions.Pick(baseMainReward, minerTxWeight, medianWeight)
|
||||
|
||||
utils.Logf("", "got %d, %d (%s)", pickedTxs.Weight(), pickedTxs.Fees(), utils.XMRUnits(pickedTxs.Fees()))
|
||||
}
|
||||
|
||||
//TODO: nonce
|
||||
}
|
||||
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
)
|
||||
|
||||
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 := utils.UnmarshalJSON(data, &version); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if version.Version == 2 {
|
||||
var b JsonBlock2
|
||||
if err = utils.UnmarshalJSON(data, &b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
} else if version.Version == 0 || version.Version == 1 {
|
||||
|
||||
var b JsonBlock1
|
||||
if err = utils.UnmarshalJSON(data, &b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown version %d", version.Version)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/scansweeps
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0 => ../index
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0 => ../utils
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/floatdrop/lru v1.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,46 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,84 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
p2poolapi "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/api"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 sweep from this main height")
|
||||
dbString := flag.String("db", "", "")
|
||||
p2poolApiHost := flag.String("api-host", "", "Host URL for p2pool go observer consensus")
|
||||
flag.Parse()
|
||||
|
||||
client.SetDefaultClientSettings(fmt.Sprintf("http://%s:%d", *moneroHost, *moneroRpcPort))
|
||||
|
||||
client.GetDefaultClient().SetThrottle(1000)
|
||||
|
||||
p2api := p2poolapi.NewP2PoolApi(*p2poolApiHost)
|
||||
|
||||
if err := p2api.WaitSync(); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
indexDb, err := index.OpenIndex(*dbString, p2api.Consensus(), p2api.DifficultyByHeight, p2api.SeedByHeight, p2api.ByTemplateId)
|
||||
if err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
defer indexDb.Close()
|
||||
|
||||
startHeight := *startFromHeight
|
||||
|
||||
var tipHeight uint64
|
||||
|
||||
if err := indexDb.Query("SELECT MIN(height), MAX(height) FROM main_blocks WHERE height > 0;", func(row index.RowScanInterface) error {
|
||||
var minHeight uint64
|
||||
if err := row.Scan(&minHeight, &tipHeight); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if minHeight > startHeight {
|
||||
startHeight = minHeight
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
|
||||
stopHeight := tipHeight - monero.MinerRewardUnlockTime
|
||||
if tipHeight < monero.MinerRewardUnlockTime {
|
||||
stopHeight = tipHeight
|
||||
}
|
||||
|
||||
utils.Logf("", "Starting at height %d, stopping at %d", startHeight, stopHeight)
|
||||
|
||||
isProcessedPrevious := true
|
||||
for height := startHeight; height <= stopHeight; height++ {
|
||||
b := indexDb.GetMainBlockByHeight(height)
|
||||
if b == nil {
|
||||
utils.Logf("", "Block at %d is nil", height)
|
||||
continue
|
||||
}
|
||||
|
||||
if isProcessed, ok := b.GetMetadata("processed").(bool); ok && isProcessed && isProcessedPrevious {
|
||||
utils.Logf("", "Block %s at %d (%s) has already been processed", b.Id, b.Height, time.Unix(int64(b.Timestamp), 0).UTC().Format("02-01-2006 15:04:05 MST"))
|
||||
continue
|
||||
}
|
||||
|
||||
isProcessedPrevious = false
|
||||
|
||||
if err := cmdutils.ProcessFullBlock(b, indexDb); err != nil {
|
||||
utils.Logf("", "error processing block %s at %d: %s", b.Id, b.Height, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
types2 "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
JSONEventSideBlock = "side_block"
|
||||
JSONEventFoundBlock = "found_block"
|
||||
JSONEventOrphanedBlock = "orphaned_block"
|
||||
JSONEventPayout = "payout"
|
||||
)
|
||||
|
||||
type JSONEvent struct {
|
||||
Type string `json:"type"`
|
||||
SideBlock *index.SideBlock `json:"side_block,omitempty"`
|
||||
FoundBlock *index.FoundBlock `json:"found_block,omitempty"`
|
||||
MainCoinbaseOutputs index.MainCoinbaseOutputs `json:"main_coinbase_outputs,omitempty"`
|
||||
Payout *index.Payout `json:"payout,omitempty"`
|
||||
}
|
||||
|
||||
type VersionInfo struct {
|
||||
Version string `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Link string `json:"link"`
|
||||
CheckedTimestamp int64 `json:"-"`
|
||||
}
|
||||
|
||||
func (v VersionInfo) ShortVersion() types2.SemanticVersion {
|
||||
parts := strings.Split(v.Version, ".")
|
||||
for len(parts) < 2 {
|
||||
parts = append(parts, "0")
|
||||
}
|
||||
for len(parts) > 2 {
|
||||
parts = parts[:len(parts)-1]
|
||||
}
|
||||
return types2.SemanticVersionFromString(strings.Join(parts, "."))
|
||||
}
|
||||
|
||||
type ReleaseDataJson struct {
|
||||
TagName string `json:"tag_name"`
|
||||
TargetCommitish string `json:"target_commitish"`
|
||||
Name string `json:"name"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
}
|
||||
|
||||
type SideChainVersionEntry struct {
|
||||
Weight types.Difficulty `json:"weight"`
|
||||
Share float64 `json:"share"`
|
||||
Count int `json:"count"`
|
||||
SoftwareId types2.SoftwareId `json:"software_id"`
|
||||
SoftwareVersion types2.SoftwareVersion `json:"software_version"`
|
||||
SoftwareString string `json:"software_string"`
|
||||
}
|
||||
|
||||
type PoolInfoResult struct {
|
||||
SideChain PoolInfoResultSideChain `json:"sidechain"`
|
||||
MainChain PoolInfoResultMainChain `json:"mainchain"`
|
||||
Versions struct {
|
||||
P2Pool VersionInfo `json:"p2pool"`
|
||||
Monero VersionInfo `json:"monero"`
|
||||
} `json:"versions"`
|
||||
}
|
||||
|
||||
type PoolInfoResultSideChain struct {
|
||||
// Consensus Specifies the consensus parameters for the backing p2pool instance
|
||||
Consensus *sidechain.Consensus `json:"consensus"`
|
||||
|
||||
// LastBlock Last sidechain block on database
|
||||
LastBlock *index.SideBlock `json:"last_block"`
|
||||
|
||||
// SecondsSinceLastBlock
|
||||
// Prefer using max(0, time.Now().Unix()-int64(LastBlock .Timestamp)) instead
|
||||
SecondsSinceLastBlock int64 `json:"seconds_since_last_block"`
|
||||
|
||||
// LastFound Last sidechain block on database found and accepted on Monero network
|
||||
LastFound *index.FoundBlock `json:"last_found"`
|
||||
|
||||
Effort PoolInfoResultSideChainEffort `json:"effort"`
|
||||
Window PoolInfoResultSideChainWindow `json:"window"`
|
||||
|
||||
// Found Total count of found blocks in database
|
||||
Found uint64 `json:"found"`
|
||||
// Miners Total count of miners in database
|
||||
Miners uint64 `json:"miners"`
|
||||
|
||||
// Id Available on LastBlock .TemplateId
|
||||
// Deprecated
|
||||
Id types.Hash `json:"id"`
|
||||
|
||||
// Height Available on LastBlock .SideHeight
|
||||
// Deprecated
|
||||
Height uint64 `json:"height"`
|
||||
|
||||
// Version Available via sidechain.P2PoolShareVersion
|
||||
// Deprecated
|
||||
Version sidechain.ShareVersion `json:"version"`
|
||||
|
||||
// Difficulty Available on LastBlock .Difficulty
|
||||
// Deprecated
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
|
||||
// CumulativeDifficulty Available on LastBlock .CumulativeDifficulty
|
||||
// Deprecated
|
||||
CumulativeDifficulty types.Difficulty `json:"cumulative_difficulty"`
|
||||
|
||||
// Timestamp Available on LastBlock .Timestamp
|
||||
// Deprecated
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
|
||||
// WindowSize Available on Window .Blocks
|
||||
// Deprecated
|
||||
WindowSize int `json:"window_size"`
|
||||
|
||||
// MaxWindowSize Available on Consensus
|
||||
// Deprecated
|
||||
MaxWindowSize int `json:"max_window_size"`
|
||||
|
||||
// BlockTime Available on Consensus
|
||||
// Deprecated
|
||||
BlockTime int `json:"block_time"`
|
||||
|
||||
// UnclePenalty Available on Consensus
|
||||
// Deprecated
|
||||
UnclePenalty int `json:"uncle_penalty"`
|
||||
}
|
||||
|
||||
type PoolInfoResultSideChainEffortLastEntry struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Effort float64 `json:"effort"`
|
||||
}
|
||||
|
||||
type PoolInfoResultSideChainEffort struct {
|
||||
Current float64 `json:"current"`
|
||||
Average10 float64 `json:"average10"`
|
||||
Average50 float64 `json:"average"`
|
||||
Average200 float64 `json:"average200"`
|
||||
Last []PoolInfoResultSideChainEffortLastEntry `json:"last"`
|
||||
}
|
||||
type PoolInfoResultSideChainWindow struct {
|
||||
// Miners Unique miners found in window
|
||||
Miners int `json:"miners"`
|
||||
// Blocks total count of blocks in the window, including uncles
|
||||
Blocks int `json:"blocks"`
|
||||
Uncles int `json:"uncles"`
|
||||
|
||||
// Top TemplateId of the window tip
|
||||
Top types.Hash `json:"top"`
|
||||
// Bottom TemplateId of the last non-uncle block in the window
|
||||
Bottom types.Hash `json:"bottom"`
|
||||
|
||||
// BottomUncles TemplateId of the uncles included under the last block, if any
|
||||
BottomUncles []types.Hash `json:"bottom_uncles,omitempty"`
|
||||
|
||||
Weight types.Difficulty `json:"weight"`
|
||||
Versions []SideChainVersionEntry `json:"versions"`
|
||||
}
|
||||
|
||||
type PoolInfoResultMainChainConsensus struct {
|
||||
BlockTime uint64 `json:"block_time"`
|
||||
TransactionUnlockTime uint64 `json:"transaction_unlock_time"`
|
||||
MinerRewardUnlockTime uint64 `json:"miner_reward_unlock_time"`
|
||||
|
||||
// HardForkSupportedVersion
|
||||
HardForkSupportedVersion uint8 `json:"hard_fork_supported_version"`
|
||||
// HardForks HardFork information for Monero known hardfork by backing p2pool
|
||||
HardForks []sidechain.HardFork `json:"hard_forks,omitempty"`
|
||||
}
|
||||
|
||||
type PoolInfoResultMainChain struct {
|
||||
Consensus PoolInfoResultMainChainConsensus `json:"consensus"`
|
||||
Id types.Hash `json:"id"`
|
||||
CoinbaseId types.Hash `json:"coinbase_id"`
|
||||
Height uint64 `json:"height"`
|
||||
Difficulty types.Difficulty `json:"difficulty"`
|
||||
Reward uint64 `json:"reward"`
|
||||
BaseReward uint64 `json:"base_reward"`
|
||||
|
||||
NextDifficulty types.Difficulty `json:"next_difficulty"`
|
||||
|
||||
// BlockTime included in Consensus
|
||||
// Deprecated
|
||||
BlockTime int `json:"block_time"`
|
||||
}
|
||||
|
||||
type MinerInfoBlockData struct {
|
||||
ShareCount uint64 `json:"shares"`
|
||||
UncleCount uint64 `json:"uncles"`
|
||||
LastShareHeight uint64 `json:"last_height"`
|
||||
}
|
||||
|
||||
type MinerInfoResult struct {
|
||||
Id uint64 `json:"id"`
|
||||
Address *address.Address `json:"address"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Shares [index.InclusionCount]MinerInfoBlockData `json:"shares"`
|
||||
LastShareHeight uint64 `json:"last_share_height"`
|
||||
LastShareTimestamp uint64 `json:"last_share_timestamp"`
|
||||
}
|
||||
|
||||
type TransactionLookupResult struct {
|
||||
Id types.Hash `json:"id"`
|
||||
Inputs index.TransactionInputQueryResults `json:"inputs"`
|
||||
Outs []client.Output `json:"outs"`
|
||||
Match []index.TransactionInputQueryResultsMatch `json:"matches"`
|
||||
}
|
|
@ -1,302 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/go-monero/pkg/rpc/daemon"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
"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/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"github.com/floatdrop/lru"
|
||||
"math"
|
||||
)
|
||||
|
||||
// keep last few main blocks
|
||||
var mainBlockCache = lru.New[types.Hash, *block.Block](100)
|
||||
|
||||
func GetMainBlock(id types.Hash, client *client.Client) (*block.Block, error) {
|
||||
if bb := mainBlockCache.Get(id); bb == nil {
|
||||
blockData, err := client.GetBlock(id, context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bufData, err := hex.DecodeString(blockData.Blob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mainBlock := &block.Block{}
|
||||
err = mainBlock.UnmarshalBinary(bufData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(mainBlock.Coinbase.Outputs) == 0 {
|
||||
//should never happen from monero itself
|
||||
return nil, errors.New("outputs not filled")
|
||||
}
|
||||
mainBlockCache.Set(id, mainBlock)
|
||||
return mainBlock, nil
|
||||
} else {
|
||||
return *bb, nil
|
||||
}
|
||||
}
|
||||
|
||||
func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFunc func(b *sidechain.PoolBlock), client *client.Client, difficultyByHeight block.GetDifficultyByHeightFunc, getByTemplateId sidechain.GetByTemplateIdFunc, getByMainId sidechain.GetByMainIdFunc, getByMainHeight sidechain.GetByMainHeightFunc, processBlock func(b *sidechain.PoolBlock) error) error {
|
||||
mainId, _ := types.HashFromString(h.Hash)
|
||||
coinbaseId, _ := types.HashFromString(h.MinerTxHash)
|
||||
prevHash, _ := types.HashFromString(h.PrevHash)
|
||||
|
||||
var indexMain *index.MainBlock
|
||||
indexMain = indexDb.GetMainBlockByHeight(h.Height)
|
||||
if indexMain == nil {
|
||||
indexMain = &index.MainBlock{}
|
||||
}
|
||||
if indexMain.Id != mainId {
|
||||
//Zero data if they don't match to insert new height
|
||||
indexMain.Metadata = make(map[string]any)
|
||||
indexMain.SideTemplateId = types.ZeroHash
|
||||
indexMain.CoinbasePrivateKey = crypto.PrivateKeyBytes{}
|
||||
}
|
||||
indexMain.Id = mainId
|
||||
indexMain.Height = h.Height
|
||||
indexMain.Timestamp = uint64(h.Timestamp)
|
||||
indexMain.Reward = h.Reward
|
||||
indexMain.CoinbaseId = coinbaseId
|
||||
indexMain.Difficulty = h.Difficulty
|
||||
|
||||
// Find possible candidates that match on main height, main previous id, header nonce, timestamp, and count of transactions to reduce full block lookups
|
||||
var candidates sidechain.UniquePoolBlockSlice
|
||||
for _, b := range getByMainHeight(h.Height) {
|
||||
if b.Main.PreviousId == prevHash && b.Main.Coinbase.TotalReward == h.Reward && b.Main.Timestamp == uint64(h.Timestamp) && len(b.Main.Transactions) == int(h.NumTxes) {
|
||||
// candidate for checking
|
||||
candidates = append(candidates, b)
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidates) == 0 {
|
||||
// no candidates, insert header as-is
|
||||
if err := indexDb.InsertOrUpdateMainBlock(indexMain); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
utils.Logf("Monero", "checking block %s at height %d: %d candidate(s)", mainId, h.Height, len(candidates))
|
||||
|
||||
mainBlock, err := GetMainBlock(mainId, client)
|
||||
if err != nil {
|
||||
utils.Errorf("Monero", "could not get main block: %e", err)
|
||||
// insert errored block as-is
|
||||
if err := indexDb.InsertOrUpdateMainBlock(indexMain); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var sideTemplateId types.Hash
|
||||
var extraNonce []byte
|
||||
|
||||
// try to fetch extra nonce if existing
|
||||
{
|
||||
if t := mainBlock.Coinbase.Extra.GetTag(uint8(sidechain.SideExtraNonce)); t != nil {
|
||||
if len(t.Data) >= sidechain.SideExtraNonceSize || len(t.Data) < sidechain.SideExtraNonceMaxSize {
|
||||
extraNonce = t.Data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get a merge tag if existing
|
||||
{
|
||||
if t := mainBlock.Coinbase.Extra.GetTag(uint8(sidechain.SideTemplateId)); t != nil {
|
||||
if len(t.Data) == types.HashSize {
|
||||
sideTemplateId = types.HashFromBytes(t.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sideTemplateId == types.ZeroHash || len(extraNonce) == 0 {
|
||||
utils.Logf("Monero", "checking block %s at height %d: not a p2pool block", mainId, h.Height)
|
||||
if err := indexDb.InsertOrUpdateMainBlock(indexMain); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if t := getByTemplateId(sideTemplateId); t != nil {
|
||||
//found template
|
||||
if err := processBlock(t); err != nil {
|
||||
return fmt.Errorf("could not process template: %w", err)
|
||||
}
|
||||
|
||||
if t.MainId() == mainId { //exact match, no need to re-arrange template inclusion
|
||||
sideBlock := indexDb.GetSideBlockByMainId(t.MainId())
|
||||
if sideBlock == nil {
|
||||
//found deep orphan which found block, insert parents
|
||||
cur := t
|
||||
for cur != nil && !index.QueryHasResults(indexDb.GetSideBlocksByTemplateId(cur.SideTemplateId(indexDb.Consensus()))) {
|
||||
orphanBlock, orphanUncles, err := indexDb.GetSideBlockFromPoolBlock(cur, index.InclusionOrphan)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if index.QueryHasResults(indexDb.GetSideBlocksByTemplateId(orphanBlock.TemplateId)) {
|
||||
continue
|
||||
}
|
||||
if err := indexDb.InsertOrUpdateSideBlock(orphanBlock); err != nil {
|
||||
return fmt.Errorf("error inserting %s, %s at %d: %s", cur.SideTemplateId(indexDb.Consensus()), cur.MainId(), cur.Side.Height, err)
|
||||
}
|
||||
|
||||
for _, orphanUncle := range orphanUncles {
|
||||
if index.QueryHasResults(indexDb.GetSideBlocksByTemplateId(orphanUncle.TemplateId)) {
|
||||
continue
|
||||
}
|
||||
if err := indexDb.InsertOrUpdateSideBlock(orphanUncle); err != nil {
|
||||
return fmt.Errorf("error inserting uncle of %s, %s; %s, %s at %d: %s", cur.SideTemplateId(indexDb.Consensus()), cur.MainId(), orphanUncle.TemplateId, orphanUncle.MainId, cur.Side.Height, err)
|
||||
}
|
||||
}
|
||||
|
||||
cur = getByTemplateId(cur.Side.Parent)
|
||||
if cur == nil {
|
||||
break
|
||||
}
|
||||
if err := processBlock(cur); err != nil {
|
||||
return fmt.Errorf("could not process cur template: %w", err)
|
||||
}
|
||||
}
|
||||
sideBlock = indexDb.GetSideBlockByMainId(t.MainId())
|
||||
if sideBlock == nil {
|
||||
return errors.New("no block in database for orphan")
|
||||
}
|
||||
sideBlock.Inclusion = index.InclusionOrphan
|
||||
} else if sideBlock.Inclusion == index.InclusionAlternateInVerifiedChain {
|
||||
sideBlock.Inclusion = index.InclusionInVerifiedChain
|
||||
}
|
||||
|
||||
if err := indexDb.InsertOrUpdateSideBlock(sideBlock); err != nil {
|
||||
return fmt.Errorf("error inserting %s, %s at %d: %s", sideBlock.TemplateId, sideBlock.MainId, sideBlock.SideHeight, err)
|
||||
}
|
||||
t.Depth.Store(math.MaxUint64)
|
||||
storeFunc(t)
|
||||
|
||||
indexMain.SideTemplateId = sideTemplateId
|
||||
indexMain.CoinbasePrivateKey = t.Side.CoinbasePrivateKey
|
||||
if err := indexDb.InsertOrUpdateMainBlock(indexMain); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Logf("Monero", "INSERTED found block %s at height %d, template id %s", mainId, h.Height, sideTemplateId)
|
||||
if err := FindAndInsertMainHeaderOutputs(indexMain, indexDb, client, difficultyByHeight, getByTemplateId, getByMainId, getByMainHeight, processBlock); err != nil {
|
||||
utils.Errorf("", "error inserting coinbase outputs: %s", err)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
data, _ := t.MarshalBinary()
|
||||
newBlock := &sidechain.PoolBlock{}
|
||||
if err := newBlock.UnmarshalBinary(indexDb.Consensus(), &sidechain.NilDerivationCache{}, data); err != nil {
|
||||
utils.Panic(err)
|
||||
}
|
||||
copy(newBlock.CoinbaseExtra(sidechain.SideExtraNonce), extraNonce)
|
||||
newBlock.Main.Nonce = uint32(h.Nonce)
|
||||
newBlock.Depth.Store(math.MaxUint64)
|
||||
|
||||
if newBlock.MainId() == mainId {
|
||||
//store into archive
|
||||
storeFunc(newBlock)
|
||||
//insert into db as well
|
||||
var sideBlock *index.SideBlock
|
||||
if indexDb.GetSideBlockByMainId(t.MainId()).Inclusion == index.InclusionOrphan {
|
||||
sideBlock, _, err = indexDb.GetSideBlockFromPoolBlock(newBlock, index.InclusionOrphan)
|
||||
} else {
|
||||
sideBlock, _, err = indexDb.GetSideBlockFromPoolBlock(newBlock, index.InclusionInVerifiedChain)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not process %s, at side height %d, template id %s: %w", types.HashFromBytes(t.CoinbaseExtra(sidechain.SideTemplateId)), t.Side.Height, sideTemplateId, err)
|
||||
}
|
||||
|
||||
if err := indexDb.InsertOrUpdateSideBlock(sideBlock); err != nil {
|
||||
return fmt.Errorf("error inserting %s, %s at %d: %s", sideBlock.TemplateId, sideBlock.MainId, sideBlock.SideHeight, err)
|
||||
}
|
||||
|
||||
indexMain.SideTemplateId = sideTemplateId
|
||||
indexMain.CoinbasePrivateKey = newBlock.Side.CoinbasePrivateKey
|
||||
if err := indexDb.InsertOrUpdateMainBlock(indexMain); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Logf("", "INSERTED ALTERNATE found block %s at height %d, template id %s", mainId, h.Height, sideTemplateId)
|
||||
if err := FindAndInsertMainHeaderOutputs(indexMain, indexDb, client, difficultyByHeight, getByTemplateId, getByMainId, getByMainHeight, processBlock); err != nil {
|
||||
utils.Errorf("", "error inserting coinbase outputs: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Logf("", "checking block %s at height %d, template id %s: could not find matching template id", mainId, h.Height, sideTemplateId)
|
||||
|
||||
// could not find template, maybe it's other pool?
|
||||
|
||||
// fill template id for future reference
|
||||
// TODO: do this for all blocks
|
||||
indexMain.SetMetadata("merge_mining_tag", sideTemplateId)
|
||||
indexMain.SetMetadata("extra_nonce", binary.LittleEndian.Uint32(extraNonce))
|
||||
|
||||
if err := indexDb.InsertOrUpdateMainBlock(indexMain); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindAndInsertMainHeaderOutputs(mb *index.MainBlock, indexDb *index.Index, client *client.Client, difficultyByHeight block.GetDifficultyByHeightFunc, getByTemplateId sidechain.GetByTemplateIdFunc, getByMainId sidechain.GetByMainIdFunc, getByMainHeight sidechain.GetByMainHeightFunc, processBlock func(b *sidechain.PoolBlock) error) error {
|
||||
if !index.QueryHasResults(indexDb.GetMainCoinbaseOutputs(mb.CoinbaseId)) {
|
||||
//fill information
|
||||
utils.Logf("", "inserting coinbase outputs for %s, template id %s, coinbase id %s", mb.Id, mb.SideTemplateId, mb.CoinbaseId)
|
||||
var t *sidechain.PoolBlock
|
||||
if t = getByTemplateId(mb.SideTemplateId); t == nil || t.MainId() != mb.Id {
|
||||
t = nil
|
||||
}
|
||||
if t == nil {
|
||||
t = getByMainId(mb.Id)
|
||||
}
|
||||
if t != nil {
|
||||
if err := processBlock(t); err != nil {
|
||||
return fmt.Errorf("could not process block: %s", err)
|
||||
}
|
||||
if mb.CoinbaseId != t.CoinbaseId() {
|
||||
return fmt.Errorf("not matching coinbase id: %s vs %s", mb.CoinbaseId, t.CoinbaseId())
|
||||
}
|
||||
indexes, err := client.GetOutputIndexes(mb.CoinbaseId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting output indexes: %w", err)
|
||||
}
|
||||
if len(indexes) != len(t.Main.Coinbase.Outputs) {
|
||||
return fmt.Errorf("not matching indexes vs coinbase output len: %d vs %d", len(indexes), len(t.Main.Coinbase.Outputs))
|
||||
}
|
||||
preAllocatedShares := sidechain.PreAllocateShares(indexDb.Consensus().ChainWindowSize * 2)
|
||||
shares, _ := sidechain.GetShares(t, indexDb.Consensus(), difficultyByHeight, getByTemplateId, preAllocatedShares)
|
||||
if len(shares) != len(t.Main.Coinbase.Outputs) {
|
||||
return fmt.Errorf("not matching shares vs coinbase output len: %d vs %d", len(shares), len(t.Main.Coinbase.Outputs))
|
||||
}
|
||||
outputs := make(index.MainCoinbaseOutputs, 0, len(indexes))
|
||||
for _, o := range t.Main.Coinbase.Outputs {
|
||||
output := index.MainCoinbaseOutput{
|
||||
Id: mb.CoinbaseId,
|
||||
Index: uint32(o.Index),
|
||||
GlobalOutputIndex: indexes[o.Index],
|
||||
Miner: indexDb.GetOrCreateMinerPackedAddress(shares[o.Index].Address).Id(),
|
||||
Value: o.Reward,
|
||||
}
|
||||
outputs = append(outputs, output)
|
||||
}
|
||||
if err := indexDb.InsertOrUpdateMainCoinbaseOutputs(outputs); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("nil template")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0 => ../index
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0
|
||||
github.com/floatdrop/lru v1.3.0
|
||||
github.com/goccy/go-json v0.10.2
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,46 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
@ -1,140 +0,0 @@
|
|||
package utils
|
||||
|
||||
type PositionChart struct {
|
||||
totalItems uint64
|
||||
bucket []uint64
|
||||
perBucket int
|
||||
idle byte
|
||||
}
|
||||
|
||||
func (p *PositionChart) Add(index int, value uint64) {
|
||||
if index < 0 || index >= int(p.totalItems) {
|
||||
return
|
||||
}
|
||||
if len(p.bucket) == 1 {
|
||||
p.bucket[0] += value
|
||||
return
|
||||
}
|
||||
|
||||
p.bucket[p.indexOf(index)] += value
|
||||
}
|
||||
|
||||
func (p *PositionChart) indexOf(index int) int {
|
||||
if len(p.bucket) == 1 {
|
||||
return 0
|
||||
}
|
||||
i := (index*len(p.bucket) - 1) / int(p.totalItems-1)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func (p *PositionChart) Total() (result uint64) {
|
||||
for _, e := range p.bucket {
|
||||
result += e
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *PositionChart) Size() uint64 {
|
||||
return uint64(len(p.bucket))
|
||||
}
|
||||
|
||||
func (p *PositionChart) Resolution() uint64 {
|
||||
return p.totalItems / uint64(len(p.bucket))
|
||||
}
|
||||
|
||||
func (p *PositionChart) SetIdle(idleChar byte) {
|
||||
p.idle = idleChar
|
||||
}
|
||||
|
||||
func (p *PositionChart) String() string {
|
||||
position := make([]byte, 2*2+len(p.bucket))
|
||||
position[0], position[1] = '[', '<'
|
||||
position[len(position)-2], position[len(position)-1] = '<', ']'
|
||||
|
||||
//reverse
|
||||
size := len(p.bucket) - 1
|
||||
for i := len(p.bucket) - 1; i >= 0; i-- {
|
||||
e := p.bucket[i]
|
||||
if e > 0 {
|
||||
if e > 9 {
|
||||
position[2+size-i] = '+'
|
||||
} else {
|
||||
position[2+size-i] = 0x30 + byte(e)
|
||||
}
|
||||
} else {
|
||||
position[2+size-i] = p.idle
|
||||
}
|
||||
}
|
||||
|
||||
return string(position)
|
||||
}
|
||||
|
||||
func (p *PositionChart) StringWithoutDelimiters() string {
|
||||
position := make([]byte, len(p.bucket))
|
||||
|
||||
//reverse
|
||||
size := len(p.bucket) - 1
|
||||
for i := len(p.bucket) - 1; i >= 0; i-- {
|
||||
e := p.bucket[i]
|
||||
if e > 0 {
|
||||
if e > 9 {
|
||||
position[size-i] = '+'
|
||||
} else {
|
||||
position[size-i] = 0x30 + byte(e)
|
||||
}
|
||||
} else {
|
||||
position[size-i] = p.idle
|
||||
}
|
||||
}
|
||||
|
||||
return string(position)
|
||||
}
|
||||
|
||||
func (p *PositionChart) StringWithSeparator(index int) string {
|
||||
if index < 0 || index >= int(p.totalItems) {
|
||||
return p.String()
|
||||
}
|
||||
separatorIndex := p.indexOf(index)
|
||||
position := make([]byte, 1+2*2+len(p.bucket))
|
||||
position[0], position[1] = '[', '<'
|
||||
position[2+separatorIndex] = '|'
|
||||
position[len(position)-2], position[len(position)-1] = '<', ']'
|
||||
|
||||
//reverse
|
||||
size := len(p.bucket) - 1
|
||||
for i := len(p.bucket) - 1; i >= 0; i-- {
|
||||
e := p.bucket[i]
|
||||
j := size - i
|
||||
if j >= separatorIndex {
|
||||
j++
|
||||
}
|
||||
if e > 0 {
|
||||
if e > 9 {
|
||||
position[2+j] = '+'
|
||||
} else {
|
||||
position[2+j] = 0x30 + byte(e)
|
||||
}
|
||||
} else {
|
||||
position[2+j] = p.idle
|
||||
}
|
||||
}
|
||||
|
||||
return string(position)
|
||||
}
|
||||
|
||||
func NewPositionChart(size uint64, totalItems uint64) *PositionChart {
|
||||
if size < 1 {
|
||||
size = 1
|
||||
}
|
||||
perBucket := int(totalItems / size)
|
||||
if totalItems%size > 0 {
|
||||
perBucket += 1
|
||||
}
|
||||
return &PositionChart{
|
||||
totalItems: totalItems,
|
||||
bucket: make([]uint64, size),
|
||||
perBucket: perBucket,
|
||||
idle: '.',
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChart(t *testing.T) {
|
||||
p := NewPositionChart(32, 4096)
|
||||
for i := 0; i < 4096; i++ {
|
||||
p.Add(i, 1)
|
||||
}
|
||||
t.Log(p.String())
|
||||
|
||||
}
|
||||
|
||||
func TestChartIncrement(t *testing.T) {
|
||||
p := NewPositionChart(32, 32)
|
||||
for i := 0; i < 32; i++ {
|
||||
p.Add(i, uint64(i))
|
||||
}
|
||||
t.Log(p.String())
|
||||
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"github.com/goccy/go-json"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type SignedActionEntry struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type SignedAction struct {
|
||||
Action string `json:"action"`
|
||||
Data []SignedActionEntry `json:"data"`
|
||||
Realm string `json:"realm"`
|
||||
}
|
||||
|
||||
func (a *SignedAction) Get(key string) (string, bool) {
|
||||
if i := slices.IndexFunc(a.Data, func(entry SignedActionEntry) bool {
|
||||
return entry.Key == key
|
||||
}); i != -1 {
|
||||
return a.Data[i].Value, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// String Creates a consistent string for signing
|
||||
func (a *SignedAction) String() string {
|
||||
if a.Data == nil {
|
||||
a.Data = make([]SignedActionEntry, 0)
|
||||
}
|
||||
d, err := utils.MarshalJSON(a)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buf := bytes.NewBuffer(make([]byte, 0, len(d)))
|
||||
if err = json.Compact(buf, d); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (a *SignedAction) Verify(realm string, addr address.Interface, signature string) address.SignatureVerifyResult {
|
||||
if a.Realm != realm {
|
||||
// Realm does not match
|
||||
return address.ResultFail
|
||||
}
|
||||
message := a.String()
|
||||
return address.VerifyMessage(addr, []byte(message), signature)
|
||||
}
|
||||
|
||||
func (a *SignedAction) VerifyFallbackToZero(realm string, addr address.Interface, signature string) address.SignatureVerifyResult {
|
||||
if a.Realm != realm {
|
||||
// Realm does not match
|
||||
return address.ResultFail
|
||||
}
|
||||
message := a.String()
|
||||
return address.VerifyMessageFallbackToZero(addr, []byte(message), signature)
|
||||
}
|
||||
|
||||
func SignedActionSetMinerAlias(realm, alias string) *SignedAction {
|
||||
return &SignedAction{
|
||||
Action: "set_miner_alias",
|
||||
Data: []SignedActionEntry{
|
||||
{
|
||||
Key: "alias",
|
||||
Value: alias,
|
||||
},
|
||||
},
|
||||
Realm: realm,
|
||||
}
|
||||
}
|
||||
|
||||
func SignedActionUnsetMinerAlias(realm string) *SignedAction {
|
||||
return &SignedAction{
|
||||
Action: "unset_miner_alias",
|
||||
Data: make([]SignedActionEntry, 0),
|
||||
Realm: realm,
|
||||
}
|
||||
}
|
||||
|
||||
func SignedActionAddWebHook(realm, webhookType, webhookUrl string, other ...SignedActionEntry) *SignedAction {
|
||||
return &SignedAction{
|
||||
Action: "add_webhook",
|
||||
Data: append([]SignedActionEntry{
|
||||
{
|
||||
Key: "type",
|
||||
Value: webhookType,
|
||||
},
|
||||
{
|
||||
Key: "url",
|
||||
Value: webhookUrl,
|
||||
},
|
||||
}, other...),
|
||||
Realm: realm,
|
||||
}
|
||||
}
|
||||
|
||||
func SignedActionRemoveWebHook(realm, webhookType, webhookUrlHash string, other ...SignedActionEntry) *SignedAction {
|
||||
return &SignedAction{
|
||||
Action: "remove_webhook",
|
||||
Data: append([]SignedActionEntry{
|
||||
{
|
||||
Key: "type",
|
||||
Value: webhookType,
|
||||
},
|
||||
{
|
||||
Key: "url_hash",
|
||||
Value: webhookUrlHash,
|
||||
},
|
||||
}, other...),
|
||||
Realm: realm,
|
||||
}
|
||||
}
|
|
@ -1,291 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/client"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func LookupTransactions(requestOther func(ctx context.Context, indices []uint64) []*index.MatchedOutput, indexDb *index.Index, ctx context.Context, inputThreshold int, ids ...types.Hash) (results []index.TransactionInputQueryResults) {
|
||||
txs, err := client.GetDefaultClient().GetTransactionInputs(ctx, ids...)
|
||||
if err != nil || len(txs) != len(ids) {
|
||||
return nil
|
||||
}
|
||||
|
||||
decoys := make([]uint64, 0, len(txs)*16*16)
|
||||
for _, tx := range txs {
|
||||
if len(tx.Inputs) < inputThreshold {
|
||||
continue
|
||||
}
|
||||
for _, i := range tx.Inputs {
|
||||
decoys = append(decoys, i.KeyOffsets...)
|
||||
}
|
||||
}
|
||||
|
||||
var otherResult []*index.MatchedOutput
|
||||
if requestOther != nil {
|
||||
otherResult = requestOther(ctx, decoys)
|
||||
if len(otherResult) != len(decoys) {
|
||||
otherResult = nil
|
||||
}
|
||||
}
|
||||
|
||||
var otherIndex int
|
||||
for _, tx := range txs {
|
||||
if len(tx.Inputs) < inputThreshold {
|
||||
queryResult := make(index.TransactionInputQueryResults, len(tx.Inputs))
|
||||
for i := range queryResult {
|
||||
queryResult[i].Input = tx.Inputs[i]
|
||||
queryResult[i].MatchedOutputs = make([]*index.MatchedOutput, len(tx.Inputs[i].KeyOffsets))
|
||||
}
|
||||
results = append(results, queryResult)
|
||||
continue
|
||||
}
|
||||
queryResult := indexDb.QueryTransactionInputs(tx.Inputs)
|
||||
|
||||
if otherResult != nil {
|
||||
for i, input := range tx.Inputs {
|
||||
for j := range input.KeyOffsets {
|
||||
output := otherResult[otherIndex]
|
||||
otherIndex++
|
||||
if output == nil {
|
||||
continue
|
||||
}
|
||||
if queryResult[i].MatchedOutputs[j] == nil { //todo: multiple matches??
|
||||
queryResult[i].MatchedOutputs[j] = output
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, queryResult)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
var otherLookupHostFunc func(ctx context.Context, indices []uint64) []*index.MatchedOutput
|
||||
|
||||
func init() {
|
||||
if os.Getenv("TRANSACTION_LOOKUP_OTHER") != "" {
|
||||
otherLookupHostFunc = func(ctx context.Context, indices []uint64) (result []*index.MatchedOutput) {
|
||||
data, _ := utils.MarshalJSON(indices)
|
||||
|
||||
result = make([]*index.MatchedOutput, len(indices))
|
||||
|
||||
for _, host := range strings.Split(os.Getenv("TRANSACTION_LOOKUP_OTHER"), ",") {
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
uri, _ := url.Parse(host + "/api/global_indices_lookup")
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "POST",
|
||||
URL: uri,
|
||||
Body: io.NopCloser(bytes.NewReader(data)),
|
||||
}); err == nil {
|
||||
func() {
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode == http.StatusOK {
|
||||
if data, err := io.ReadAll(response.Body); err == nil {
|
||||
r := make([]*index.MatchedOutput, 0, len(indices))
|
||||
if utils.UnmarshalJSON(data, &r) == nil && len(r) == len(indices) {
|
||||
for i := range r {
|
||||
if result[i] == nil {
|
||||
result[i] = r[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ProcessFullBlock(b *index.MainBlock, indexDb *index.Index) error {
|
||||
|
||||
var sideTemplateId types.Hash
|
||||
var extraNonce []byte
|
||||
mainBlock, err := GetMainBlock(b.Id, client.GetDefaultClient())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get main block for %s at %d: %w", b.Id, b.Height, err)
|
||||
}
|
||||
|
||||
utils.Logf("", "Block %s at %d (%s), processing %d transactions", b.Id, b.Height, time.Unix(int64(b.Timestamp), 0).UTC().Format("02-01-2006 15:04:05 MST"), len(mainBlock.Transactions))
|
||||
|
||||
// try to fetch extra nonce if existing
|
||||
{
|
||||
if t := mainBlock.Coinbase.Extra.GetTag(uint8(sidechain.SideExtraNonce)); t != nil {
|
||||
if len(t.Data) >= sidechain.SideExtraNonceSize || len(t.Data) < sidechain.SideExtraNonceMaxSize {
|
||||
extraNonce = t.Data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get a merge tag if existing
|
||||
{
|
||||
if t := mainBlock.Coinbase.Extra.GetTag(uint8(sidechain.SideTemplateId)); t != nil {
|
||||
if len(t.Data) == types.HashSize {
|
||||
sideTemplateId = types.HashFromBytes(t.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sideTemplateId != types.ZeroHash && len(extraNonce) != 0 {
|
||||
b.SetMetadata("merge_mining_tag", sideTemplateId)
|
||||
b.SetMetadata("extra_nonce", binary.LittleEndian.Uint32(extraNonce))
|
||||
utils.Logf("", "p2pool tags found template id %s, extra nonce %d", sideTemplateId, binary.LittleEndian.Uint32(extraNonce))
|
||||
}
|
||||
|
||||
const txInputThreshold = 4
|
||||
const txInputThresholdForRatio = 8
|
||||
|
||||
if len(mainBlock.Transactions) > 0 {
|
||||
|
||||
results := LookupTransactions(otherLookupHostFunc, indexDb, context.Background(), txInputThreshold, mainBlock.Transactions...)
|
||||
|
||||
for i, inputs := range results {
|
||||
txId := mainBlock.Transactions[i]
|
||||
|
||||
if len(inputs) < txInputThreshold {
|
||||
//skip due to not enough matches later on
|
||||
continue
|
||||
}
|
||||
|
||||
matches := inputs.Match()
|
||||
|
||||
var topMiner *index.TransactionInputQueryResultsMatch
|
||||
for j, m := range matches {
|
||||
if m.Address == nil {
|
||||
continue
|
||||
} else if topMiner == nil {
|
||||
topMiner = &matches[j]
|
||||
} else {
|
||||
if topMiner.Count <= 2 && topMiner.Count == m.Count {
|
||||
//if count is not greater
|
||||
topMiner = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if topMiner != nil {
|
||||
var noMinerCount, minerCount, otherMinerCount uint64
|
||||
for _, i := range inputs {
|
||||
var isNoMiner, isMiner, isOtherMiner bool
|
||||
for _, o := range i.MatchedOutputs {
|
||||
if o == nil {
|
||||
isNoMiner = true
|
||||
} else if topMiner.Address.Compare(o.Address) == 0 {
|
||||
isMiner = true
|
||||
} else {
|
||||
isOtherMiner = true
|
||||
}
|
||||
}
|
||||
|
||||
if isMiner {
|
||||
minerCount++
|
||||
} else if isOtherMiner {
|
||||
otherMinerCount++
|
||||
} else if isNoMiner {
|
||||
noMinerCount++
|
||||
}
|
||||
}
|
||||
|
||||
minerRatio := float64(minerCount) / float64(len(inputs))
|
||||
noMinerRatio := float64(noMinerCount) / float64(len(inputs))
|
||||
otherMinerRatio := float64(otherMinerCount) / float64(len(inputs))
|
||||
var likelyMiner bool
|
||||
if (len(inputs) >= txInputThresholdForRatio && minerRatio >= noMinerRatio && minerRatio > otherMinerRatio) || (len(inputs) >= txInputThresholdForRatio && minerRatio > 0.35 && minerRatio > otherMinerRatio) || (len(inputs) >= txInputThreshold && minerRatio > 0.75) {
|
||||
likelyMiner = true
|
||||
}
|
||||
|
||||
if likelyMiner {
|
||||
utils.Logf("", "transaction %s is LIKELY for %s: miner ratio %.02f (%d/%d), none %.02f (%d/%d), other %.02f (%d/%d); coinbase %d, sweep %d", txId, topMiner.Address.ToBase58(), minerRatio, minerCount, len(inputs), noMinerRatio, noMinerCount, len(inputs), otherMinerRatio, otherMinerCount, len(inputs), topMiner.CoinbaseCount, topMiner.SweepCount)
|
||||
|
||||
minimalInputs := make(index.MinimalTransactionInputQueryResults, len(inputs))
|
||||
|
||||
decoyCount := len(inputs[0].Input.KeyOffsets)
|
||||
spendingOutputIndices := make([]uint64, 0, len(inputs)*decoyCount)
|
||||
for j, input := range inputs {
|
||||
spendingOutputIndices = append(spendingOutputIndices, input.Input.KeyOffsets...)
|
||||
minimalInputs[j].Input = input.Input
|
||||
minimalInputs[j].MatchedOutputs = make([]*index.MinimalMatchedOutput, len(input.MatchedOutputs))
|
||||
for oi, o := range input.MatchedOutputs {
|
||||
if o == nil {
|
||||
continue
|
||||
} else if o.Coinbase != nil {
|
||||
minimalInputs[j].MatchedOutputs[oi] = &index.MinimalMatchedOutput{
|
||||
Coinbase: o.Coinbase.Id,
|
||||
GlobalOutputIndex: o.GlobalOutputIndex,
|
||||
Address: o.Address,
|
||||
}
|
||||
} else if o.Sweep != nil {
|
||||
minimalInputs[j].MatchedOutputs[oi] = &index.MinimalMatchedOutput{
|
||||
Sweep: o.Sweep.Id,
|
||||
GlobalOutputIndex: o.GlobalOutputIndex,
|
||||
Address: o.Address,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputIndexes, err := client.GetDefaultClient().GetOutputIndexes(txId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx := &index.MainLikelySweepTransaction{
|
||||
Id: txId,
|
||||
Timestamp: mainBlock.Timestamp,
|
||||
Result: minimalInputs,
|
||||
Match: matches,
|
||||
Value: topMiner.CoinbaseAmount,
|
||||
SpendingOutputIndices: spendingOutputIndices,
|
||||
GlobalOutputIndices: outputIndexes,
|
||||
InputCount: len(inputs),
|
||||
InputDecoyCount: decoyCount,
|
||||
MinerCount: int(minerCount),
|
||||
OtherMinersCount: int(otherMinerCount),
|
||||
NoMinerCount: int(noMinerCount),
|
||||
MinerRatio: float32(minerRatio),
|
||||
OtherMinersRatio: float32(otherMinerRatio),
|
||||
NoMinerRatio: float32(noMinerRatio),
|
||||
Address: topMiner.Address,
|
||||
}
|
||||
if err := indexDb.InsertOrUpdateMainLikelySweepTransaction(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
utils.Logf("", "transaction %s is NOT likely for %s: miner ratio %.02f (%d/%d), none %.02f (%d/%d), other %.02f (%d/%d); coinbase %d, sweep %d", txId, topMiner.Address.ToBase58(), minerRatio, topMiner.Count, len(inputs), noMinerRatio, noMinerCount, len(inputs), otherMinerRatio, otherMinerCount, len(inputs), topMiner.CoinbaseCount, topMiner.SweepCount)
|
||||
}
|
||||
} else {
|
||||
//utils.Logf("", "transaction %s does not have enough matches, %d outputs", txId, len(inputs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.SetMetadata("processed", true)
|
||||
|
||||
if err = indexDb.InsertOrUpdateMainBlock(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
package utils
|
||||
|
||||
type urlEntry struct {
|
||||
Host string
|
||||
Onion string
|
||||
}
|
||||
|
||||
type siteKey int
|
||||
|
||||
const (
|
||||
SiteKeyP2PoolIo = siteKey(iota)
|
||||
SiteKeyLocalMonero
|
||||
SiteKeyExploreMonero
|
||||
SiteKeyMoneroCom
|
||||
SiteKeyP2PoolObserver
|
||||
SiteKeyP2PoolObserverMini
|
||||
SiteKeyP2PoolObserverOld
|
||||
SiteKeyP2PoolObserverOldMini
|
||||
SiteKeyGitGammaspectraLive
|
||||
SiteKeyXmrChainNet
|
||||
SiteKeySethForPrivacy
|
||||
SiteKeyXmrVsBeast
|
||||
)
|
||||
|
||||
var existingUrl = map[siteKey]urlEntry{
|
||||
SiteKeyP2PoolIo: {
|
||||
Host: "https://p2pool.io",
|
||||
Onion: "http://yucmgsbw7nknw7oi3bkuwudvc657g2xcqahhbjyewazusyytapqo4xid.onion",
|
||||
},
|
||||
SiteKeyLocalMonero: {
|
||||
Host: "https://localmonero.co",
|
||||
Onion: "http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion",
|
||||
},
|
||||
SiteKeyExploreMonero: {
|
||||
Host: "https://www.exploremonero.com",
|
||||
},
|
||||
SiteKeyMoneroCom: {
|
||||
Host: "https://monero.com",
|
||||
},
|
||||
SiteKeyP2PoolObserver: {
|
||||
Host: "https://p2pool.observer",
|
||||
Onion: "http://p2pool2giz2r5cpqicajwoazjcxkfujxswtk3jolfk2ubilhrkqam2id.onion",
|
||||
},
|
||||
SiteKeyP2PoolObserverMini: {
|
||||
Host: "https://mini.p2pool.observer",
|
||||
Onion: "http://p2pmin25k4ei5bp3l6bpyoap6ogevrc35c3hcfue7zfetjpbhhshxdqd.onion",
|
||||
},
|
||||
SiteKeyP2PoolObserverOld: {
|
||||
Host: "https://old.p2pool.observer",
|
||||
Onion: "http://temp2p7m2ddclcsqx2mrbqrmo7ccixpiu5s2cz2c6erxi2lppptdvxqd.onion",
|
||||
},
|
||||
SiteKeyP2PoolObserverOldMini: {
|
||||
Host: "https://old-mini.p2pool.observer",
|
||||
Onion: "http://temp2pbud6av2jx3lh3yovrj4mjjy2k4p5rxydviosp356ndzs4nd6yd.onion",
|
||||
},
|
||||
SiteKeyGitGammaspectraLive: {
|
||||
Host: "https://git.gammaspectra.live",
|
||||
Onion: "http://gitshn5x75sgs53q3pxwjva2z65ns5vadx3h7u3hrdssbxsova66cxid.onion",
|
||||
},
|
||||
SiteKeyXmrChainNet: {
|
||||
Host: "https://xmrchain.net",
|
||||
},
|
||||
SiteKeySethForPrivacy: {
|
||||
Host: "https://sethforprivacy.com",
|
||||
Onion: "http://sfprivg7qec6tdle7u6hdepzjibin6fn3ivm6qlwytr235rh5vc6bfqd.onion",
|
||||
},
|
||||
SiteKeyXmrVsBeast: {
|
||||
Host: "https://xmrvsbeast.com",
|
||||
},
|
||||
}
|
||||
|
||||
func GetSiteUrl(k siteKey, tryOnion bool) string {
|
||||
e := existingUrl[k]
|
||||
if tryOnion && e.Onion != "" {
|
||||
return e.Onion
|
||||
}
|
||||
return e.Host
|
||||
}
|
||||
|
||||
func GetSiteUrlByHost(host string, tryOnion bool) string {
|
||||
findKey := "https://" + host
|
||||
for k, e := range existingUrl {
|
||||
if e.Host == findKey {
|
||||
return GetSiteUrl(k, tryOnion)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -1,401 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JSONWebHookDiscord struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
AvatarUrl string `json:"avatar_url,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Embeds []JSONWebHookDiscordEmbed `json:"embeds,omitempty"`
|
||||
Components []JSONWebHookDiscordComponentRow `json:"components,omitempty"`
|
||||
TTS bool `json:"tts,omitempty"`
|
||||
AllowedMentions *struct {
|
||||
Parse []string `json:"parse,omitempty"`
|
||||
Users []string `json:"users,omitempty"`
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
} `json:"allowed_mentions,omitempty"`
|
||||
}
|
||||
|
||||
func NewJSONWebHookDiscordComponent(b ...JSONWebHookDiscordComponentButton) JSONWebHookDiscordComponentRow {
|
||||
return JSONWebHookDiscordComponentRow{
|
||||
Type: 1,
|
||||
Components: b,
|
||||
}
|
||||
}
|
||||
|
||||
func NewJSONWebHookDiscordComponentButton(label, url string) JSONWebHookDiscordComponentButton {
|
||||
return JSONWebHookDiscordComponentButton{
|
||||
Type: 2,
|
||||
Style: 5,
|
||||
Label: label,
|
||||
Url: url,
|
||||
}
|
||||
}
|
||||
|
||||
type JSONWebHookDiscordComponentRow struct {
|
||||
Type int `json:"type"`
|
||||
Components []JSONWebHookDiscordComponentButton `json:"components"`
|
||||
}
|
||||
|
||||
type JSONWebHookDiscordComponentButton struct {
|
||||
Type int `json:"type"`
|
||||
Style int `json:"style"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type JSONWebHookDiscordEmbed struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
// Timestamp Time in time.RFC3339 YYYY-MM-DDTHH:MM:SS.MSSZ format
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
Color *uint64 `json:"color,omitempty"`
|
||||
|
||||
Footer *struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
} `json:"footer,omitempty"`
|
||||
|
||||
Image *struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
} `json:"image,omitempty"`
|
||||
Thumbnail *struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
} `json:"thumbnail,omitempty"`
|
||||
|
||||
Provider *struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
IconUrl string `json:"icon_url,omitempty"`
|
||||
} `json:"provider,omitempty"`
|
||||
|
||||
Author *struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
IconUrl string `json:"icon_url,omitempty"`
|
||||
} `json:"author,omitempty"`
|
||||
|
||||
Fields []JSONWebHookDiscordEmbedField `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
type JSONWebHookDiscordEmbedField struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Inline bool `json:"inline,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
DiscordColorGreen uint64 = 5763719
|
||||
DiscordColorDarkGreen uint64 = 2067276
|
||||
DiscordColorOrange uint64 = 15105570
|
||||
)
|
||||
|
||||
var WebHookRateLimit = time.NewTicker(time.Second / 20)
|
||||
var WebHookUserAgent string
|
||||
|
||||
var WebHookHost string
|
||||
|
||||
var WebHookVersion = fmt.Sprintf("%d.%d", types.CurrentSoftwareVersion.Major(), types.CurrentSoftwareVersion.Minor())
|
||||
|
||||
func init() {
|
||||
WebHookHost = os.Getenv("NET_SERVICE_ADDRESS")
|
||||
WebHookUserAgent = fmt.Sprintf("Mozilla/5.0 (compatible;GoObserver %s; +https://%s/api#webhooks)", types.CurrentSoftwareVersion.String(), WebHookHost)
|
||||
}
|
||||
|
||||
var WebHookClient = http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
func SetWebHookProxy(hostPort string) {
|
||||
WebHookClient.Transport = &http.Transport{
|
||||
Proxy: http.ProxyURL(&url.URL{
|
||||
Scheme: "socks5",
|
||||
Host: hostPort,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func SendSideBlock(w *index.MinerWebHook, ts int64, source *address.Address, block *index.SideBlock) error {
|
||||
if !CanSendToHook(w, "side_blocks") {
|
||||
return nil
|
||||
}
|
||||
|
||||
blockValuation := func() string {
|
||||
if block.IsOrphan() {
|
||||
return "0%"
|
||||
} else if block.IsUncle() {
|
||||
return strconv.FormatUint(100-w.Consensus.UnclePenalty, 10) + "% (uncle)"
|
||||
} else if block.IsUncle() {
|
||||
return "100% + " + strconv.FormatUint(w.Consensus.UnclePenalty, 10) + "% of " + strconv.FormatUint(uint64(len(block.Uncles)), 10) + "% uncle(s)"
|
||||
} else {
|
||||
return "100%"
|
||||
}
|
||||
}
|
||||
|
||||
blockWeight, _ := block.Weight(block.EffectiveHeight, w.Consensus.ChainWindowSize, w.Consensus.UnclePenalty)
|
||||
|
||||
switch w.Type {
|
||||
case index.WebHookDiscord:
|
||||
|
||||
var color = &DiscordColorGreen
|
||||
fields := []JSONWebHookDiscordEmbedField{
|
||||
{
|
||||
Name: "Pool Height",
|
||||
Value: strconv.FormatUint(block.SideHeight, 10),
|
||||
Inline: true,
|
||||
},
|
||||
}
|
||||
if block.IsUncle() {
|
||||
color = &DiscordColorDarkGreen
|
||||
fields = append(fields, JSONWebHookDiscordEmbedField{
|
||||
Name: "Parent Height",
|
||||
Value: "[" + strconv.FormatUint(block.EffectiveHeight, 10) + "](" + "https://" + WebHookHost + "/share/" + block.UncleOf.String() + ")",
|
||||
Inline: true,
|
||||
})
|
||||
} else {
|
||||
fields = append(fields, JSONWebHookDiscordEmbedField{
|
||||
Name: "Monero Height",
|
||||
Value: strconv.FormatUint(block.MainHeight, 10),
|
||||
Inline: true,
|
||||
})
|
||||
}
|
||||
fields = append(fields,
|
||||
JSONWebHookDiscordEmbedField{
|
||||
Name: "Found by",
|
||||
Value: "[`" + string(utils.ShortenSlice(block.MinerAddress.ToBase58(), 24)) + "`](" + "https://" + WebHookHost + "/miner/" + string(block.MinerAddress.ToBase58()) + ")",
|
||||
},
|
||||
JSONWebHookDiscordEmbedField{
|
||||
Name: "Template Id",
|
||||
Value: "[`" + block.TemplateId.String() + "`](" + "https://" + WebHookHost + "/share/" + block.TemplateId.String() + ")",
|
||||
},
|
||||
JSONWebHookDiscordEmbedField{
|
||||
Name: "Valuation",
|
||||
Value: blockValuation(),
|
||||
Inline: true,
|
||||
},
|
||||
JSONWebHookDiscordEmbedField{
|
||||
Name: "Weight",
|
||||
Value: utils.SiUnits(float64(blockWeight), 4),
|
||||
Inline: true,
|
||||
},
|
||||
)
|
||||
|
||||
return SendJsonPost(w, ts, source, &JSONWebHookDiscord{
|
||||
Embeds: []JSONWebHookDiscordEmbed{
|
||||
{
|
||||
Color: color,
|
||||
Title: "**" + func() string {
|
||||
if block.IsUncle() {
|
||||
return "UNCLE "
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}() + "SHARE FOUND** on P2Pool " + func() string {
|
||||
if w.Consensus.IsDefault() {
|
||||
return "Main"
|
||||
} else if w.Consensus.IsMini() {
|
||||
return "Mini"
|
||||
} else {
|
||||
return "Unknown"
|
||||
}
|
||||
}(),
|
||||
Url: "https://" + WebHookHost + "/share/" + block.MainId.String(),
|
||||
Description: "",
|
||||
Fields: fields,
|
||||
Footer: &struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
}{
|
||||
Text: "Share mined using " + block.SoftwareId.String() + " " + block.SoftwareVersion.String(),
|
||||
},
|
||||
Timestamp: time.Unix(int64(block.Timestamp), 0).UTC().Format(time.RFC3339),
|
||||
},
|
||||
},
|
||||
})
|
||||
case index.WebHookCustom:
|
||||
return SendJsonPost(w, ts, source, &JSONEvent{
|
||||
Type: JSONEventSideBlock,
|
||||
SideBlock: block,
|
||||
})
|
||||
default:
|
||||
return errors.New("unsupported hook type")
|
||||
}
|
||||
}
|
||||
|
||||
func SendFoundBlock(w *index.MinerWebHook, ts int64, source *address.Address, block *index.FoundBlock, outputs index.MainCoinbaseOutputs) error {
|
||||
if !CanSendToHook(w, "found_blocks") {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch w.Type {
|
||||
case index.WebHookCustom:
|
||||
return SendJsonPost(w, ts, source, &JSONEvent{
|
||||
Type: JSONEventFoundBlock,
|
||||
FoundBlock: block,
|
||||
MainCoinbaseOutputs: outputs,
|
||||
})
|
||||
default:
|
||||
return errors.New("unsupported hook type")
|
||||
}
|
||||
}
|
||||
|
||||
func CanSendToHook(w *index.MinerWebHook, key string) bool {
|
||||
if v, ok := w.Settings["send_"+key]; ok && v == "false" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func SendPayout(w *index.MinerWebHook, ts int64, source *address.Address, payout *index.Payout) error {
|
||||
if !CanSendToHook(w, "payouts") {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch w.Type {
|
||||
case index.WebHookDiscord:
|
||||
return SendJsonPost(w, ts, source, &JSONWebHookDiscord{
|
||||
Embeds: []JSONWebHookDiscordEmbed{
|
||||
{
|
||||
Color: &DiscordColorOrange,
|
||||
Title: "**RECEIVED PAYOUT** on P2Pool " + func() string {
|
||||
if w.Consensus.IsDefault() {
|
||||
return "Main"
|
||||
} else if w.Consensus.IsMini() {
|
||||
return "Mini"
|
||||
} else {
|
||||
return "Unknown"
|
||||
}
|
||||
}(),
|
||||
Url: "https://" + WebHookHost + "/proof/" + payout.MainId.String() + "/" + strconv.FormatUint(payout.Index, 10),
|
||||
Description: "",
|
||||
Fields: []JSONWebHookDiscordEmbedField{
|
||||
{
|
||||
Name: "Pool Height",
|
||||
Value: strconv.FormatUint(payout.SideHeight, 10),
|
||||
Inline: true,
|
||||
},
|
||||
{
|
||||
Name: "Monero Height",
|
||||
Value: strconv.FormatUint(payout.MainHeight, 10),
|
||||
Inline: true,
|
||||
},
|
||||
{
|
||||
Name: "Monero Id",
|
||||
Value: "[`" + payout.MainId.String() + "`](" + GetSiteUrl(SiteKeyP2PoolIo, false) + "/explorer/block/" + payout.MainId.String() + ")",
|
||||
},
|
||||
{
|
||||
Name: "Template Id",
|
||||
Value: "[`" + payout.TemplateId.String() + "`](" + "https://" + WebHookHost + "/share/" + payout.TemplateId.String() + ")",
|
||||
},
|
||||
|
||||
{
|
||||
Name: "Coinbase Id",
|
||||
Value: "[`" + payout.CoinbaseId.String() + "`](" + GetSiteUrl(SiteKeyP2PoolIo, false) + "/explorer/tx/" + payout.CoinbaseId.String() + ")",
|
||||
},
|
||||
{
|
||||
Name: "Coinbase Private Key",
|
||||
Value: "[`" + payout.PrivateKey.String() + "`](" + "https://" + WebHookHost + "/proof/" + payout.MainId.String() + "/" + strconv.FormatUint(payout.Index, 10) + ")",
|
||||
},
|
||||
{
|
||||
Name: "Payout address",
|
||||
Value: "[`" + string(utils.ShortenSlice(source.ToBase58(), 24)) + "`](" + "https://" + WebHookHost + "/miner/" + string(source.ToBase58()) + ")",
|
||||
},
|
||||
{
|
||||
Name: "Reward",
|
||||
Value: "**" + utils.XMRUnits(payout.Reward) + " XMR" + "**",
|
||||
Inline: true,
|
||||
},
|
||||
{
|
||||
Name: "Global Output Index",
|
||||
Value: strconv.FormatUint(payout.GlobalOutputIndex, 10),
|
||||
Inline: true,
|
||||
},
|
||||
},
|
||||
Timestamp: time.Unix(int64(payout.Timestamp), 0).UTC().Format(time.RFC3339),
|
||||
},
|
||||
},
|
||||
})
|
||||
case index.WebHookCustom:
|
||||
return SendJsonPost(w, ts, source, &JSONEvent{
|
||||
Type: JSONEventPayout,
|
||||
Payout: payout,
|
||||
})
|
||||
default:
|
||||
return errors.New("unsupported hook type")
|
||||
}
|
||||
}
|
||||
|
||||
func SendOrphanedBlock(w *index.MinerWebHook, ts int64, source *address.Address, block *index.SideBlock) error {
|
||||
if !CanSendToHook(w, "orphaned_blocks") {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch w.Type {
|
||||
case index.WebHookCustom:
|
||||
return SendJsonPost(w, ts, source, &JSONEvent{
|
||||
Type: JSONEventOrphanedBlock,
|
||||
SideBlock: block,
|
||||
})
|
||||
default:
|
||||
return errors.New("unsupported hook type")
|
||||
}
|
||||
}
|
||||
|
||||
func SendJsonPost(w *index.MinerWebHook, ts int64, source *address.Address, data any) error {
|
||||
uri, err := url.Parse(w.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := utils.MarshalJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headers := make(http.Header)
|
||||
headers.Set("Accept", "*/*")
|
||||
headers.Set("User-Agent", WebHookUserAgent)
|
||||
headers.Set("Content-Type", "application/json")
|
||||
headers.Set("X-P2Pool-Observer-Timestamp", strconv.FormatInt(ts, 10))
|
||||
headers.Set("X-P2Pool-Observer-Version", WebHookVersion)
|
||||
headers.Set("X-P2Pool-Observer-Host", WebHookHost)
|
||||
headers.Set("X-P2Pool-Observer-Consensus-ID", w.Consensus.Id.String())
|
||||
headers.Set("X-P2Pool-Observer-Address", string(source.ToBase58()))
|
||||
|
||||
// apply rate limit
|
||||
<-WebHookRateLimit.C
|
||||
|
||||
response, err := WebHookClient.Do(&http.Request{
|
||||
Method: "POST",
|
||||
URL: uri,
|
||||
Header: headers,
|
||||
Body: io.NopCloser(bytes.NewBuffer(body)),
|
||||
ContentLength: int64(len(body)),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
|
||||
if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNoContent {
|
||||
data, _ := io.ReadAll(response.Body)
|
||||
return fmt.Errorf("error code %d: %s", response.StatusCode, string(data))
|
||||
}
|
||||
return nil
|
||||
}
|
177
cmd/web/api.go
177
cmd/web/api.go
|
@ -1,177 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getTypeFromAPI[T any](method string, cacheTime ...int) *T {
|
||||
cTime := 0
|
||||
if len(cacheTime) > 0 {
|
||||
cTime = cacheTime[0]
|
||||
}
|
||||
|
||||
return cacheResult[*T](method, time.Second*time.Duration(cTime), func() *T {
|
||||
uri, _ := url.Parse(os.Getenv("API_URL") + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
if response.StatusCode == http.StatusOK {
|
||||
var result T
|
||||
decoder := utils.NewJSONDecoder(response.Body)
|
||||
if decoder.Decode(&result) != nil {
|
||||
return nil
|
||||
} else {
|
||||
return &result
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getSliceFromAPI[T any](method string, cacheTime ...int) []T {
|
||||
cTime := 0
|
||||
if len(cacheTime) > 0 {
|
||||
cTime = cacheTime[0]
|
||||
}
|
||||
|
||||
return cacheResult[[]T](method, time.Second*time.Duration(cTime), func() []T {
|
||||
uri, _ := url.Parse(os.Getenv("API_URL") + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
if response.StatusCode == http.StatusOK {
|
||||
var result []T
|
||||
decoder := utils.NewJSONDecoder(response.Body)
|
||||
if decoder.Decode(&result) != nil {
|
||||
return nil
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getStreamFromAPI[T any](method string) <-chan T {
|
||||
result := make(chan T, 1)
|
||||
|
||||
go func() {
|
||||
defer close(result)
|
||||
uri, _ := url.Parse(os.Getenv("API_URL") + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
|
||||
if response.StatusCode == http.StatusOK {
|
||||
|
||||
var err error
|
||||
|
||||
// Read opening
|
||||
var b [1]byte
|
||||
for {
|
||||
if _, err = response.Body.Read(b[:]); err != nil {
|
||||
return
|
||||
}
|
||||
if b[0] == '[' {
|
||||
break
|
||||
} else if b[0] != ' ' && b[0] != 0xa {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
decoder := utils.NewJSONDecoder(response.Body)
|
||||
for decoder.More() {
|
||||
var item T
|
||||
if err := decoder.Decode(&item); err != nil {
|
||||
return
|
||||
} else {
|
||||
result <- item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func getSideBlocksStreamFromAPI(method string) <-chan *index.SideBlock {
|
||||
return getStreamFromAPI[*index.SideBlock](method)
|
||||
}
|
||||
|
||||
func getSideBlocksFromAPI(method string, cacheTime ...int) []*index.SideBlock {
|
||||
return getSliceFromAPI[*index.SideBlock](method, cacheTime...)
|
||||
}
|
||||
|
||||
func getFromAPIRaw(method string, cacheTime ...int) []byte {
|
||||
cTime := 0
|
||||
if len(cacheTime) > 0 {
|
||||
cTime = cacheTime[0]
|
||||
}
|
||||
|
||||
return cacheResult[[]byte](method, time.Second*time.Duration(cTime), func() []byte {
|
||||
uri, _ := url.Parse(os.Getenv("API_URL") + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
if response.StatusCode == http.StatusOK {
|
||||
if data, err := io.ReadAll(response.Body); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getFromAPI(method string) (statusCode int, data []byte) {
|
||||
uri, _ := url.Parse(os.Getenv("API_URL") + method)
|
||||
if response, err := http.DefaultClient.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: uri,
|
||||
}); err != nil {
|
||||
return 0, nil
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
defer io.ReadAll(response.Body)
|
||||
|
||||
if data, err = io.ReadAll(response.Body); err != nil {
|
||||
return response.StatusCode, nil
|
||||
} else {
|
||||
return response.StatusCode, data
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var cache = make(map[string]any)
|
||||
var cacheLock sync.RWMutex
|
||||
|
||||
type cachedResult[T any] struct {
|
||||
t time.Time
|
||||
result T
|
||||
}
|
||||
|
||||
func cacheResult[T any](k string, cacheTime time.Duration, result func() T) T {
|
||||
var zeroVal T
|
||||
if r, ok := func() (T, bool) {
|
||||
if cacheTime > 0 {
|
||||
cacheLock.RLock()
|
||||
defer cacheLock.RUnlock()
|
||||
|
||||
if r := cache[k]; r != nil {
|
||||
if r2, ok := r.(*cachedResult[T]); ok && r2 != nil && r2.t.Add(cacheTime).After(time.Now()) {
|
||||
return r2.result, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return zeroVal, false
|
||||
}(); ok {
|
||||
return r
|
||||
}
|
||||
|
||||
r := result()
|
||||
|
||||
if cacheTime > 0 {
|
||||
cacheLock.Lock()
|
||||
defer cacheLock.Unlock()
|
||||
cache[k] = &cachedResult[T]{
|
||||
t: time.Now(),
|
||||
result: r,
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
Binary file not shown.
|
@ -1,44 +0,0 @@
|
|||
module git.gammaspectra.live/P2Pool/p2pool-observer/cmd/web
|
||||
|
||||
go 1.22
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0 => ../../
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0 => ../index
|
||||
|
||||
replace git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0 => ../utils
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index v0.0.0
|
||||
git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils v0.0.0
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/mazznoer/colorgrad v0.9.1
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
|
||||
github.com/valyala/quicktemplate v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e // indirect
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/floatdrop/lru v1.3.0 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mazznoer/csscolorparser v0.1.3 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/goccy/go-json => github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887
|
|
@ -1,76 +0,0 @@
|
|||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33 h1:BPV7iIiv8T+X7gg9/JfNmEBoH4HXOkw8CR7FN6bBwB8=
|
||||
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20230701100949-027561bd2a33/go.mod h1:336HUKX25mQ1qUtzkwV9Wrqi153tTgUOKcIhpYuF2ts=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
|
||||
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 h1:bzHDuu1IgETKqPBOlIdCE2LaZIJ+ZpROSprNn+fnzd8=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e h1:ropqS9niQR/ZKCUrlmWe+uDH0fLIyAnCIjkEjyTDgA8=
|
||||
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20230722215223-18ecc51ae61e/go.mod h1:Wn5QI7XIMHMpEu10pPspW9h3eGmXQPJwh/4/+Gi3G1U=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 h1:MgeHHcF+GnCJBWMSzq8XAbc8p/UhNwFruEKCPPJ74YQ=
|
||||
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a h1:c24MHv/z+aBYpYNsQHcJqmFuaYInGVixJZgDCXA/4bs=
|
||||
git.gammaspectra.live/P2Pool/sha3 v0.0.0-20230604092430-04fe7dc6439a/go.mod h1:6wZ0+whl+HZdcRve4R6Rq6jV1fmL1xCYO8Wty6lR008=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887 h1:P01nqSM+0b6zlPasOFYsqnQSP2dTRVZkanAnY9q/Bcc=
|
||||
github.com/WeebDataHoarder/go-json v0.0.0-20230730135821-d8f6463bb887/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
|
||||
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
|
||||
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mazznoer/colorgrad v0.9.1 h1:MB80JYVndKWSMEM1beNqnuOowWGhoQc3DXWXkFp6JlM=
|
||||
github.com/mazznoer/colorgrad v0.9.1/go.mod h1:WX2R9wt9B47+txJZVVpM9LY+LAGIdi4lTI5wIyreDH4=
|
||||
github.com/mazznoer/csscolorparser v0.1.2/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic=
|
||||
github.com/mazznoer/csscolorparser v0.1.3 h1:vug4zh6loQxAUxfU1DZEu70gTPufDPspamZlHAkKcxE=
|
||||
github.com/mazznoer/csscolorparser v0.1.3/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
|
||||
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
2
cmd/web/views/.gitignore
vendored
2
cmd/web/views/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*qtpl.go.tmp
|
||||
*qtpl.go
|
|
@ -1,1208 +0,0 @@
|
|||
{% import cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils" %}
|
||||
|
||||
{% code
|
||||
type ApiPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
|
||||
PoolInfoExample cmdutils.PoolInfoResult
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *ApiPage) Style() %}
|
||||
{%= p.BasePage.Style() %}
|
||||
pre {
|
||||
padding: 2px;
|
||||
border: 1px dashed #BBB;
|
||||
}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *ApiPage) Title() %}
|
||||
{%= p.BasePage.Title() %} - API Documentation
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *ApiPage) Content() %}
|
||||
|
||||
<div class="center" style="min-width: 800px; width: 50%">
|
||||
<div>
|
||||
<h2>API Documentation</h2>
|
||||
<p>This site and API is in development. As such, returned data could be different, or methods be deprecated / replaced anytime. Though it will be avoided if possible without affecting performance :)</p>
|
||||
<p>All the data showed on this site comes from the API, P2Pool or Monero. So if new features are added on site, it's possible to do so via the API.</p>
|
||||
<p>At the moment there is no broad rate-limiting. If abuse is detected, temporary rate limits can be put in place. You can always run your own instance of this site / API / consensus, see <a href="{%s p.Context().GetUrl("git.gammaspectra.live") %}/P2Pool/p2pool-observer">source code</a>.</p>
|
||||
<p>Some historical data could be incomplete. Some block records do not exist.</p>
|
||||
<p>In general, mandatory parameters are specified on the path, optional parameters on query.</p>
|
||||
<p><strong>Knowledge of P2Pool internals is strongly advised.</strong> Feel free to ask questions on IRC or Matrix.</p>
|
||||
<p>Unless specified, all returned data is JSON with status code 200. A not found error will issue a status code 404 and JSON of <code>{"error": "not_found"}</code> or similar.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Common types and fields</h3>
|
||||
<p>
|
||||
Fields within JSON objects may be omitted if marked as <em>omitempty</em> on its definition within the source code. An empty/zero representation is assumed.
|
||||
</br>
|
||||
For example, a types.Hash marked as <em>omitempty</em> would represent the value <span class="smaller mono">0000000000000000000000000000000000000000000000000000000000000000</span>.
|
||||
</br>
|
||||
For pointers/structs, the zero or nil value is considered empty. In lists, an empty / length of zero is assumed when empty.
|
||||
</br>
|
||||
Refer to source code to check the struct definition when in doubt.
|
||||
</p>
|
||||
|
||||
<h4>types.Hash</h4>
|
||||
<p>Represents a 256-bit hash value. This can refer to a block template id, main id, parents, transaction ids, proof of work hashes or any other value.</p>
|
||||
<p>This value is encoded as 64 hexadecimal characters.</p>
|
||||
|
||||
<h4>crypto.PublicKeyBytes / crypto.PrivateKeyBytes</h4>
|
||||
<p>Represents an <em>edwards25519</em> Point or Scalar in binary form. Encoded as a 256-bit value. This can refer to a public keys on a block, transaction keys or any other value.</p>
|
||||
<p>This value is encoded as 64 hexadecimal characters.</p>
|
||||
|
||||
<h4>address.Address</h4>
|
||||
<p>Represents a Monero address. Includes valid checksums.</p>
|
||||
<p>This value is encoded using special base58.</p>
|
||||
|
||||
<h4>types.Difficulty</h4>
|
||||
<p>Represents a 128-bit unsigned integer value. This can refer to weight, block difficulty or cumulative difficulty.</p>
|
||||
<p>This value is encoded as a 64-bit unsigned integer when the highest 64-bits are not set. Otherwise, it is encoded as 32 hexadecimal characters.</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/pool_info</h3>
|
||||
<p>Response contains general information on the current status of the P2Pool sidechain and Monero network, release versions, and other parameters.</p>
|
||||
<pre class="smaller">
|
||||
[Live result. Has been cut due to size, relevant portions shown]
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/pool_info
|
||||
{%= encodeJson(p.PoolInfoExample, true) %}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/miner_info/<id|address></h3>
|
||||
<p>Response contains general information on a specific miner, referred by its internal <em>id</em> or known Monero <em>address</em>.</p>
|
||||
<p>If you do not need the precise count of shares please pass the <em>?shareEstimates</em> parameter.</p>
|
||||
<pre class="smaller">
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/miner_info/47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw
|
||||
{
|
||||
"id": 3,
|
||||
"address": "47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"shares": [
|
||||
{
|
||||
"shares": 1022,
|
||||
"uncles": 0,
|
||||
"last_height": 4371641
|
||||
},
|
||||
{
|
||||
"shares": 559294,
|
||||
"uncles": 17861,
|
||||
"last_height": 4868281
|
||||
},
|
||||
{
|
||||
"shares": 69,
|
||||
"uncles": 0,
|
||||
"last_height": 4624653
|
||||
}
|
||||
],
|
||||
"last_share_height": 4868281,
|
||||
"last_share_timestamp": 1681256979
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/side_blocks_in_window?window=[window_size]&from=[height][&noMainStatus][&noUncles][&noMiners]</h3>
|
||||
<p>Response contains SideChain Blocks on specified range window. Optional parameters are <em>height</em>, denoting the Tip where the window gets measured from, and <em>window_size</em>, max SIDECHAIN_PPLNS_WINDOW * 4 * 7</p>
|
||||
<p>By default it will return current window blocks, from current tip.</p>
|
||||
<pre class="smaller">
|
||||
[result has been cut due to size, relevant portions shown]
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/side_blocks_in_window
|
||||
[
|
||||
{
|
||||
"main_id": "61c6463961c31584617f2ffd322fd32f91cef4a956b3ac431c128ab1ef8342c8",
|
||||
"main_height": 2863488,
|
||||
"template_id": "8782465759db40c11f64e78a26df4ec6221fb91202cc19a3beaef61acb8e1a50",
|
||||
"side_height": 4882215,
|
||||
"parent_template_id": "2ee8e55e2d0696d715049f5958c300c10b01d0a30b607c82afb8885a76f00d71",
|
||||
"miner": 3,
|
||||
"effective_height": 4882215,
|
||||
"nonce": 3691022871,
|
||||
"extra_nonce": 1665655721,
|
||||
"timestamp": 1681402700,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 294,
|
||||
"window_outputs": 30,
|
||||
"transaction_count": 2,
|
||||
"difficulty": 2107950958,
|
||||
"cumulative_difficulty": 7610896210501088,
|
||||
"pow_difficulty": 3167867138,
|
||||
"pow_hash": "3e19a8e6c1db97bae77fb19b68b5bf6c927e3b212063ee219e26155b01000000",
|
||||
"inclusion": 1,
|
||||
"miner_address": "47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"main_difficulty": 310626726029
|
||||
},
|
||||
{
|
||||
"main_id": "30e8afa1c6961afae7686f4c9c2ee5ad77bd87bdcbf7da00958af53b3349264a",
|
||||
"main_height": 2863488,
|
||||
"template_id": "2ee8e55e2d0696d715049f5958c300c10b01d0a30b607c82afb8885a76f00d71",
|
||||
"side_height": 4882214,
|
||||
"parent_template_id": "e0b745e8e3004832d95ef7eb6aa5f43dd0f86a491b6edb8ccd8e8dfbf92f6768",
|
||||
"miner": 2,
|
||||
"effective_height": 4882214,
|
||||
"nonce": 3791814950,
|
||||
"extra_nonce": 610282123,
|
||||
"timestamp": 1681402699,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 293,
|
||||
"window_outputs": 30,
|
||||
"transaction_count": 2,
|
||||
"difficulty": 2108162718,
|
||||
"cumulative_difficulty": 7610894102550130,
|
||||
"pow_difficulty": 5140029450,
|
||||
"pow_hash": "591082e76b158724c0a1a3ea8ffdec93eaa33a8d371eda2ad85ae9d500000000",
|
||||
"inclusion": 1,
|
||||
"miner_address": "435QwkC3fJ4YeX929gmaBMeuUwR1wdmRrQBNyoKfpJdHKDbqnukHJeA768yt8ngnS7UTHRJ2yJmnWP8gCz6s3kkiBX8C363",
|
||||
"uncles": [
|
||||
{
|
||||
"template_id": "4005ede80787679e83d269f09f6435ac4952d9c2cf2fadf999620ed81a948d40",
|
||||
"miner": 77,
|
||||
"side_height": 4882213,
|
||||
"difficulty": 2109635914
|
||||
}
|
||||
],
|
||||
"main_difficulty": 310626726029
|
||||
},
|
||||
{
|
||||
"main_id": "6d1865f59d8cd38f5de6a498a2a5465040194f63122187b6e439743913aac202",
|
||||
"main_height": 2863487,
|
||||
"template_id": "4005ede80787679e83d269f09f6435ac4952d9c2cf2fadf999620ed81a948d40",
|
||||
"side_height": 4882213,
|
||||
"parent_template_id": "985d621e565da6da1f772dffef4b1154678cc724b6b18eff6edde5e68136a45d",
|
||||
"miner": 77,
|
||||
"uncle_of": "2ee8e55e2d0696d715049f5958c300c10b01d0a30b607c82afb8885a76f00d71",
|
||||
"effective_height": 4882214,
|
||||
"nonce": 3326443597,
|
||||
"extra_nonce": 3803058762,
|
||||
"timestamp": 1681402680,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 294,
|
||||
"window_outputs": 31,
|
||||
"transaction_count": 0,
|
||||
"difficulty": 2109635914,
|
||||
"cumulative_difficulty": "0000000000000000001b0a106401568a",
|
||||
"pow_difficulty": 2153295646,
|
||||
"pow_hash": 7610889884751498,
|
||||
"inclusion": 1,
|
||||
"miner_address": "457fCKqWYSacjDRLjqah9kTga8KCQRNwL4WP7EfBNQKghYF3RZunjGHCoPSd6F7Y6mVRpT6eCgkEtin3cKvSCf11AqcP33g",
|
||||
"main_difficulty": 310568847048
|
||||
},
|
||||
{
|
||||
"main_id": "f5582e27b94a34713b607d0f47821142c8b88f8a3335d5b2b1d679c909a9651f",
|
||||
"main_height": 2863486,
|
||||
"template_id": "e0b745e8e3004832d95ef7eb6aa5f43dd0f86a491b6edb8ccd8e8dfbf92f6768",
|
||||
"side_height": 4882213,
|
||||
"parent_template_id": "985d621e565da6da1f772dffef4b1154678cc724b6b18eff6edde5e68136a45d",
|
||||
"miner": 18,
|
||||
"effective_height": 4882213,
|
||||
"nonce": 3506931857,
|
||||
"extra_nonce": 1625389436,
|
||||
"timestamp": 1681402674,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 294,
|
||||
"window_outputs": 31,
|
||||
"transaction_count": 115,
|
||||
"difficulty": 2109635914,
|
||||
"cumulative_difficulty": 7610889884751498,
|
||||
"pow_difficulty": 3604028547,
|
||||
"pow_hash": "1d01609ded322d6665fb96410042cc5cc9604be078b77967a818143101000000",
|
||||
"inclusion": 1,
|
||||
"miner_address": "4BBb6ZBPPPWivZnx2m3m7YUUGQv54F9HfjPWurf2Xy2UDQwwPyb956dGQ3p9whrxNJ568zc3Ygfr9McVT1UJyRKtLi1jcMb",
|
||||
"main_difficulty": 310847993727
|
||||
}
|
||||
]
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/side_blocks_in_window/<id|address>?window=[window_size]&from=[height][&noMainStatus][&noUncles][&noMiners]</h3>
|
||||
<p>Response contains SideChain Blocks on specified range window for a specific miner. Optional parameters are <em>height</em>, denoting the Tip where the window gets measured from, and <em>window_size</em>, max SIDECHAIN_PPLNS_WINDOW * 4 * 7</p>
|
||||
<p>By default it will return current window blocks, from current tip.</p>
|
||||
<pre class="smaller">
|
||||
[result has been cut due to size, relevant portions shown]
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/side_blocks_in_window/47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw
|
||||
[
|
||||
{
|
||||
"main_id": "b41a9e78f11b9f7eaed30a4211f43ae7201f7c77031839f3e484b59c32088028",
|
||||
"main_height": 2863492,
|
||||
"template_id": "ce8de3ca5a9a066c0b4f5d5068783b17ace476f56236c65f5c949b9039c0ca3e",
|
||||
"side_height": 4882250,
|
||||
"parent_template_id": "a0cb288e467f5a4069603f4047c8607f1721a4b62fc8e2f794a5a942828fdf32",
|
||||
"miner": 3,
|
||||
"effective_height": 4882250,
|
||||
"nonce": 2046825740,
|
||||
"extra_nonce": 1477203696,
|
||||
"timestamp": 1681403097,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 296,
|
||||
"window_outputs": 28,
|
||||
"transaction_count": 75,
|
||||
"difficulty": 2099479471,
|
||||
"cumulative_difficulty": 7610976044280876,
|
||||
"pow_difficulty": 2770364525,
|
||||
"pow_hash": "ea3d41d8874fb1de05aee76a0fb6df7267b07e34ee95cdf1f924e28c01000000",
|
||||
"inclusion": 1,
|
||||
"miner_address": "47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"main_difficulty": 311336952550
|
||||
},
|
||||
{
|
||||
"main_id": "fea4ee3769b350f2aad0571ff54eb555dbdf7f0cb811c19dea66bef286b041c4",
|
||||
"main_height": 2863492,
|
||||
"template_id": "74caf5d6ba885d05adbb4e4d611edabb55f0e05dcbb20cd3105d2adbcf4cdd1b",
|
||||
"side_height": 4882248,
|
||||
"parent_template_id": "3378e835d72a821b5157d8bebaf4f2193ebaf9cb86c93a106fc4fbbb1921c158",
|
||||
"miner": 3,
|
||||
"effective_height": 4882248,
|
||||
"nonce": 3456175326,
|
||||
"extra_nonce": 2803310737,
|
||||
"timestamp": 1681403084,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 296,
|
||||
"window_outputs": 28,
|
||||
"transaction_count": 73,
|
||||
"difficulty": 2103245329,
|
||||
"cumulative_difficulty": 7610971844410631,
|
||||
"pow_difficulty": 5249306449,
|
||||
"pow_hash": "4ab091d18ac65c212feff8f1a971aee1da50c177620827326b5d75d100000000",
|
||||
"inclusion": 1,
|
||||
"miner_address": "47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"main_difficulty": 311336952550
|
||||
},
|
||||
{
|
||||
"main_id": "277a19bf024b4895553bbefc047eeb7733b95a97df6d8422759ec874d2d626c1",
|
||||
"main_height": 2863492,
|
||||
"template_id": "52ad4bb20d62b0e9d7762bab8d27de05563be6147cf3367d9bc79688f794217c",
|
||||
"side_height": 4882246,
|
||||
"parent_template_id": "40865b24eb4fbd1428ff1444d21b4337c8922b60b63a3ab6bdfef2586775390b",
|
||||
"miner": 3,
|
||||
"effective_height": 4882246,
|
||||
"nonce": 754974898,
|
||||
"extra_nonce": 2953378118,
|
||||
"timestamp": 1681403082,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 296,
|
||||
"window_outputs": 28,
|
||||
"transaction_count": 72,
|
||||
"difficulty": 2104858109,
|
||||
"cumulative_difficulty": 7610967638144376,
|
||||
"pow_difficulty": 4514527717,
|
||||
"pow_hash": "fc127598502b38c4977f7faef3f7db6bab11bab42353baed9ab58cf300000000",
|
||||
"inclusion": 1,
|
||||
"miner_address": "47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"uncles": [
|
||||
{
|
||||
"template_id": "ab07fe1e4a6a00ae799bedc637c1b642b4938d302ec16db5b513923937614abe",
|
||||
"miner": 7,
|
||||
"side_height": 4882244,
|
||||
"difficulty": 2105207888
|
||||
}
|
||||
],
|
||||
"main_difficulty": 311336952550
|
||||
},
|
||||
{
|
||||
"main_id": "ff68eedc72252289e066d2dd59c209dc1502500d2cf4501fb69a91b69e2415b8",
|
||||
"main_height": 2863492,
|
||||
"template_id": "4764acc29d604f1491d600504d2802fea156a47ad691420b63d2c52924570925",
|
||||
"side_height": 4882241,
|
||||
"parent_template_id": "12f8b044e315669904f81caa055eac84f9a7d30d93bdd446cf304d38f18097cf",
|
||||
"miner": 3,
|
||||
"effective_height": 4882241,
|
||||
"nonce": 4026633700,
|
||||
"extra_nonce": 2755066571,
|
||||
"timestamp": 1681403011,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 296,
|
||||
"window_outputs": 29,
|
||||
"transaction_count": 47,
|
||||
"difficulty": 2099621378,
|
||||
"cumulative_difficulty": 7610955003350240,
|
||||
"pow_difficulty": 5985480395,
|
||||
"pow_hash": "6e0cc4b1e989a18a8f7a65c5aa5ad252a071cd57d877cac4f14bb2b700000000",
|
||||
"inclusion": 1,
|
||||
"miner_address": "47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"main_difficulty": 311336952550
|
||||
}
|
||||
]
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/payouts/<id|address>?search_limit=[search_limit]</h3>
|
||||
<p>Response contains payouts received by this miner due to blocks on Monero network. Optional parameters are <em>search_limit</em>, maximum amount of records to return, set 0 to return all. Defaults to 10</p>
|
||||
<p><em>.[].coinbase_private_key</em> can be used to prove the payout is correct.</p>
|
||||
<pre class="smaller">
|
||||
[result has been cut due to size, relevant portions shown]
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/payouts/47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw
|
||||
[
|
||||
{
|
||||
"miner": 3,
|
||||
"template_id": "cebb0c11be67c43c1a01cf2ac43e6bafe745a92012bbbb04be03842f56c5cc78",
|
||||
"side_height": 5428093,
|
||||
"main_id": "8e0c7b08f92be1bdc5c4153f7a8c3e536f1353aff898684477cc6ba857b8510e",
|
||||
"main_height": 2910747,
|
||||
"timestamp": 1687085695,
|
||||
"coinbase_id": "e1b20914f4ff768277c19daa65ef5f2ace7cda6f3f2586caf569c3008b7510b1",
|
||||
"coinbase_reward": 3400854816,
|
||||
"coinbase_private_key": "9438ef07d1bb10ae05e68cdf900938807fe8639acf7dab27feedc08119072b02",
|
||||
"coinbase_output_index": 26,
|
||||
"global_output_index": 75393989,
|
||||
"including_height": 5427726
|
||||
},
|
||||
{
|
||||
"miner": 3,
|
||||
"template_id": "9bab936c12790d7e006ef1e97bf00572e85eb2f5647ce6320491fc93ba47f6a0",
|
||||
"side_height": 5427897,
|
||||
"main_id": "8bf0f5f17c53a68e5fb36a89781acd84c5936c4b06287aad09c038dac8b52860",
|
||||
"main_height": 2910737,
|
||||
"timestamp": 1687083786,
|
||||
"coinbase_id": "413b6cac611c49d543d058a7d4ea908ba8a3774ced6781c2fdcdbbc0b187f24a",
|
||||
"coinbase_reward": 103013108631,
|
||||
"coinbase_private_key": "4344896bdacc7c58758090b955c7fd098f7414cdf773ec39fd12808f1fb4090f",
|
||||
"coinbase_output_index": 11,
|
||||
"global_output_index": 75393229,
|
||||
"including_height": 5427531
|
||||
},
|
||||
{
|
||||
"miner": 3,
|
||||
"template_id": "566257bf73bc937a7b5bb6c3b6c1a2008db078a56d4fcbb2e34bde6f2b847d55",
|
||||
"side_height": 5427764,
|
||||
"main_id": "d74f9c0201c248911d6c306f7e6b5d58dea469ecf40ffd891e59dd0f6d082aea",
|
||||
"main_height": 2910725,
|
||||
"timestamp": 1687082281,
|
||||
"coinbase_id": "561ed794fbdf5502fbd934fe7d7e4b9f4ccd4a8f7436fb93bc51c1e6dd431357",
|
||||
"coinbase_reward": 170359596687,
|
||||
"coinbase_private_key": "0332c944b6919f8e284993c35f810ce6b0c43cbf5f743f339fd4aaa197787109",
|
||||
"coinbase_output_index": 14,
|
||||
"global_output_index": 75392201,
|
||||
"including_height": 5427402
|
||||
}
|
||||
]
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/found_blocks?limit=[limit]&miner=[id|address]</h3>
|
||||
<p>Response contains last found block records on the Monero network by P2Pool. Optional parameters are <em>limit</em>, maximum amount of records to return, defaults to 50, max 1000. Additionally, you can pass the <em>miner</em> parameter to select blocks from that miner only. <em>coinbase</em> parameter can also be used here to decode coinbase outputs.</p>
|
||||
<pre class="smaller">
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/found_blocks?limit=2
|
||||
[
|
||||
{
|
||||
"main_block": {
|
||||
"id": "be284c7fc784ca83f92c2f1eac2e02524a99a63876179dae6c370a199d533d95",
|
||||
"height": 2942191,
|
||||
"timestamp": 1690866882,
|
||||
"reward": 600373170000,
|
||||
"coinbase_id": "691eb1e5b9222df2c667a481370acdd751c7f5f3cd0345ac9fc0f1feea396f7c",
|
||||
"difficulty": 260315236919,
|
||||
"side_template_id": "b76318caa4822826573ceebcabbd0309d2a710dd83700cb5d0cae206d9835cb4",
|
||||
"coinbase_private_key": "5cdea4c89482c19588527238d32bdad97151c4e40800882183222d080ff5f203"
|
||||
},
|
||||
"side_height": 5789985,
|
||||
"miner": 2915,
|
||||
"effective_height": 5789985,
|
||||
"window_depth": 438,
|
||||
"window_outputs": 41,
|
||||
"transaction_count": 2,
|
||||
"difficulty": 1301336116,
|
||||
"cumulative_difficulty": 9202373148639334,
|
||||
"inclusion": 1,
|
||||
"miner_address": "42HEEF3NM9cHkJoPpDhNyJHuZ6DFhdtymCohF9CwP5KPM1Mp3eH2RVXCPRrxe4iWRogT7299R8PP7drGvThE8bHmRDq1qWp"
|
||||
},
|
||||
{
|
||||
"main_block": {
|
||||
"id": "ac6ae3cfe0f241e22aae6b7a165e7254f380d50c5a690994aa462120c00c4f16",
|
||||
"height": 2942190,
|
||||
"timestamp": 1690866866,
|
||||
"reward": 608015760000,
|
||||
"coinbase_id": "a7f784cc724f9e172d3bea13a18569b339e96d218b3fa54f9189abacbeb349ae",
|
||||
"difficulty": 260396879898,
|
||||
"side_template_id": "be9e779f4f8b2cd96c6e30a94c28c6d36af261ecf7cc195394471f112b850c49",
|
||||
"coinbase_private_key": "896cde95bf6713d60b7ed4bafa88018e7418cd55db9232db08dc2de174cebd0b"
|
||||
},
|
||||
"side_height": 5789983,
|
||||
"miner": 2915,
|
||||
"effective_height": 5789983,
|
||||
"window_depth": 438,
|
||||
"window_outputs": 41,
|
||||
"transaction_count": 49,
|
||||
"difficulty": 1301263505,
|
||||
"cumulative_difficulty": 9202370546222535,
|
||||
"inclusion": 1,
|
||||
"miner_address": "42HEEF3NM9cHkJoPpDhNyJHuZ6DFhdtymCohF9CwP5KPM1Mp3eH2RVXCPRrxe4iWRogT7299R8PP7drGvThE8bHmRDq1qWp"
|
||||
}
|
||||
]
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/shares?limit=[limit]&miner=[id|address][&onlyBlocks][&noMainStatus][&noUncles][&noMiners]</h3>
|
||||
<p>Response contains last found SideChain block records on P2Pool. Optional parameters are <em>limit</em>, maximum amount of records to return, defaults to 50, max SIDECHAIN_PPLNS_WINDOW. Additionally, you can pass the <em>miner</em> parameter to select shares from that miner only. <em>onlyBlocks</em> can be set to not receive uncle share entries.</p>
|
||||
<pre class="smaller">
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/shares?limit=2
|
||||
[
|
||||
{
|
||||
"main_id": "7717e2e59fb345ce3e5c5585b51632f32db2e98bb0a69b64a0b91f7b370be3b7",
|
||||
"main_height": 2942216,
|
||||
"template_id": "7f0ce404ab3d576d97dc4affacb5e443fb4c1f36f1bb09feed5b9af3dcca0169",
|
||||
"side_height": 5790240,
|
||||
"parent_template_id": "7070785f6ac7c486ab2ae7df443e6afc31010f87206b44c90be1339cd2ecfc70",
|
||||
"miner": 2915,
|
||||
"effective_height": 5790240,
|
||||
"nonce": 1660977225,
|
||||
"extra_nonce": 2706560389,
|
||||
"timestamp": 1690869415,
|
||||
"software_id": 0,
|
||||
"software_version": 196613,
|
||||
"window_depth": 424,
|
||||
"window_outputs": 40,
|
||||
"difficulty": 1327149158,
|
||||
"cumulative_difficulty": 9202726518962104,
|
||||
"pow_difficulty": 1720691274,
|
||||
"pow_hash": "36685a8106e8b6a7450820f97cba0fa3cd2aa017fb4119f3737ffe7e02000000",
|
||||
"inclusion": 1,
|
||||
"transaction_count": 9,
|
||||
"miner_address": "42HEEF3NM9cHkJoPpDhNyJHuZ6DFhdtymCohF9CwP5KPM1Mp3eH2RVXCPRrxe4iWRogT7299R8PP7drGvThE8bHmRDq1qWp",
|
||||
"main_difficulty": 260565791438
|
||||
},
|
||||
{
|
||||
"main_id": "9ce6483c303ada746b9e969c65235bcba0433865f6afd11153e94fb0e6d41046",
|
||||
"main_height": 2942216,
|
||||
"template_id": "7070785f6ac7c486ab2ae7df443e6afc31010f87206b44c90be1339cd2ecfc70",
|
||||
"side_height": 5790239,
|
||||
"parent_template_id": "d7477275674bd90bba39e4604cf2539ae99460856567895369cda165370ba081",
|
||||
"miner": 5695,
|
||||
"effective_height": 5790239,
|
||||
"nonce": 460496,
|
||||
"extra_nonce": 346986124,
|
||||
"timestamp": 1690869412,
|
||||
"software_id": 0,
|
||||
"software_version": 196612,
|
||||
"window_depth": 425,
|
||||
"window_outputs": 40,
|
||||
"difficulty": 1326689571,
|
||||
"cumulative_difficulty": 9202725191812946,
|
||||
"pow_difficulty": 1929563612,
|
||||
"pow_hash": "1ba49ad252103dc9e5eba606a3c1fac0875e01b6163201d6b6f0d23902000000",
|
||||
"inclusion": 1,
|
||||
"transaction_count": 9,
|
||||
"miner_address": "48bSCfGTKE4Kb3Fe3YWYgmNjgxjGqxZsP4mwHEQDVUQST2HxRGEX2566LbfmBE5bW2JcByTnjsWZiQZWj6f8XSpX4EDE9Gj",
|
||||
"main_difficulty": 260565791438
|
||||
}
|
||||
]
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/block_by_id/<blockId>[/full|/light|/raw|/info|/payouts|/coinbase]</h3>
|
||||
<p>Response contains a block/share record on P2Pool by Template, Main Id, or Coinbase Id. You can select the JSON version or the binary format, or the full/light JSON format of the raw share.</p>
|
||||
<p>Using <em>/payouts</em> you can query all payouts that include weights by this share.</p>
|
||||
<p>Using <em>/coinbase</em> you can query all possible outputs and ownership for found and not found blocks.</p>
|
||||
<pre class="smaller">
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/block_by_id/179cf9d77c7d198dc4d9a5deb03561b6121336fe9ea72fbda00fbf6eea34a34d
|
||||
{
|
||||
"main_id": "abf18da3388c1c5c8ca372ac4804c6b1b2adb511d6a6cc5f781d883211568eb0",
|
||||
"main_height": 2863113,
|
||||
"template_id": "179cf9d77c7d198dc4d9a5deb03561b6121336fe9ea72fbda00fbf6eea34a34d",
|
||||
"side_height": 4877733,
|
||||
"parent_template_id": "ffd6a96ce3131f043f273582a69d1eaa42b7557f47f7b241140cb22af9fbf556",
|
||||
"miner": 3,
|
||||
"effective_height": 4877733,
|
||||
"nonce": 1694662668,
|
||||
"extra_nonce": 2797508428,
|
||||
"timestamp": 1681355864,
|
||||
"software_id": 0,
|
||||
"software_version": 196610,
|
||||
"window_depth": 327,
|
||||
"window_outputs": 33,
|
||||
"difficulty": 1956175240,
|
||||
"cumulative_difficulty": 7601017513575704,
|
||||
"pow_difficulty": 1301275836828,
|
||||
"pow_hash": "602d39809af709405eb8660028fdfa5bf80e71e6a4ec1fbb924ed80000000000",
|
||||
"inclusion": 1,
|
||||
"transaction_count": 5,
|
||||
"mined_main_at_height": true,
|
||||
"miner_address": "47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"main_difficulty": 332971955880
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
<div>
|
||||
<h3 class="mono">/api/block_by_height/<blockHeight>[/full|/light|/raw|/info|/payouts|/coinbase]</h3>
|
||||
<p>Response contains a block/share record on P2Pool by SideChain height. You can select the JSON version or the binary format, or the full/light JSON format of the raw share.</p>
|
||||
<p>Using <em>/payouts</em> you can query all payouts that include weights by this share.</p>
|
||||
<p>Using <em>/coinbase</em> you can query all possible outputs and ownership for found and not found blocks.</p>
|
||||
<pre class="smaller">
|
||||
curl --silent https://{%s p.Context().NetServiceAddress %}/api/block_by_height/4868351
|
||||
{
|
||||
"main_id": "5b468e062dc4aa1570f956e6314f6d0634a772aa8d2905d8ab9c83026a7ac6af",
|
||||
"main_height": 2862309,
|
||||
"template_id": "09d1974eea6de73d3cb95bc495b0b24a7b6f6405eaada53c4ec1ad3f513a4cc8",
|
||||
"side_height": 4868351,
|
||||
"parent_template_id": "b74acca03b7593386dd54b8132bf79522c9861b41f7797d0ce4f2af5f35cc170",
|
||||
"miner": 1,
|
||||
"effective_height": 4868351,
|
||||
"nonce": 3271624915,
|
||||
"extra_nonce": 1072029533,
|
||||
"timestamp": 1681257577,
|
||||
"software_id": 0,
|
||||
"software_version": 196609,
|
||||
"window_depth": 323,
|
||||
"window_outputs": 30,
|
||||
"difficulty": 2002581944,
|
||||
"cumulative_difficulty": 7579715115168858,
|
||||
"pow_difficulty": 3782914221,
|
||||
"pow_hash": "fe471ad3fbd112fabe2abebafeccd2cb8eb0077864e7cfe939eaa62201000000",
|
||||
"inclusion": 1,
|
||||
"transaction_count": 28,
|
||||
"miner_address": "4ApeMQzcRS7huBW8HEGkug2e5kQxhhC2Ub9y8F9bc7arKHsCktAV24n5ezKM9PjX5yS3Pv4PPNMXT8rTAFVuKCRi1jEqJZd",
|
||||
"main_difficulty": 326506434325
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
<hr/>
|
||||
<div>
|
||||
<h3><span class="mono">/api/events</span> WebSocket Event API</h3>
|
||||
<p>
|
||||
Connect to this using a WebSocket library. Produces a stream of JSON events for new blocks (type <em>side_block</em>), found blocks (type <em>found_block</em>), orphaned blocks (type <em>orphaned_block</em>).
|
||||
<pre class="smaller">
|
||||
[result has been cut due to size, relevant portions shown]
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"77d54ef67b566439c7df43614928ee6a503d82fb16f69720ed2ca0852b4ea5b6",
|
||||
"main_height":2871492,
|
||||
"template_id":"7c5db68e5af86e66df94d03688a8f61d7e7cd4544dbadc3f4d2f7972c0c72b17",
|
||||
"side_height":4973833,
|
||||
"parent_template_id":"10e2b50eb5c688bea4852f25b0d3550fe0e4e8192ea0602a373fb48454496e20",
|
||||
"miner":19,
|
||||
"uncle_of":"372ee3a80d8ab9131878a56e5e2802aa3f797a55d38f76871f1eeeafd60cd828",
|
||||
"effective_height":4973834,
|
||||
"nonce":4127591028,
|
||||
"extra_nonce":3737539655,
|
||||
"timestamp":1682358001,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":333,
|
||||
"window_outputs":31,
|
||||
"transaction_count":2,
|
||||
"difficulty":1902041368,
|
||||
"cumulative_difficulty":"0000000000000000001ba95c40814041",
|
||||
"pow_difficulty":39725018671,
|
||||
"pow_hash":"f6ddc7bdb80b6050fc7dd9fea1ad009434b7bcda3e3cb309a295ad1b00000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"4B4mXWBqrFYGk9W8t1J3UifSdnU723HHKf8VaWGSZGzLZUncHRJetPCjbDUha1udPd2ZpJrwYCmPv1jzvg3NCf7Z1YViuoa",
|
||||
"main_difficulty":344857075709
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"808c9c2a413b8480cc5b445575580a6edd5d237cc120f18a668c6f602dff9c1b",
|
||||
"main_height":2871492,
|
||||
"template_id":"372ee3a80d8ab9131878a56e5e2802aa3f797a55d38f76871f1eeeafd60cd828",
|
||||
"side_height":4973834,
|
||||
"parent_template_id":"8d691190875157d160c8bc8787ab140b1855c1afcc7b8f3076bc3366711bb085",
|
||||
"miner":2,
|
||||
"effective_height":4973834,
|
||||
"nonce":402718979,
|
||||
"extra_nonce":3611692200,
|
||||
"timestamp":1682358008,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":2,
|
||||
"difficulty":1900059444,
|
||||
"cumulative_difficulty":"0000000000000000001ba95d2320b48d",
|
||||
"pow_difficulty":4388053454,
|
||||
"pow_hash":"727019817d63c52ac2ce2067652804cbfc8d3be778fe5548d0bf91fa00000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"435QwkC3fJ4YeX929gmaBMeuUwR1wdmRrQBNyoKfpJdHKDbqnukHJeA768yt8ngnS7UTHRJ2yJmnWP8gCz6s3kkiBX8C363",
|
||||
"uncles":[
|
||||
{
|
||||
"template_id":"7c5db68e5af86e66df94d03688a8f61d7e7cd4544dbadc3f4d2f7972c0c72b17",
|
||||
"miner":19,
|
||||
"side_height":4973833,
|
||||
"difficulty":1902041368
|
||||
}
|
||||
],
|
||||
"main_difficulty":344857075709
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"220534c981f71a7d2d935825b5cf479d960345177387eb7fcd196a762b018453",
|
||||
"main_height":2871492,
|
||||
"template_id":"320a1a1d8849d48243670938efa640a271b289fae392ea35eb1a7daf88d3e2c8",
|
||||
"side_height":4973835,
|
||||
"parent_template_id":"372ee3a80d8ab9131878a56e5e2802aa3f797a55d38f76871f1eeeafd60cd828",
|
||||
"miner":3,
|
||||
"effective_height":4973835,
|
||||
"nonce":3825436994,
|
||||
"extra_nonce":2417583792,
|
||||
"timestamp":1682358009,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":3,
|
||||
"difficulty":1899187725,
|
||||
"cumulative_difficulty":"0000000000000000001ba95d9454029a",
|
||||
"pow_difficulty":4870458263,
|
||||
"pow_hash":"d6b5911b6b13e6258e2f1d23ab5256cf9b3c4a002e0722f15b4cc0e100000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"main_difficulty":344857075709
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"3e735ce653c2f1c312125309367ad6016e3f55c80834efd63fdb94d0929d017a",
|
||||
"main_height":2871492,
|
||||
"template_id":"3f089efbc7fd31eb98705665acbbe1e7d3b290866cb94f39a0d2e072f2a11f14",
|
||||
"side_height":4973836,
|
||||
"parent_template_id":"320a1a1d8849d48243670938efa640a271b289fae392ea35eb1a7daf88d3e2c8",
|
||||
"miner":9,
|
||||
"effective_height":4973836,
|
||||
"nonce":132358,
|
||||
"extra_nonce":1448692177,
|
||||
"timestamp":1682358019,
|
||||
"software_id":0,
|
||||
"software_version":196609,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":7,
|
||||
"difficulty":1896666403,
|
||||
"cumulative_difficulty":"0000000000000000001ba95e0560d7bd",
|
||||
"pow_difficulty":15371353282,
|
||||
"pow_hash":"cf9774d250b905729cb988b24437ac5f27ccc55d78d6388ca7a8874700000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"4AxaapsBMRYMJHFV5uApnFJth7stf4GiK9sqnLAH8SizguBbZ8YYtE2C3aF3UGVbh3ATRKdtd7ai1Hi1zfzMkdqmAZLqaGN",
|
||||
"main_difficulty":344857075709
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"3ecbfae652abf9ecfd043d8921b3c18f9acc6f08a8521bd4a06c46b8ff5181f9",
|
||||
"main_height":2871492,
|
||||
"template_id":"ba8514bd7403c16a61b7e20f34981d9cb72de09a7b0593d55ef14d55bff1908e",
|
||||
"side_height":4973837,
|
||||
"parent_template_id":"3f089efbc7fd31eb98705665acbbe1e7d3b290866cb94f39a0d2e072f2a11f14",
|
||||
"miner":2,
|
||||
"effective_height":4973837,
|
||||
"nonce":1879082776,
|
||||
"extra_nonce":1309685883,
|
||||
"timestamp":1682358030,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":8,
|
||||
"difficulty":1897117455,
|
||||
"cumulative_difficulty":"0000000000000000001ba95e76748ecc",
|
||||
"pow_difficulty":4828639998,
|
||||
"pow_hash":"979dbacd27e0c15bfa93a72bda30f9e4e472ab86cfc9bc5f71ceb4e300000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"435QwkC3fJ4YeX929gmaBMeuUwR1wdmRrQBNyoKfpJdHKDbqnukHJeA768yt8ngnS7UTHRJ2yJmnWP8gCz6s3kkiBX8C363",
|
||||
"main_difficulty":344857075709
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"f7aab8fda85491a5b3847435fa16ae925d55b60791c01faf5a23c6d5365ef6ca",
|
||||
"main_height":2871492,
|
||||
"template_id":"19278cd3a3022a25edf4b91a5544a88b8c8f6f78f84e862b01781de493b562c8",
|
||||
"side_height":4973838,
|
||||
"parent_template_id":"ba8514bd7403c16a61b7e20f34981d9cb72de09a7b0593d55ef14d55bff1908e",
|
||||
"miner":3,
|
||||
"effective_height":4973838,
|
||||
"nonce":2868937852,
|
||||
"extra_nonce":869103007,
|
||||
"timestamp":1682358036,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":8,
|
||||
"difficulty":1894821734,
|
||||
"cumulative_difficulty":"0000000000000000001ba95ee7653e32",
|
||||
"pow_difficulty":2019716446,
|
||||
"pow_hash":"2d03c8818798cb1bde4a6d2d64d0a59a04b8228e94aff841599c632002000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"main_difficulty":344857075709
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"877b4df53e2347e19d94ba52e04522601d461c0dc5d0439e8ec87a5ecb58a31f",
|
||||
"main_height":2871492,
|
||||
"template_id":"000c39fc469e4062fe4da6fc8444c3c990ad817397c94abe13c46720a4c9ab75",
|
||||
"side_height":4973839,
|
||||
"parent_template_id":"19278cd3a3022a25edf4b91a5544a88b8c8f6f78f84e862b01781de493b562c8",
|
||||
"miner":2,
|
||||
"effective_height":4973839,
|
||||
"nonce":822086856,
|
||||
"extra_nonce":1665899192,
|
||||
"timestamp":1682358038,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":10,
|
||||
"difficulty":1893298342,
|
||||
"cumulative_difficulty":"0000000000000000001ba95f583eaed8",
|
||||
"pow_difficulty":29696623614,
|
||||
"pow_hash":"b1aa60a163d39b0524f7448954f770de8cbb873e346b63e37659062500000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"435QwkC3fJ4YeX929gmaBMeuUwR1wdmRrQBNyoKfpJdHKDbqnukHJeA768yt8ngnS7UTHRJ2yJmnWP8gCz6s3kkiBX8C363",
|
||||
"main_difficulty":344857075709
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"b6516743e3693824f0d8eceab7109c4242661f78e498f78e42a57bd602b7b167",
|
||||
"main_height":2871493,
|
||||
"template_id":"af88e464df1f31761996031f5c851f1a59d6ecb4b6e917407e1164ce6a569a07",
|
||||
"side_height":4973840,
|
||||
"parent_template_id":"000c39fc469e4062fe4da6fc8444c3c990ad817397c94abe13c46720a4c9ab75",
|
||||
"miner":19,
|
||||
"effective_height":4973840,
|
||||
"nonce":1543602719,
|
||||
"extra_nonce":2911703055,
|
||||
"timestamp":1682358053,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":0,
|
||||
"difficulty":1893967043,
|
||||
"cumulative_difficulty":"0000000000000000001ba95fc922539b",
|
||||
"pow_difficulty":1157101996504,
|
||||
"pow_hash":"d855eed5d88837eb44c3d0a80457981fd39e9af67e8b58a03142f30000000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"4B4mXWBqrFYGk9W8t1J3UifSdnU723HHKf8VaWGSZGzLZUncHRJetPCjbDUha1udPd2ZpJrwYCmPv1jzvg3NCf7Z1YViuoa",
|
||||
"mined_main_at_height":true,
|
||||
"main_difficulty":344633368046
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"found_block",
|
||||
"found_block":{
|
||||
"main_block":{
|
||||
"id":"b6516743e3693824f0d8eceab7109c4242661f78e498f78e42a57bd602b7b167",
|
||||
"height":2871493,
|
||||
"timestamp":1682358053,
|
||||
"reward":600000000000,
|
||||
"coinbase_id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"difficulty":344633368046,
|
||||
"side_template_id":"af88e464df1f31761996031f5c851f1a59d6ecb4b6e917407e1164ce6a569a07",
|
||||
"coinbase_private_key":"1331ab860507192987e98497b971daac66e8804c51ad115ab6b2bf3d2abd270d"
|
||||
},
|
||||
"side_height":4973840,
|
||||
"miner":19,
|
||||
"effective_height":4973840,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":0,
|
||||
"difficulty":1893967043,
|
||||
"cumulative_difficulty":"0000000000000000001ba95fc922539b",
|
||||
"inclusion":1,
|
||||
"miner_address":"4B4mXWBqrFYGk9W8t1J3UifSdnU723HHKf8VaWGSZGzLZUncHRJetPCjbDUha1udPd2ZpJrwYCmPv1jzvg3NCf7Z1YViuoa"
|
||||
},
|
||||
"main_coinbase_outputs":[
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":0,
|
||||
"global_output_index":72132937,
|
||||
"miner":30,
|
||||
"value":1747977812,
|
||||
"miner_address":"47J5KAEgevN8TKMhstn85mbASkhpZq5BtSt9afcseHPEVtDggZ8uBdxgoTRjdP2ooS4Px1y1pXeryhr9UFQqaykY5J4Ev85"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":1,
|
||||
"global_output_index":72132938,
|
||||
"miner":195,
|
||||
"value":1736642632,
|
||||
"miner_address":"47goCBjSdpqAGUFkc1nM9F7gNcSfSjtKuTqcLxGpEUn6DgboSZMpn2CMRkQ2JKWbxRJvHy2fbSJxtFqXVDbQqs5wPpxgbYD"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":2,
|
||||
"global_output_index":72132939,
|
||||
"miner":10,
|
||||
"value":1737989471,
|
||||
"miner_address":"46vavwU3QDmK2Jdw6CgezsB5538rbwFGi67EPWqkEBfvWuuiFTNaPVQPNSbxVc1UNbN6E7xakVkCxCnttvgyohaN4U9F7kf"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":3,
|
||||
"global_output_index":72132940,
|
||||
"miner":1,
|
||||
"value":3139170892,
|
||||
"miner_address":"4ApeMQzcRS7huBW8HEGkug2e5kQxhhC2Ub9y8F9bc7arKHsCktAV24n5ezKM9PjX5yS3Pv4PPNMXT8rTAFVuKCRi1jEqJZd"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":4,
|
||||
"global_output_index":72132941,
|
||||
"miner":47,
|
||||
"value":1751320020,
|
||||
"miner_address":"47Eqp7fsvVnPPSU4rsXrKJhyAme6LhDRZDzFky9xWsWUS9pd6FPjJCMDCNX1NnNiDzTwfbAgGMk2N6A1aucNcrkhLffta1p"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":5,
|
||||
"global_output_index":72132942,
|
||||
"miner":26,
|
||||
"value":6966089461,
|
||||
"miner_address":"42yAHAH5at9ZHvMXvb1xPV2aKnoibjZWCfTgQg2HdxumFx857g3DK75GbDKzmvu8QMW8CnWxuPAYyfQADjwKw6dy78x2gbp"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":6,
|
||||
"global_output_index":72132943,
|
||||
"miner":6,
|
||||
"value":1739307539,
|
||||
"miner_address":"4AzyJ87Tp3yLXjA3E5v9iAEnTTLu6UTeSdatiwto92n91z7NE11QmpV4YGatwS3ihvFsmvTfz85bfKMLBBAsZMGFKcJJ8jG"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":7,
|
||||
"global_output_index":72132944,
|
||||
"miner":18,
|
||||
"value":11494230879,
|
||||
"miner_address":"4BBb6ZBPPPWivZnx2m3m7YUUGQv54F9HfjPWurf2Xy2UDQwwPyb956dGQ3p9whrxNJ568zc3Ygfr9McVT1UJyRKtLi1jcMb"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":8,
|
||||
"global_output_index":72132945,
|
||||
"miner":19,
|
||||
"value":39395535008,
|
||||
"miner_address":"4B4mXWBqrFYGk9W8t1J3UifSdnU723HHKf8VaWGSZGzLZUncHRJetPCjbDUha1udPd2ZpJrwYCmPv1jzvg3NCf7Z1YViuoa"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":9,
|
||||
"global_output_index":72132946,
|
||||
"miner":842,
|
||||
"value":3477271817,
|
||||
"miner_address":"457Ht8LpLt2iq8y5HShzySduWTC58JvGLj6eWiURzUxNVXrDBWJc9jd3niHHLZ4o6hHYpufyd8zhL9EMktn3p3EdHQ6LAkj"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":10,
|
||||
"global_output_index":72132947,
|
||||
"miner":1104,
|
||||
"value":1746268557,
|
||||
"miner_address":"48LszLe98KgS1H4ELW9rmEH6dTar1uWdZDx7ttDHNSZvcANADw9PUbDU3KWREmgozxMbzofNmj8m5ZuieukEsj7aHjfo3in"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":11,
|
||||
"global_output_index":72132948,
|
||||
"miner":445,
|
||||
"value":1748663264,
|
||||
"miner_address":"44XavwVX8pP2U5NxtwGusU2obpoK55nNVBBqWLC2aJBX2yhAozsC1tvVG8NcC2PEQy2SRmBuB4WqTfVgfBCS3NkY1wYHZ3T"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":12,
|
||||
"global_output_index":72132949,
|
||||
"miner":156,
|
||||
"value":1755605809,
|
||||
"miner_address":"42FUiB6725E4UDkVXMJ3nZ21dhV9atHP9Z1yqxMGfNhnDkJUhdxDaTGaCej35H346eS5vcbEVY4kzdLVhKM63LjNJxFYMUF"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":13,
|
||||
"global_output_index":72132950,
|
||||
"miner":23,
|
||||
"value":1749533284,
|
||||
"miner_address":"46aSNdCK35w9fdX3iCwk3GLt3iZ4hnR34j2mJtxE14MWLXB5JRXxvzmjMUoz2mmFZk9mkqQRpCbPd2TbFqcQxqGkHxJPHUK"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":14,
|
||||
"global_output_index":72132951,
|
||||
"miner":61,
|
||||
"value":3491378711,
|
||||
"miner_address":"44YdBost8RfFjVT9W712c5KN1ZeTq5rPC6Uu12D4nBkUMBHxeb7TxKfGnAGLbfsvbaKtBvwzqKLqBWf7cpU7d2nZ55pXeCU"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":15,
|
||||
"global_output_index":72132952,
|
||||
"miner":647,
|
||||
"value":24776511644,
|
||||
"miner_address":"4AQ3YkqG2XdWsPHEgrDGdyQLq1qMMGFqWTFJfrVQW99qPmCzZKvJqzxgf5342KC17o9bchfJcUzLhVW9QgNKTYUBLg876Gt"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":16,
|
||||
"global_output_index":72132953,
|
||||
"miner":11,
|
||||
"value":1739154107,
|
||||
"miner_address":"42safVv7VKVLrGc1RWEgVqi9YJRV8XiUeFViStfLPWE8FAH1i4HvovcXntT8k5MvVuKkpYLcjELwL4Azrcb65MZC2AkoDoJ"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":17,
|
||||
"global_output_index":72132954,
|
||||
"miner":4,
|
||||
"value":82984625360,
|
||||
"miner_address":"4ADyLD6c7hzT5gCYxWnw7k2q5RPDxwqNTMsHcz9n91ir22YJbjBQ4LjGZgcLUVV7tyG33vXfB5SR1iCWaCjEQ7BNJUg6SyJ"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":18,
|
||||
"global_output_index":72132955,
|
||||
"miner":111,
|
||||
"value":1741890162,
|
||||
"miner_address":"45vMeqsvpW5UoQ4HQDjbUoLpmrLkhyVKgiSeR9WwuCpLV9egZtkwGGYPJ5HDvdAVtgF4bcu93dzEc6mLFbYRqVJzAQWHRnL"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":19,
|
||||
"global_output_index":72132956,
|
||||
"miner":8,
|
||||
"value":5240626741,
|
||||
"miner_address":"47oeGVdUWXDPSqjgFd2dQS57S3W4KPGnneq1o8hAdGE2ZwMZzKHeLFhiQoguKMFq4jScJ2qL7jrHoiP8xh2Su6SuFtxk3Da"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":20,
|
||||
"global_output_index":72132957,
|
||||
"miner":5283,
|
||||
"value":1750270617,
|
||||
"miner_address":"45j2xT1JbU27Mv8ScdumCC5bg3P9EU1HEE2DKZ9TzVje6wVE8kKqviwFuvwaoNywwzh7uiJoxfpWe4jp5HvYDZgyKdU2iEt"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":21,
|
||||
"global_output_index":72132958,
|
||||
"miner":179,
|
||||
"value":1749846757,
|
||||
"miner_address":"42TUuapMMbs4taYHYFnFTeFAM57WzVKDNXn4yeAQcJcgHq1m6q993EedsF2ebd7WP5Gjixw3NPu6ncH7WFWfsPDiPSWEnr9"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":22,
|
||||
"global_output_index":72132959,
|
||||
"miner":639,
|
||||
"value":3497561198,
|
||||
"miner_address":"49bNJ3mdVBfGjr3vWTVhXNig75gCxHQCwNG2FyvRErxs7U4A1c8K6Pn39Tq8oZAxtbXsfNsBtyKzT8apMkYD67QTS2v42oW"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":23,
|
||||
"global_output_index":72132960,
|
||||
"miner":15,
|
||||
"value":5247959737,
|
||||
"miner_address":"44N5jui2nbtQaxWjs9hoJBX5ELrCk5ftja7Zu3QCGTA1FftJhDKrEDL92Rw12j8FALHKnJEUb6KZ45SAncJ6iqKN6nShj2S"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":24,
|
||||
"global_output_index":72132961,
|
||||
"miner":2161,
|
||||
"value":1750436503,
|
||||
"miner_address":"44WHz1PGu8mYWxVE1p6vVtXDtvKtkd7eY23DdXncavA9Ybb6kP9i6BrhZschXpxGCHdTfBoLDznv55SYUqDhoCyJJxV84tD"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":25,
|
||||
"global_output_index":72132962,
|
||||
"miner":91,
|
||||
"value":2100792894,
|
||||
"miner_address":"48tdvJBtQN5Vt3jmLDTTYX7dpS9oDXATAdD8XFhuE1CshWUdShUsEif6UivD5iGsroKGEKqxGqA1aFA2XVx8rtmiFAqKT6E"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":26,
|
||||
"global_output_index":72132963,
|
||||
"miner":2,
|
||||
"value":125897735171,
|
||||
"miner_address":"435QwkC3fJ4YeX929gmaBMeuUwR1wdmRrQBNyoKfpJdHKDbqnukHJeA768yt8ngnS7UTHRJ2yJmnWP8gCz6s3kkiBX8C363"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":27,
|
||||
"global_output_index":72132964,
|
||||
"miner":5190,
|
||||
"value":1741473336,
|
||||
"miner_address":"49trSMSTsAmE2uMeCo1MmEUvfk6i9DWF15skLn9hut11ehcq1vLHkYi9sTujrcDG61dLFk61LeWMafa3EkamaGEUD1fSvRe"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":28,
|
||||
"global_output_index":72132965,
|
||||
"miner":9,
|
||||
"value":50246935661,
|
||||
"miner_address":"4AxaapsBMRYMJHFV5uApnFJth7stf4GiK9sqnLAH8SizguBbZ8YYtE2C3aF3UGVbh3ATRKdtd7ai1Hi1zfzMkdqmAZLqaGN"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":29,
|
||||
"global_output_index":72132966,
|
||||
"miner":4990,
|
||||
"value":15680539194,
|
||||
"miner_address":"4B4aCvEcZr6GcusVJfEds2LXixCeJ2dQBaDUCguWmzi5L7PW5tVXfAnE4cn1mQdiNzH6zWcEPMQTiYTsNcX44ryxCJWZKZH"
|
||||
},
|
||||
{
|
||||
"id":"9ba3e0449f6ff3a108a877f2f37eedf0b87f811ff5725634e109ba0d2789d479",
|
||||
"index":30,
|
||||
"global_output_index":72132967,
|
||||
"miner":3,
|
||||
"value":190176655762,
|
||||
"miner_address":"47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"5594278518615f55a7e911cca93280fd0c3d2dff1d1fd184db6602e0afb69e0b",
|
||||
"main_height":2871494,
|
||||
"template_id":"0ef3e5ddcb0c4f6398eb833361b3749f92fc232a049c52052c5da956c9bbc868",
|
||||
"side_height":4973841,
|
||||
"parent_template_id":"af88e464df1f31761996031f5c851f1a59d6ecb4b6e917407e1164ce6a569a07",
|
||||
"miner":4,
|
||||
"effective_height":4973841,
|
||||
"nonce":2466322468,
|
||||
"extra_nonce":893603150,
|
||||
"timestamp":1682358055,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":0,
|
||||
"difficulty":1894526953,
|
||||
"cumulative_difficulty":"0000000000000000001ba9603a0e8384",
|
||||
"pow_difficulty":11901446960,
|
||||
"pow_hash":"bbe4a67f6a9bca4028ca1faa940d692f834978d10ed18e34d07b625c00000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"4ADyLD6c7hzT5gCYxWnw7k2q5RPDxwqNTMsHcz9n91ir22YJbjBQ4LjGZgcLUVV7tyG33vXfB5SR1iCWaCjEQ7BNJUg6SyJ",
|
||||
"main_difficulty":344205037143
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"6c50a9aa027a09b41c56f965f8e21041649c1638a956576fc3fc350441e0f4ab",
|
||||
"main_height":2871494,
|
||||
"template_id":"5f72a35b571746ac3835b204020e548e65a7350d84d5d7a7aa3a749dc058f7ad",
|
||||
"side_height":4973842,
|
||||
"parent_template_id":"0ef3e5ddcb0c4f6398eb833361b3749f92fc232a049c52052c5da956c9bbc868",
|
||||
"miner":19,
|
||||
"effective_height":4973842,
|
||||
"nonce":1090620480,
|
||||
"extra_nonce":162834581,
|
||||
"timestamp":1682358073,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":1,
|
||||
"difficulty":1893992163,
|
||||
"cumulative_difficulty":"0000000000000000001ba960aaf28a67",
|
||||
"pow_difficulty":4704880707,
|
||||
"pow_hash":"bc26c47392a94004bc736822520f67bbf56dfba432e6d75aa12ab2e900000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"4B4mXWBqrFYGk9W8t1J3UifSdnU723HHKf8VaWGSZGzLZUncHRJetPCjbDUha1udPd2ZpJrwYCmPv1jzvg3NCf7Z1YViuoa",
|
||||
"main_difficulty":344205037143
|
||||
}
|
||||
}
|
||||
{
|
||||
"type":"side_block",
|
||||
"side_block":{
|
||||
"main_id":"617ea6fe4a47a3e7f51c8cc719a5158b88a4ca2583cd2f0cd5ed0b006bca3564",
|
||||
"main_height":2871494,
|
||||
"template_id":"b95dcd9d440fcd49a050c76f006189c01e1857d11bfc0b41f3fa560c15a4750f",
|
||||
"side_height":4973843,
|
||||
"parent_template_id":"5f72a35b571746ac3835b204020e548e65a7350d84d5d7a7aa3a749dc058f7ad",
|
||||
"miner":3,
|
||||
"effective_height":4973843,
|
||||
"nonce":4026630147,
|
||||
"extra_nonce":2353672144,
|
||||
"timestamp":1682358101,
|
||||
"software_id":0,
|
||||
"software_version":196610,
|
||||
"window_depth":332,
|
||||
"window_outputs":31,
|
||||
"transaction_count":15,
|
||||
"difficulty":1895322145,
|
||||
"cumulative_difficulty":"0000000000000000001ba9611beadc88",
|
||||
"pow_difficulty":6105552648,
|
||||
"pow_hash":"5c06f2ebd7fc22dca35bed2a0faa9e1aad7e36a5c89af94d587915b400000000",
|
||||
"inclusion":1,
|
||||
"miner_address":"47ab14EokGgCTX7RYoVhrNMjVA7GfW1jyMAmL7qBQz9fa4RZ6ZsBUgeRGuPWjqeM1wLptSJH5xuX2H4mAepMYvu6JqWMsGw",
|
||||
"main_difficulty":344205037143
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<div>
|
||||
<h3>Redirect APIs</h3>
|
||||
<p>Several endpoints are available that redirect/forward your request via <em>Location</em> HTTP header to the relevant API listed above.</p>
|
||||
<p>
|
||||
<h4 class="mono">/api/last_found[/raw|...]?...</h4>
|
||||
Redirects to the <em>/api/block_by_id/...</em> record for the last block found on the Monero network by P2Pool.
|
||||
</p>
|
||||
<p>
|
||||
<h4 class="mono">/api/tip[/raw|...]?...</h4>
|
||||
Redirects to the <em>/api/block_by_id/...</em> record for the last share found by P2Pool.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfunc %}
|
|
@ -1,77 +0,0 @@
|
|||
{% import (
|
||||
_ "embed"
|
||||
) %}
|
||||
|
||||
{% code
|
||||
//go:embed "css/style.css"
|
||||
var styleCssContent string
|
||||
%}
|
||||
|
||||
{% interface
|
||||
Page {
|
||||
Title()
|
||||
Content()
|
||||
Style()
|
||||
}
|
||||
%}
|
||||
|
||||
{% code
|
||||
type ContextProviderPage interface {
|
||||
Page
|
||||
Context() *GlobalRequestContext
|
||||
}
|
||||
type ContextSetterPage interface {
|
||||
ContextProviderPage
|
||||
SetContext(ctx *GlobalRequestContext)
|
||||
}
|
||||
%}
|
||||
|
||||
Page prints a page implementing Page interface.
|
||||
{% func PageTemplate(p ContextProviderPage) %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<title>{%=h p.Title() %}</title>
|
||||
<style>
|
||||
{%= p.Style() %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{%= Header(p.Context()) %}
|
||||
|
||||
<div class="content center">
|
||||
{%= p.Content() %}
|
||||
</div>
|
||||
|
||||
{%= Footer(p.Context()) %}
|
||||
</body>
|
||||
</html>
|
||||
{% endfunc %}
|
||||
|
||||
Base page implementation. Other pages may inherit from it if they need
|
||||
overriding only certain Page methods
|
||||
{% code type BasePage struct {
|
||||
ctx *GlobalRequestContext
|
||||
} %}
|
||||
{% func (p *BasePage) Title() %}
|
||||
{% if p.Context().SiteTitle == "" %}
|
||||
P2Pool Observer
|
||||
{% else %}
|
||||
{%s p.Context().SiteTitle %}
|
||||
{% endif %}
|
||||
{% endfunc %}
|
||||
{% func (p *BasePage) Body() %}{% endfunc %}
|
||||
{% code
|
||||
func (p *BasePage) Context() *GlobalRequestContext {
|
||||
return p.ctx
|
||||
}
|
||||
func (p *BasePage) SetContext(ctx *GlobalRequestContext) {
|
||||
p.ctx = ctx
|
||||
}
|
||||
%}
|
||||
{% func (p *BasePage) Style() %}
|
||||
{%s= styleCssContent %}
|
||||
{% endfunc %}
|
|
@ -1,42 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index" %}
|
||||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/monero/address" %}
|
||||
|
||||
{% code
|
||||
type BlocksPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
Refresh int
|
||||
FoundBlocks []*index.FoundBlock
|
||||
Miner *address.Address
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *BlocksPage) Title() %}
|
||||
{% if p.Miner == nil %}
|
||||
{%= p.BasePage.Title() %} - Recent Found Monero blocks
|
||||
{% else %}
|
||||
{%= p.BasePage.Title() %} - Found Monero blocks by {%z= p.Miner.ToBase58() %}
|
||||
{% endif %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *BlocksPage) Content() %}
|
||||
{% if p.Miner == nil %}
|
||||
<div style="text-align: center; font-weight: bold;">
|
||||
{% if p.Refresh > 0 %}
|
||||
<a href="/blocks">Autorefresh is ON ({%d p.Refresh %} s)</a>
|
||||
{% else %}
|
||||
<a href="/blocks?refresh">Autorefresh is OFF</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%endif %}
|
||||
|
||||
<div style="text-align: center">
|
||||
{% if p.Miner == nil %}
|
||||
<h2>Recent Found Monero blocks</h2>
|
||||
{% else %}
|
||||
<h2>{%= p.BasePage.Title() %} - Found Monero blocks by {%z= p.Miner.ToBase58() %}</h2>
|
||||
{% endif %}
|
||||
{%= TemplateFoundBlocks(p.Context(), p.FoundBlocks, p.Miner != nil) %}
|
||||
</div>
|
||||
|
||||
{% endfunc %}
|
|
@ -1,170 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/utils" %}
|
||||
{% code
|
||||
type CalculateShareTimePage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
|
||||
Hashrate float64
|
||||
Magnitude float64
|
||||
Efforts []CalculateShareTimePageEffortEntry
|
||||
EstimatedRewardPerDay uint64
|
||||
EstimatedSharesPerDay float64
|
||||
EstimatedBlocksPerDay float64
|
||||
}
|
||||
%}
|
||||
|
||||
{% code
|
||||
type CalculateShareTimePageEffortEntry struct {
|
||||
Effort float64
|
||||
Probability float64
|
||||
Between float64
|
||||
BetweenSolo float64
|
||||
ShareProbabilities []float64
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *CalculateShareTimePage) Title() %}
|
||||
{%= p.BasePage.Title() %} - Average Share Time Calculator
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *CalculateShareTimePage) Content() %}
|
||||
|
||||
<style type="text/css">
|
||||
.bulk {
|
||||
color: #00dddd;
|
||||
}
|
||||
.mode {
|
||||
font-weight: bolder;
|
||||
/* color: rgb(100,206,145); */
|
||||
color: #00ffff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div style="text-align: center">
|
||||
<form action="/calculate-share-time" method="get">
|
||||
<h2>Average Share Time Calculator</h2>
|
||||
<p>
|
||||
This tool calculates your average expected time between shares on the pool at current difficulty.
|
||||
<br/>
|
||||
<small>Do note this time will vary (can get shares sooner, or later, even way later), depending on your luck, and changing difficulty of the pool due to increase / reductions in hashrate.</small>
|
||||
<br/>
|
||||
For lower hashrate, this average is what statistically will be achieved long term.
|
||||
<br/>
|
||||
The long term average per-share effort would be <span style="font-weight:bolder; color: {%s effort_color(100) %};">100%</span>
|
||||
</p>
|
||||
<div>
|
||||
<label for="hashrate">Your Hashrate</label><br/>
|
||||
<input type="numeric" name="hashrate" id="hashrate" placeholder="100" size="8" class="mono" value="{% if p.Hashrate > 0 %}{%s str(p.Hashrate) %}{% endif %}"/>
|
||||
<select name="magnitude">
|
||||
<option value="1"{% if p.Magnitude == 1 %} selected{% endif %}>H/s</option>
|
||||
<option value="1000"{% if p.Magnitude == 1000 %} selected{% endif %}>KH/s</option>
|
||||
<option value="1000000"{% if p.Magnitude == 1000000 %} selected{% endif %}>MH/s</option>
|
||||
<option value="1000000000"{% if p.Magnitude == 1000000000 %} selected{% endif %}>GH/s</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="margin-top: 10px">
|
||||
<input type="submit" value="Calculate" style="width: 20em;"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if p.Hashrate > 0 %}
|
||||
<hr/>
|
||||
<div style="text-align: center">
|
||||
|
||||
<table class="center" style="max-width: calc(15em + 15em + 15em + 15em + 15em);">
|
||||
{% code between := (float64(p.Context().Pool.SideChain.LastBlock.Difficulty) / (p.Hashrate * p.Magnitude)) %}
|
||||
{% code between_solo := (float64(p.Context().Pool.MainChain.Difficulty.Lo) / (p.Hashrate * p.Magnitude)) %}
|
||||
<tr style="line-height: 1.5;">
|
||||
<td style="width: 15em"><strong>P2Pool Difficulty</strong><br/>{%s si_units(p.Context().Pool.SideChain.LastBlock.Difficulty, 2) %}</td>
|
||||
<td style="width: 15em"><strong>P2Pool Hashrate</strong><br/>{%s si_units(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime), 2) %}H/s</td>
|
||||
<td style="width: 15em"><strong>Your Hashrate</strong><br/>{%s si_units(p.Hashrate * p.Magnitude, 2) %}H/s</td>
|
||||
<td title="Mean frequency between P2Pool shares" style="width: 15em; border: #ff6600 dashed 1px;"><strong>Your Share Mean<br/>{%s time_duration_long(between) %}</strong></td>
|
||||
<td title="Mean P2Pool shares per day" style="width: 15em; border: #ff6600 dashed 1px;"><strong>Your Daily Mean Shares<br/>{%f.3 p.EstimatedSharesPerDay %} share(s)</strong></td>
|
||||
</tr>
|
||||
<tr><th colspan="5"> </th></tr>
|
||||
<tr style="line-height: 1.5;">
|
||||
<td><strong>Monero Difficulty</strong><br/>{%s si_units(p.Context().Pool.MainChain.Difficulty.Lo, 2) %}</td>
|
||||
<td><strong>Monero Hashrate</strong><br/>{%s si_units(diff_hashrate(p.Context().Pool.MainChain.Difficulty, uint64(p.Context().Pool.MainChain.Consensus.BlockTime)), 2) %}H/s</td>
|
||||
<td title="Mean frequency between P2Pool finds Monero Blocks"><strong>P2Pool Block Mean</strong><br/><em>{%s time_duration_long(float64(p.Context().Pool.MainChain.Difficulty.Lo) / float64(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime))) %}</em></td>
|
||||
<td title="Mean frequency between Solo Monero Blocks (without P2Pool)"><strong>Your Solo Block Mean</strong><br/><em>{%s time_duration_long(between_solo) %}</em></td>
|
||||
<td title="Mean Solo Monero Blocks (without P2Pool) per day"><strong>Your Solo Daily Mean Blocks</strong><br/><em>{%f.3 p.EstimatedBlocksPerDay %} block(s)</em></td>
|
||||
</tr>
|
||||
<tr><th colspan="5"> </th></tr>
|
||||
<tr style="line-height: 1.5; font-size: 21px;"><td></td><td colspan="3" style="border: #ff6600 dashed 1px;"><strong>Your Estimated Daily Reward</strong><br/>{%s monero_to_xmr(p.EstimatedRewardPerDay) %} XMR</td><td></td></tr>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
<h3>Single Share Effort</h3>
|
||||
<table class="center" style="max-width: calc(10em + 15em + 15em + 15em);">
|
||||
<tr style="line-height: 1.5;">
|
||||
<th>Share Effort</th>
|
||||
<th>Found Probability</th>
|
||||
<th>P2Pool Estimate</th>
|
||||
<th>Solo Estimate</th>
|
||||
</tr>
|
||||
|
||||
{% for _, e := range p.Efforts %}
|
||||
<tr>
|
||||
<td style="font-size: 17px; font-weight:bolder; color: {%s effort_color(e.Effort) %};">
|
||||
{%f.0 e.Effort %}%
|
||||
</td>
|
||||
<td title="Chance share is not found before: {%f.5 100 - e.Probability %}%">
|
||||
{%f.5 e.Probability %}%
|
||||
</td>
|
||||
<td>
|
||||
{%s time_duration_long(e.Between) %}
|
||||
</td>
|
||||
<td>
|
||||
{%s time_duration_long(e.BetweenSolo) %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
<h3>Cumulative Share Effort</h3>
|
||||
<table class="center" style="max-width: calc(15em + 15em + {%d len(p.Efforts[0].ShareProbabilities)*2 %}em);">
|
||||
<tr>
|
||||
<th colspan="2"></th>
|
||||
<th colspan="{%d len(p.Efforts[0].ShareProbabilities) %}">Number of shares</th>
|
||||
</tr>
|
||||
<tr style="line-height: 1.5;">
|
||||
<th>Cumulative Effort</th>
|
||||
<th>Cumulative Time</th>
|
||||
{% for i := range p.Efforts[0].ShareProbabilities %}
|
||||
<th>{%d i %}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
{% for _, e := range p.Efforts %}
|
||||
<tr>
|
||||
<td style="font-size: 17px; font-weight:bolder; color: {%s effort_color(e.Effort) %};">
|
||||
{%f.0 e.Effort %}%
|
||||
</td>
|
||||
<td>
|
||||
{%s time_duration_long(e.Between) %}
|
||||
</td>
|
||||
{% code var mode = utils.ProbabilityMode(e.ShareProbabilities...) %}
|
||||
{% for _, p := range e.ShareProbabilities %}
|
||||
{% if p*100 > 1 %}
|
||||
<td class="{% if mode == p %}mode{% else %}{% endif %}" title="{%f.5 p*100 %}%">{%f.0 p*100 %}%</td>
|
||||
{% else %}
|
||||
<td title="{%f.5 p*100 %}%"></td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
<tr>
|
||||
<th colspan="2"></th>
|
||||
{% for i := range p.Efforts[0].ShareProbabilities %}
|
||||
<th>{%d i %}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfunc %}
|
|
@ -1,128 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index" %}
|
||||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain" %}
|
||||
{% import p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types" %}
|
||||
{% import "net/netip" %}
|
||||
|
||||
{% code
|
||||
type ConnectivityCheckPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
|
||||
Address netip.AddrPort
|
||||
YourTip *index.SideBlock
|
||||
YourTipRaw *sidechain.PoolBlock
|
||||
OurTip *index.SideBlock
|
||||
Check *p2pooltypes.P2PoolConnectionCheckInformation[*sidechain.PoolBlock]
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *ConnectivityCheckPage) Title() %}
|
||||
{%= p.BasePage.Title() %} - Connectivity Check
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *ConnectivityCheckPage) Content() %}
|
||||
<div style="text-align: center">
|
||||
<form action="/connectivity-check" method="get">
|
||||
<h2>Connectivity Check</h2>
|
||||
<p>
|
||||
This tool tries to connect or looks up a running P2Pool node to verify ports have been properly opened.
|
||||
<br/>
|
||||
<small>You need to have external ports opened for this tool to work.</small>
|
||||
</p>
|
||||
<div>
|
||||
<label for="address">IP Address and Port</label><br/>
|
||||
<input type="text" name="address" id="address" placeholder="5.9.17.234:{%dul uint64(p.Context().Consensus.DefaultPort()) %}" size="20" class="mono" value="{% if p.Address.IsValid() %}{%s p.Address.String() %}{% endif %}"/>
|
||||
</div>
|
||||
<div style="margin-top: 10px">
|
||||
<input type="submit" value="Check" style="width: 20em;"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if p.Address.IsValid() %}
|
||||
<hr/>
|
||||
<div style="text-align: center">
|
||||
<h2>Connectivity Check Result</h2>
|
||||
{% if p.Check == nil %}
|
||||
<p style="color:#FF4433">Could not connect to host.</p>
|
||||
{% elseif p.Check.Error != "" && p.Check.Address == "" %}
|
||||
<p style="color:#FF4433">Error connecting to host: <code>{%s p.Check.Error %}</code>.</p>
|
||||
{% if p.Check.Error == "disconnected before finishing handshake" %}
|
||||
<p>This peer is likely running on a different SideChain or is running an incompatible P2Pool software version. Try a different Observer or verify your software.</p>
|
||||
{% elseif p.Check.Error == "not_valid_ip" %}
|
||||
<p>The IP Address you are trying to connect to is not valid or accepted on this tool.</p>
|
||||
{% elseif p.Check.Error == "not_valid_port" %}
|
||||
<p>The Port you are trying to connect to is not valid or accepted on this tool.</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<table class="center" style="max-width: calc(8em + 28em + 8em + 28em + 20em)">
|
||||
<tr style="line-height: 1.5;">
|
||||
<td style="width: 10em"><strong>Peer Address</strong><br/>{%s p.Check.Address %}</td>
|
||||
<td style="width: 10em"><strong>Peer Port</strong><br/>{%dul uint64(p.Check.Port) %}</td>
|
||||
<td style="width: 10em"><strong>Listen Port</strong><br/>{% if p.Check.ListenPort > 0 %}{%dul uint64(p.Check.ListenPort) %}{% else %}-{% endif %}</td>
|
||||
<td style="width: 10em"><strong>Peer Id</strong><br/>{% if p.Check.PeerId > 0 %}{%dul p.Check.PeerId %}{% else %}-{% endif %}</td>
|
||||
<td style="width: 10em"><strong>Connection Direction</strong><br/>{% if p.Check.Incoming %}Incoming{% else %}Outgoing{% endif %}</td>
|
||||
<td style="width: 10em"><strong>Connection Status</strong><br/>{% if p.Check.Closed %}Closed{% else %}Active{% endif %}{% if !p.Check.AlreadyConnected %}, new connection{% endif %}{% if p.Check.Banned %} (banned){% endif %}</td>
|
||||
</tr>
|
||||
{% if !p.Check.Closed || p.Check.PeerId > 0 %}
|
||||
<tr style="line-height: 1.5;">
|
||||
<td style="width: 10em"><strong>Peer Software</strong><br/>{%s p.Check.SoftwareId %} {%s p.Check.SoftwareVersion %}</td>
|
||||
<td style="width: 10em"><strong>Peer Protocol</strong><br/>{%s p.Check.ProtocolVersion %}</td>
|
||||
<td style="width: 10em"><strong>Handshake Complete</strong><br/>{% if p.Check.HandshakeComplete %}Yes{% else %}No{% endif %}</td>
|
||||
<td style="width: 10em"><strong>Last Broadcast</strong><br/><span title="{%s utc_date(p.Check.BroadcastTime) %}">{%s time_elapsed_short(p.Check.BroadcastTime) %}</span></td>
|
||||
<td style="width: 10em"><strong>Connection Duration</strong><br/><span title="{%s utc_date(p.Check.ConnectionTime) %}">{%s time_elapsed_short(p.Check.ConnectionTime) %}</span></td>
|
||||
<td style="width: 10em"><strong>Connection Latency</strong><br/>{% if p.Check.Latency > 0 %}{%dul p.Check.Latency %}ms{% else %}-{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if p.Check.BanError != "" %}
|
||||
<tr><th colspan="6"> </th></tr>
|
||||
<tr><td colspan="6" style="color:#FF4433"><strong>Previous Ban Error:</strong> <code>{%s p.Check.BanError %}</code></td></tr>
|
||||
{% endif %}
|
||||
{% if p.Check.Error != "" %}
|
||||
<tr><th colspan="6"> </th></tr>
|
||||
<tr><td colspan="6" style="color:#FF4433"><strong>Error:</strong> <code>{%s p.Check.Error %}</code></td></tr>
|
||||
{% if p.Check.Error == "disconnected before finishing handshake" %}
|
||||
<tr><td colspan="6">This peer is likely running on a different SideChain or is running an incompatible P2Pool software version. Try a different Observer or verify your software.<br/>Alternatively this peer could have banned the Observer node and we cannot connect properly.</td></tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if p.Check.Tip != nil %}
|
||||
<tr><th colspan="6"> </th></tr>
|
||||
<tr style="line-height: 1.5;">
|
||||
<td style="width: 10em"><strong>Peer SideChain Height</strong><br/><a href="/share/{%= hex(p.Context(), p.YourTipRaw.SideTemplateId(p.Context().Consensus)) %}">{%dul p.YourTipRaw.Side.Height %}</a></td>
|
||||
<td style="width: 10em"><strong>Peer SideChain Id</strong><br/><a href="/share/{%= hex(p.Context(), p.YourTipRaw.SideTemplateId(p.Context().Consensus)) %}" title="{%= hex(p.Context(), p.YourTipRaw.SideTemplateId(p.Context().Consensus)) %}">{%= shorten(p.Context(), p.YourTipRaw.SideTemplateId(p.Context().Consensus), 10) %}</a></td>
|
||||
<td style="width: 10em"><strong>Peer MainChain Height</strong><br/>{%dul p.YourTipRaw.Main.Coinbase.GenHeight %}</td>
|
||||
<td style="width: 10em"><strong>Peer Difficulty</strong><br/>{%s si_units(p.YourTipRaw.Side.Difficulty.Lo, 4) %}</td>
|
||||
<td style="width: 10em"><strong>Peer Cumulative Difficulty</strong><br/>{%s si_units(p.YourTipRaw.Side.CumulativeDifficulty.Lo, 4) %}</td>
|
||||
<td style="width: 10em"><strong>Peer Timestamp</strong><br/>{%s utc_date(p.YourTipRaw.Main.Timestamp) %}</td>
|
||||
</tr>
|
||||
<tr style="line-height: 1.5;">
|
||||
<td style="width: 10em"><strong>Observer SideChain Height</strong><br/><a href="/share/{%= hex(p.Context(), p.OurTip.TemplateId) %}">{%dul p.OurTip.SideHeight %}</a></td>
|
||||
<td style="width: 10em"><strong>Observer SideChain Id</strong><br/><a href="/share/{%= hex(p.Context(), p.OurTip.TemplateId) %}" title="{%= hex(p.Context(), p.OurTip.TemplateId) %}">{%= shorten(p.Context(), p.OurTip.TemplateId, 10) %}</a></td>
|
||||
<td style="width: 10em"><strong>Observer MainChain Height</strong><br/>{%dul p.OurTip.MainHeight %}</td>
|
||||
<td style="width: 10em"><strong>Observer Difficulty</strong><br/>{%s si_units(p.OurTip.Difficulty, 4) %}</td>
|
||||
<td style="width: 10em"><strong>Observer Cumulative Difficulty</strong><br/>{%s si_units(p.OurTip.CumulativeDifficulty.Lo, 4) %}</td>
|
||||
<td style="width: 10em"><strong>Observer Timestamp</strong><br/>{%s utc_date(p.OurTip.Timestamp) %}</td>
|
||||
</tr>
|
||||
<tr><th colspan="6"> </th></tr>
|
||||
{% if ((p.YourTipRaw.Main.Coinbase.GenHeight < (p.OurTip.MainHeight-3)) || (p.YourTipRaw.Main.Coinbase.GenHeight > (p.OurTip.MainHeight+3))) %}
|
||||
<tr><td colspan="6" style="font-weight: bold; color:#FF4433">Peer Monero node is on a wildly different Monero height than Observer.<br/>Either peer node is lagging behind or your monerod is not up to sync.</td></tr>
|
||||
{% endif %}
|
||||
{% if p.YourTip == nil && ((p.YourTipRaw.Side.Height < (p.OurTip.SideHeight-3)) || (p.YourTipRaw.Side.Height > (p.OurTip.SideHeight+3))) %}
|
||||
<tr><td colspan="6" style="font-weight: bold; color:#FF4433">Could not find Peer SideChain Tip on Observer.<br/>Either peer node is lagging behind or you are on a forked SideChain.</td></tr>
|
||||
{% elseif (p.YourTipRaw.Side.Height < (p.OurTip.SideHeight-3)) || (p.YourTipRaw.Side.Height > (p.OurTip.SideHeight+3)) %}
|
||||
<tr><td colspan="6" style="font-weight: bold">The peer is connectable and on the SideChain.<br/>Peer SideChain Tip is on a wildly different SideChain height than Observer.<br/>Either peer node is lagging behind or your p2pool is not up to sync.</td></tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" style="font-weight: bold">The peer is connectable and on the SideChain.<br/>{% if p.OurTip.TemplateId == p.YourTipRaw.SideTemplateId(p.Context().Consensus) %}The peer Tip matches exactly the Observer Tip.{% else %}Verify Peer SideChain Height against Observer SideChain Height, so it's not consistently different.{% endif %}</td></tr>
|
||||
{% endif %}
|
||||
<tr><th colspan="6"> </th></tr>
|
||||
<tr><td colspan="6">
|
||||
{%= TemplatePoolBlock(p.Context(), p.YourTipRaw, "Peer Tip Share information") %}
|
||||
</td></tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfunc %}
|
|
@ -1,32 +0,0 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
)
|
||||
|
||||
type GlobalRequestContext struct {
|
||||
IsOnion bool
|
||||
DonationAddress string
|
||||
SiteTitle string
|
||||
NetServiceAddress string
|
||||
TorServiceAddress string
|
||||
Consensus *sidechain.Consensus
|
||||
Pool *cmdutils.PoolInfoResult
|
||||
Socials struct {
|
||||
Irc struct {
|
||||
Title string
|
||||
Link string
|
||||
WebChat string
|
||||
}
|
||||
Matrix struct {
|
||||
Link string
|
||||
}
|
||||
}
|
||||
HexBuffer [types.HashSize * 2]byte
|
||||
}
|
||||
|
||||
func (ctx *GlobalRequestContext) GetUrl(host string) string {
|
||||
return cmdutils.GetSiteUrlByHost(host, ctx.IsOnion)
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #d1d1d1;
|
||||
background-color: #4c4c4c;
|
||||
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
ul{
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.datatable tr, li, code.mono {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 96%;
|
||||
}
|
||||
|
||||
.hl-found{
|
||||
background: rgba(0%, 75.3%, 0%, 0.5);
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
white-space: nowrap;
|
||||
line-break: strict;
|
||||
}
|
||||
|
||||
a, a:hover, a:visited, a:link, a:active {
|
||||
text-decoration: underline dotted #333;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
form {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #ff6600;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 60%;
|
||||
}
|
||||
|
||||
code.small {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
code.smaller {
|
||||
font-size: 8px;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
{% import "fmt" %}
|
||||
{% code
|
||||
type ErrorPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
|
||||
Code int
|
||||
Message string
|
||||
Error any
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *ErrorPage) Title() %}
|
||||
{%= p.BasePage.Title() %} - Error {%d p.Code %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *ErrorPage) Content() %}
|
||||
<div class="center" style="text-align: center">
|
||||
<h2>Error {%d p.Code %} - {%s p.Message %}</h2>
|
||||
{% if p.Error != nil %}
|
||||
<div class="center" style="text-align: center">{%s fmt.Sprintf("%s", p.Error) %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfunc %}
|
||||
|
||||
{% code
|
||||
|
||||
func NewErrorPage(code int, message string, err any) *ErrorPage {
|
||||
return &ErrorPage{Code: code, Message: message, Error: err}
|
||||
}
|
||||
|
||||
%}
|
|
@ -1,15 +0,0 @@
|
|||
{% func Footer(ctx *GlobalRequestContext) %}
|
||||
<div>
|
||||
<footer class="center" style="text-align: center; margin-bottom: 20px;">
|
||||
<a href="{%s ctx.GetUrl("git.gammaspectra.live") %}/P2Pool/p2pool-observer">source code</a>
|
||||
—
|
||||
<a href="https://{%s ctx.NetServiceAddress %}">{%s ctx.NetServiceAddress %}</a>
|
||||
::
|
||||
Tor <a href="http://{%s ctx.TorServiceAddress %}">{%s ctx.TorServiceAddress %}</a>
|
||||
<br/>
|
||||
Donate to P2Pool Observer developer on <a class="mono small" style="font-weight: bold" href="monero:{%s ctx.DonationAddress %}?tx_description=p2pool.observer">{%s ctx.DonationAddress %}</a> or OpenAlias <span class="mono">p2pool.observer</span> :: <a href="https://github.com/SChernykh/p2pool#donations">Donate to P2Pool Development</a>
|
||||
<br/>
|
||||
<strong>NOTE:</strong> This site is in development. You might find errors, incomplete or invalid data. :: <a target="_blank" href="{%s ctx.GetUrl("git.gammaspectra.live") %}/P2Pool/p2pool-observer/issues?state=open">Report any issues on tracker</a>, via IRC on <a target="_blank" href="ircs://irc.libera.chat:6697/#p2pool-observer">#p2pool-observer@libera.chat</a>, or via <a rel="nofollow" href="https://matrix.to/#/#p2pool-observer:monero.social">Matrix</a>
|
||||
</footer>
|
||||
</div>
|
||||
{% endfunc %}
|
|
@ -1,184 +0,0 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
hex2 "encoding/hex"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
||||
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
||||
"github.com/mazznoer/colorgrad"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func utc_date[T int64 | uint64 | int | float64](v T) string {
|
||||
return time.Unix(int64(v), 0).UTC().Format("02-01-2006 15:04:05 MST")
|
||||
}
|
||||
|
||||
func time_elapsed_short[T int64 | uint64 | int | float64](v T) string {
|
||||
diff := time.Since(time.Unix(int64(v), 0).UTC())
|
||||
|
||||
days := int64(diff.Hours() / 24)
|
||||
hours := int64(diff.Hours()) % 24
|
||||
minutes := int64(diff.Minutes()) % 60
|
||||
seconds := int64(diff.Seconds()) % 60
|
||||
|
||||
var result []string
|
||||
if days > 0 {
|
||||
result = append(result, strconv.FormatInt(days, 10)+"d")
|
||||
}
|
||||
if hours > 0 {
|
||||
result = append(result, strconv.FormatInt(hours, 10)+"h")
|
||||
}
|
||||
if minutes > 0 {
|
||||
result = append(result, strconv.FormatInt(minutes, 10)+"m")
|
||||
}
|
||||
if seconds > 0 {
|
||||
result = append(result, strconv.FormatInt(seconds, 10)+"s")
|
||||
}
|
||||
|
||||
if v == 0 {
|
||||
return "never"
|
||||
} else if len(result) == 0 {
|
||||
return "just now"
|
||||
} else {
|
||||
return strings.Join(result[0:1], " ") + " ago"
|
||||
}
|
||||
}
|
||||
|
||||
func side_block_weight(s *index.SideBlock, tipHeight, windowSize uint64, consensus *sidechain.Consensus) uint64 {
|
||||
w, _ := s.Weight(tipHeight, windowSize, consensus.UnclePenalty)
|
||||
return w
|
||||
}
|
||||
|
||||
func si_units[T int64 | uint64 | int | float64](v T, n ...int) string {
|
||||
if len(n) > 0 {
|
||||
return utils.SiUnits(float64(v), n[0])
|
||||
} else {
|
||||
return utils.SiUnits(float64(v), 3)
|
||||
}
|
||||
}
|
||||
func date_diff_short[T int64 | uint64 | int | float64](v T) string {
|
||||
diff := time.Since(time.Unix(int64(v), 0).UTC())
|
||||
s := fmt.Sprintf("%02d:%02d:%02d", int64(diff.Hours())%24, int64(diff.Minutes())%60, int64(diff.Seconds())%60)
|
||||
|
||||
days := int64(diff.Hours() / 24)
|
||||
if days > 0 {
|
||||
return strconv.FormatInt(days, 10) + ":" + s
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func time_duration_long[T int64 | uint64 | int | float64](v T) string {
|
||||
diff := time.Second * time.Duration(v)
|
||||
diff += time.Microsecond * time.Duration((float64(v)-float64(int64(v)))*1000000)
|
||||
days := int64(diff.Hours() / 24)
|
||||
hours := int64(diff.Hours()) % 24
|
||||
minutes := int64(diff.Minutes()) % 60
|
||||
seconds := int64(diff.Seconds()) % 60
|
||||
ms := int64(diff.Milliseconds()) % 1000
|
||||
|
||||
var result []string
|
||||
if days > 0 {
|
||||
result = append(result, strconv.FormatInt(days, 10)+"d")
|
||||
}
|
||||
if hours > 0 {
|
||||
result = append(result, strconv.FormatInt(hours, 10)+"h")
|
||||
}
|
||||
if minutes > 0 {
|
||||
result = append(result, strconv.FormatInt(minutes, 10)+"m")
|
||||
}
|
||||
if seconds > 0 {
|
||||
result = append(result, strconv.FormatInt(seconds, 10)+"s")
|
||||
}
|
||||
|
||||
if len(result) == 0 || (len(result) == 1 && seconds > 0) {
|
||||
result = append(result, strconv.FormatInt(ms, 10)+"ms")
|
||||
}
|
||||
|
||||
return strings.Join(result, " ")
|
||||
}
|
||||
|
||||
func benc(n uint64) string {
|
||||
return utils.EncodeBinaryNumber(n)
|
||||
}
|
||||
|
||||
func bencstr(val string) string {
|
||||
if n, err := strconv.ParseUint(val, 10, 0); err == nil {
|
||||
return utils.EncodeBinaryNumber(n)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func str(val any) string {
|
||||
switch t := val.(type) {
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(t), 'f', -1, 64)
|
||||
case float64:
|
||||
return strconv.FormatFloat(t, 'f', -1, 64)
|
||||
case uint32:
|
||||
return strconv.FormatFloat(float64(t), 'f', -1, 64)
|
||||
case uint64:
|
||||
return strconv.FormatFloat(float64(t), 'f', -1, 64)
|
||||
case int:
|
||||
return strconv.FormatFloat(float64(t), 'f', -1, 64)
|
||||
case fmt.Stringer:
|
||||
return t.String()
|
||||
default:
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func found_block_effort(b, previous *index.FoundBlock) float64 {
|
||||
if previous == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return float64(b.CumulativeDifficulty.Sub(previous.CumulativeDifficulty).Mul64(100).Lo) / float64(b.MainBlock.Difficulty)
|
||||
}
|
||||
|
||||
var effortColorGradient = colorgrad.RdYlBu()
|
||||
|
||||
const effortRangeStart = 0.15
|
||||
const effortRangeEnd = 0.85
|
||||
|
||||
func effort_color(effort float64) string {
|
||||
probability := utils.ProbabilityEffort(effort)
|
||||
|
||||
// rescale
|
||||
probability *= effortRangeEnd - effortRangeStart
|
||||
probability += effortRangeStart
|
||||
|
||||
return effortColorGradient.At(1 - probability).Hex()
|
||||
}
|
||||
|
||||
func monero_to_xmr(v uint64) string {
|
||||
return utils.XMRUnits(v)
|
||||
}
|
||||
|
||||
func diff_hashrate(v any, blockTime uint64) uint64 {
|
||||
if strVal, ok := v.(string); ok {
|
||||
if d, err := types.DifficultyFromString(strVal); err == nil {
|
||||
return d.Div64(blockTime).Lo
|
||||
}
|
||||
} else if d, ok := v.(types.Difficulty); ok {
|
||||
return d.Div64(blockTime).Lo
|
||||
} else if d, ok := v.(uint64); ok {
|
||||
return d / blockTime
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func coinbase_extra(b *sidechain.PoolBlock) string {
|
||||
coinbaseExtra := b.Main.Coinbase.Extra
|
||||
var result []string
|
||||
for _, t := range coinbaseExtra {
|
||||
buf, _ := t.MarshalBinary()
|
||||
result = append(result, hex2.EncodeToString(buf))
|
||||
}
|
||||
return strings.Join(result, " ")
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/types" %}
|
||||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto" %}
|
||||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/utils" %}
|
||||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain" %}
|
||||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index" %}
|
||||
{% import p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types" %}
|
||||
|
||||
{% import "encoding/binary" %}
|
||||
{% import "fmt" %}
|
||||
{% import "slices" %}
|
||||
{% import hex2 "encoding/hex" %}
|
||||
{% import fasthex "github.com/tmthrgd/go-hex" %}
|
||||
|
||||
{% import qt "github.com/valyala/quicktemplate" %}
|
||||
|
||||
{% stripspace %}
|
||||
|
||||
{% code
|
||||
func streamencodeJson(w *qt.Writer, val any, indent bool) {
|
||||
encoder := utils.NewJSONEncoder(w.E())
|
||||
if indent {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
if err := encoder.EncodeWithOption(val, utils.JsonEncodeOptions...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
%}
|
||||
|
||||
{% func hex(ctx *GlobalRequestContext, val any) %}
|
||||
{% switch s := val.(type) %}
|
||||
{% case string %}
|
||||
{%s s %}
|
||||
{% case types.Difficulty %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:types.DifficultySize*2], s.Bytes())
|
||||
%}
|
||||
{%z= ctx.HexBuffer[:types.DifficultySize*2] %}
|
||||
{% case crypto.PrivateKeyBytes %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s[:])
|
||||
%}
|
||||
{%z= ctx.HexBuffer[:] %}
|
||||
{% case crypto.PublicKeyBytes %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s[:])
|
||||
%}
|
||||
{%z= ctx.HexBuffer[:] %}
|
||||
{% case crypto.PrivateKey %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s.AsSlice())
|
||||
%}
|
||||
{%z= ctx.HexBuffer[:] %}
|
||||
{% case crypto.PublicKey %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s.AsSlice())
|
||||
%}
|
||||
{%z= ctx.HexBuffer[:] %}
|
||||
{% case types.Hash %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s[:])
|
||||
%}
|
||||
{%z= ctx.HexBuffer[:] %}
|
||||
{% case []byte %}
|
||||
{%s= hex2.EncodeToString(s) %}
|
||||
{% case uint32 %}
|
||||
{% code
|
||||
var buf [4]byte
|
||||
binary.LittleEndian.PutUint32(buf[:], s)
|
||||
fasthex.Encode(ctx.HexBuffer[:4*2], buf[:])
|
||||
%}
|
||||
{%z= ctx.HexBuffer[:4*2] %}
|
||||
{% case uint64 %}
|
||||
{% code
|
||||
var buf [8]byte
|
||||
binary.LittleEndian.PutUint64(buf[:], s)
|
||||
fasthex.Encode(ctx.HexBuffer[:8*2], buf[:])
|
||||
%}
|
||||
{%z= ctx.HexBuffer[:8*2] %}
|
||||
{% default %}
|
||||
{%v val %}
|
||||
{% endswitch %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func shorten(ctx *GlobalRequestContext, val any, n int) %}
|
||||
{% switch s := val.(type) %}
|
||||
{% case string %}
|
||||
{%s utils.Shorten(s, n) %}
|
||||
{% case []byte %}
|
||||
{%z= utils.ShortenSlice(slices.Clone(s), n) %}
|
||||
{% case crypto.PrivateKeyBytes %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s[:])
|
||||
%}
|
||||
{%z= utils.ShortenSlice(ctx.HexBuffer[:], n) %}
|
||||
{% case crypto.PublicKeyBytes %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s[:])
|
||||
%}
|
||||
{%z= utils.ShortenSlice(ctx.HexBuffer[:], n) %}
|
||||
{% case crypto.PrivateKey %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s.AsSlice())
|
||||
%}
|
||||
{%z= utils.ShortenSlice(ctx.HexBuffer[:], n) %}
|
||||
{% case crypto.PublicKey %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s.AsSlice())
|
||||
%}
|
||||
{%z= utils.ShortenSlice(ctx.HexBuffer[:], n) %}
|
||||
{% case types.Hash %}
|
||||
{% code
|
||||
fasthex.Encode(ctx.HexBuffer[:], s[:])
|
||||
%}
|
||||
{%z= utils.ShortenSlice(ctx.HexBuffer[:], n) %}
|
||||
{% case fmt.Stringer %}
|
||||
{%s utils.Shorten(s.String(), n) %}
|
||||
{% default %}
|
||||
{%s utils.Shorten(fmt.Sprintf("%v", val), n) %}
|
||||
{% endswitch %}
|
||||
{% endfunc %}
|
||||
|
||||
|
||||
|
||||
{% func henc(val any) %}
|
||||
{% code
|
||||
var buf [types.HashSize*2+1]byte
|
||||
%}
|
||||
{% switch s := val.(type) %}
|
||||
{% case types.Hash %}
|
||||
{% code
|
||||
dst := utils.EncodeSliceBinaryNumber(buf[:], s[:])
|
||||
%}
|
||||
{%z= dst[:] %}
|
||||
{% case crypto.PrivateKeyBytes %}
|
||||
{% code
|
||||
dst := utils.EncodeSliceBinaryNumber(buf[:], s[:])
|
||||
%}
|
||||
{%z= dst[:] %}
|
||||
{% case string %}
|
||||
{%s= utils.EncodeHexBinaryNumber(s) %}
|
||||
{% case fmt.Stringer %}
|
||||
{%s= utils.EncodeHexBinaryNumber(s.String()) %}
|
||||
{% default %}
|
||||
panic("type not allowed")
|
||||
{% endswitch %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func software_info(softwareId p2pooltypes.SoftwareId, softwareVersion p2pooltypes.SoftwareVersion) %}
|
||||
{% if softwareId == 0 && softwareVersion == 0 %}
|
||||
Not present
|
||||
{% else %}
|
||||
{%s= softwareId.String() %}{%s= ` ` %}{%s= softwareVersion.String() %}
|
||||
{% endif %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func side_block_valuation(b any, consensus *sidechain.Consensus) %}
|
||||
{% switch block := b.(type) %}
|
||||
{% case *index.SideBlock %}
|
||||
{% if block.IsOrphan() %}
|
||||
0%
|
||||
{% elseif block.IsUncle() %}
|
||||
{%dul 100-consensus.UnclePenalty %}%{%s= ` ` %}(uncle)
|
||||
{% elseif len(block.Uncles) > 0 %}
|
||||
100%{%s= ` ` %}+{%s= ` ` %}{%dul consensus.UnclePenalty %}%{%s= ` ` %}of{%s= ` ` %}{%d len(block.Uncles) %}{%s= ` ` %}uncle(s)
|
||||
{% else %}
|
||||
100%
|
||||
{% endif %}
|
||||
{% case *sidechain.PoolBlock %}
|
||||
{% if len(block.Side.Uncles) > 0 %}
|
||||
100%{%s= ` ` %}+{%s= ` ` %}{%dul consensus.UnclePenalty %}%{%s= ` ` %}of{%s= ` ` %}{%d len(block.Side.Uncles) %}{%s= ` ` %}uncle(s)
|
||||
{% else %}
|
||||
100%
|
||||
{% endif %}
|
||||
{% default %}
|
||||
panic("type not allowed")
|
||||
{% endswitch %}
|
||||
{% endfunc %}
|
||||
|
||||
{% endstripspace %}
|
|
@ -1,46 +0,0 @@
|
|||
{% func Header(ctx *GlobalRequestContext) %}
|
||||
<div>
|
||||
<div class="center" style="text-align: center">
|
||||
<h1 class="center"><a href="/">{% if ctx.SiteTitle == "" %}Monero P2Pool Observer{% else %}Monero {%s ctx.SiteTitle %}{% endif %}</a></h1>
|
||||
<h4 style="font-size: 16px; margin: 0px">
|
||||
<a href="https://gupax.io/" rel="nofollow">[Gupax] P2Pool plug and play Mining GUI, by <em>hinto-janai</em></a>
|
||||
::
|
||||
<a href="{%s ctx.GetUrl("p2pool.io") %}/#help" rel="nofollow">P2Pool Setup Help</a>
|
||||
::
|
||||
<a href="{%s ctx.GetUrl("p2pool.io") %}/#faq" rel="nofollow">P2Pool FAQ</a>
|
||||
::
|
||||
<a href="https://github.com/SChernykh/p2pool">What is P2Pool?</a>
|
||||
::
|
||||
<a href="/api">Observer API Documentation</a>
|
||||
</h4>
|
||||
{% if ctx.Socials.Irc.Link != "" %}
|
||||
<h4 style="font-size: 16px; margin: 4px 0px 0px; font-weight: normal;">
|
||||
Join chat to talk, send feedback, ask questions and be notified of changes on <a href="{%s ctx.Socials.Irc.Link %}" target="_blank"><strong>IRC on <code>{%s ctx.Socials.Irc.Title %}</code></strong></a>{% if ctx.Socials.Irc.WebChat != "" %}, on <a href="{%s ctx.Socials.Irc.WebChat %}" target="_blank"><strong>WebIRC</strong></a>{% endif %}{% if ctx.Socials.Matrix.Link != "" %}, on <a target="_blank" rel="nofollow" href="{%s ctx.Socials.Matrix.Link %}"><strong>Matrix</strong></a>{% endif %}
|
||||
</h4>
|
||||
{% endif %}
|
||||
<h4 style="font-size: 13px; margin: 4px 0px 0px; font-weight: normal;">
|
||||
<a href="https://github.com/SChernykh/p2pool#how-to-mine-on-p2pool">How to join P2Pool</a>
|
||||
::
|
||||
<a href="{%s ctx.GetUrl("sethforprivacy.com") %}/guides/run-a-p2pool-node/" rel="nofollow">Guide on how to run a Monero and P2Pool node by <em>Seth</em></a>
|
||||
::
|
||||
<a href="{%s ctx.GetUrl("xmrvsbeast.com") %}/p2pool/" rel="nofollow">P2Pool Bonus Hash Rate Raffle by <em>XMRvsBEAST</em></a>
|
||||
</h4>
|
||||
{% if pool := ctx.Pool; pool != nil %}
|
||||
|
||||
{% if uint64(pool.Versions.P2Pool.Timestamp+3600*24*30) > pool.SideChain.LastBlock.Timestamp || uint64(pool.Versions.Monero.Timestamp+3600*24*7) > pool.SideChain.LastBlock.Timestamp %}
|
||||
<h4 style="font-size: 15px; margin: 4px 0px 0px; color:yellow;">
|
||||
{% else %}
|
||||
<h4 style="font-size: 13px; margin-top: 5px; ">
|
||||
{% endif %}
|
||||
Latest releases: <a href="{%s pool.Versions.P2Pool.Link %}" target="_blank">P2Pool {%s pool.Versions.P2Pool.Version %}</a> <em title="{%s utc_date(uint64(pool.Versions.P2Pool.Timestamp)) %}"><small>{%f.1 float64((pool.SideChain.LastBlock.Timestamp - uint64(pool.Versions.P2Pool.Timestamp))) / (3600*24) %} day(s) ago</small></em>
|
||||
::
|
||||
<a href="{%s pool.Versions.Monero.Link %}" target="_blank">Monero {%s pool.Versions.Monero.Version %}</a> <em title="{%s utc_date(uint64(pool.Versions.Monero.Timestamp)) %}"><small>{%f.1 float64((pool.SideChain.LastBlock.Timestamp - uint64(pool.Versions.Monero.Timestamp))) / (3600*24) %} day(s) ago</small></em>
|
||||
</h4>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfunc %}
|
|
@ -1,137 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index" %}
|
||||
{% import cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils" %}
|
||||
|
||||
{% code
|
||||
type IndexPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
Refresh int
|
||||
Positions struct {
|
||||
BlocksFound *cmdutils.PositionChart
|
||||
}
|
||||
|
||||
Shares []*index.SideBlock
|
||||
FoundBlocks []*index.FoundBlock
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *IndexPage) Content() %}
|
||||
<div style="text-align: center; font-weight: bold;">
|
||||
{% if p.Refresh > 0 %}
|
||||
<a href="/">Autorefresh is ON ({%d p.Refresh %} s)</a>
|
||||
{% else %}
|
||||
<a href="/?refresh">Autorefresh is OFF</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2>P2Pool statistics</h2>
|
||||
<table class="center" style="max-width: calc(15em + 15em + 15em + 15em)">
|
||||
<tr>
|
||||
<th style="width: 15em">P2Pool Height</th>
|
||||
<th style="width: 15em">P2Pool Hashrate</th>
|
||||
<th style="width: 15em">Monero Height</th>
|
||||
<th style="width: 15em">Monero Hashrate</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td title="{%= hex(p.Context(), p.Context().Pool.SideChain.LastBlock.TemplateId) %}"><a href="/share/{%= hex(p.Context(), p.Context().Pool.SideChain.LastBlock.TemplateId) %}">{%dul p.Context().Pool.SideChain.LastBlock.SideHeight %}</a></td>
|
||||
<td>{%s si_units(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime), 2) %}H/s</td>
|
||||
<td title="{%= hex(p.Context(), p.Context().Pool.MainChain.Id) %}"><a href="/b/{%s benc(p.Context().Pool.MainChain.Height) %}">{%dul p.Context().Pool.MainChain.Height %}</a></td>
|
||||
<td>{%s si_units(diff_hashrate(p.Context().Pool.MainChain.Difficulty, uint64(p.Context().Pool.MainChain.Consensus.BlockTime)), 2) %}H/s</td>
|
||||
</tr>
|
||||
<tr><th colspan="4"> </th></tr>
|
||||
<tr>
|
||||
<th>P2Pool Difficulty</th>
|
||||
<th title="Percentage of Monero hashrate P2Pool has">P2Pool Monero %</th>
|
||||
<th>Monero Difficulty</th>
|
||||
<th title="This includes blocks the site knows about since it started observing. There might be more orphaned or produced by other sidechain not included here.">Blocks Found</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{%s si_units(p.Context().Pool.SideChain.LastBlock.Timestamp, 2) %}</td>
|
||||
<td>{%f.2 (float64(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime)) / float64(diff_hashrate(p.Context().Pool.MainChain.Difficulty, uint64(p.Context().Pool.MainChain.Consensus.BlockTime))))*100 %}%</td>
|
||||
<td>{%s si_units(p.Context().Pool.MainChain.Difficulty.Lo, 2) %}</td>
|
||||
<td>{%dul p.Context().Pool.SideChain.Found %}</td>
|
||||
</tr>
|
||||
<tr><th colspan="4"> </th></tr>
|
||||
<tr>
|
||||
<th title="Miners that have ever mined a share on P2Pool">Miners Known</th>
|
||||
<th>Average Effort</th>
|
||||
<th title="Mean frequency between P2Pool finds Monero Blocks">Block Found Frequency</th>
|
||||
<th>Last Found Block</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{%dul p.Context().Pool.SideChain.Miners %}</td>
|
||||
<td >
|
||||
<span class="small" style="color: {%s effort_color(p.Context().Pool.SideChain.Effort.Average10) %};" title="Last 10 found blocks">{%f.2 p.Context().Pool.SideChain.Effort.Average10 %}%</span>
|
||||
/
|
||||
<span style="font-weight:bolder; color: {%s effort_color(p.Context().Pool.SideChain.Effort.Average50) %};" title="Last 50 found blocks">{%f.2 p.Context().Pool.SideChain.Effort.Average50 %}%</span>
|
||||
/
|
||||
<span class="small" style="color: {%s effort_color(p.Context().Pool.SideChain.Effort.Average200) %};" title="Last 200 found blocks">{%f.2 p.Context().Pool.SideChain.Effort.Average200 %}%</span>
|
||||
</td>
|
||||
<td>{%s time_duration_long(float64(p.Context().Pool.MainChain.Difficulty.Lo) / float64(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime))) %}</td>
|
||||
{% if p.Context().Pool.SideChain.LastFound != nil %}
|
||||
<td title="{%s utc_date(p.Context().Pool.SideChain.LastFound.MainBlock.Timestamp) %}">{%s time_elapsed_short(p.Context().Pool.SideChain.LastFound.MainBlock.Timestamp) %}</td>
|
||||
{% else %}
|
||||
<td>-</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr><th colspan="4"> </th></tr>
|
||||
<tr>
|
||||
<th title="Current miners on P2Pool PPLNS window"><a href="/miners">Window Miners</a></th>
|
||||
<th>Current Effort</th>
|
||||
<th>Window Blocks</th>
|
||||
<th>Last Share</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="/miners">{%d p.Context().Pool.SideChain.Window.Miners %}</a></td>
|
||||
<td style="font-weight:bolder; color: {%s effort_color(p.Context().Pool.SideChain.Effort.Current) %};">
|
||||
{%f.2 p.Context().Pool.SideChain.Effort.Current %}%
|
||||
</td>
|
||||
<td>{%d p.Context().Pool.SideChain.Window.Blocks %} blocks (+{%d p.Context().Pool.SideChain.Window.Uncles %} uncles)</td>
|
||||
<td title="{%s utc_date(p.Context().Pool.SideChain.LastBlock.Timestamp) %}">{%s time_elapsed_short(p.Context().Pool.SideChain.LastBlock.Timestamp) %}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center">
|
||||
<form action="/miner" method="get">
|
||||
<h2>Lookup miner statistics</h2>
|
||||
<div>
|
||||
<label for="miner-address">Payout Monero address or Miner Alias</label><br/>
|
||||
<input type="text" name="address" id="miner-address" placeholder="{%s p.Context().DonationAddress %}" size="96" class="mono"/>
|
||||
</div>
|
||||
<div style="margin-top: 10px">
|
||||
<input type="submit" value="Lookup" style="width: 20em;"/>
|
||||
</div>
|
||||
</form>
|
||||
<p>
|
||||
<a href="/calculate-share-time">[Average share time calculator]</a> :: <a href="/connectivity-check">[Connectivity Check]</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/miners">[Current Window Miners]</a> :: <a href="/miners?weekly">[Weekly Miners]</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/transaction-lookup">[Sweep Transaction Lookup]</a> :: <a href="/sweeps">[Recent Likely Sweep Transactions]</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2>Recent Monero blocks found by P2Pool miners</h2>
|
||||
|
||||
{%= TemplateFoundBlocks(p.Context(), p.FoundBlocks, false) %}
|
||||
<div class="center"><a href="/blocks">[show more found blocks]</a></div>
|
||||
|
||||
<h3>Blocks found during last day</h3>
|
||||
<code class="mono">{%s p.Positions.BlocksFound.String() %}</code>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2>Recent P2Pool shares found</h2>
|
||||
{%= TemplateShares(p.Context(), p.Shares, false, nil) %}
|
||||
</div>
|
||||
{% endfunc %}
|
|
@ -1,183 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index" %}
|
||||
{% import cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils" %}
|
||||
|
||||
{% code
|
||||
type MinerOptionsPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
Miner *cmdutils.MinerInfoResult
|
||||
SignedAction *cmdutils.SignedAction
|
||||
WebHooks []*index.MinerWebHook
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *MinerOptionsPage) Title() %}
|
||||
{%= p.BasePage.Title() %} - Miner Options {%z= p.Miner.Address.ToBase58() %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *MinerOptionsPage) Content() %}
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2 class="mono">Miner Options</h2>
|
||||
<p><strong>Payout Address:</strong> <span class="mono small">{%z= p.Miner.Address.ToBase58() %}</span></p>
|
||||
|
||||
{% if p.Miner.Alias != "" %}
|
||||
<p><strong>Miner Alias:</strong> <span class="mono">{%s p.Miner.Alias %}</span></p>
|
||||
<p><small>Miner Alias is user content and not verified. This value should only be used for vanity purposes.</small></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if p.SignedAction != nil %}
|
||||
|
||||
<div style="text-align: center;">
|
||||
|
||||
<form action="/miner-options/{%z= p.Miner.Address.ToBase58() %}/signed_action" method="get" target="_blank">
|
||||
<h2>Sign and submit changes</h2>
|
||||
<p>To submit any changes you will need to sign a message using your wallet to prove ownership.</p>
|
||||
<p>On the Monero GUI, go to Advanced -> Sign/verify -> Select Mode "Message".</p>
|
||||
<p>Enter the Message listed below in the "Message" field, then press Sign Message. Copy the Signature and paste it on the field below.</p>
|
||||
<p>Do not modify the message, add spaces or add any new lines.</p>
|
||||
<p><small>Signatures generated by a View-Only wallet are not supported. Monero GUI does not generate valid signatures for hardware wallets.</small></p>
|
||||
<div>
|
||||
<label for="message">Message</label><br/>
|
||||
<input type="text" name="message" id="message" size="96" class="mono" value="{%s p.SignedAction.String() %}" style="user-select: all; -webkit-user-select: all;" readonly autofocus/>
|
||||
</div>
|
||||
<div style="margin-top: 10px">
|
||||
<label for="signature">Signature</label><br/>
|
||||
<input type="text" name="signature" id="signature" size="96" class="mono" placeholder="SigV2..." pattern="^SigV[12][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$" required/>
|
||||
</div>
|
||||
<div style="margin-top: 10px">
|
||||
<input type="submit" value="Submit changes" style="width: 20em;"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div style="text-align: center;">
|
||||
<h2 id="mineralias">Miner Alias</h2>
|
||||
|
||||
<div>
|
||||
<form action="/miner-options/{%z= p.Miner.Address.ToBase58() %}/set_miner_alias" method="get" target="_blank">
|
||||
<h3>Set Miner Alias</h3>
|
||||
<p><small>
|
||||
Miner Alias must be 4 to 20 characters long, and only 0-9, a-z, A-Z, and _-. are allowed, and cannot start with a number or symbol. No spaces are allowed either.
|
||||
</br>
|
||||
Alias are unique for the observer instance, and can be removed or changed after adding one.
|
||||
</small></p>
|
||||
<div>
|
||||
<label for="miner-alias">Miner Alias</label><br/>
|
||||
<input type="text" name="alias" id="miner-alias" minlength="4" maxlength="20" size="20" class="mono" value="{%s p.Miner.Alias %}" pattern="^[a-zA-Z][0-9a-zA-Z_.\-]+$" required/>
|
||||
</div>
|
||||
<div style="margin-top: 10px">
|
||||
<input type="submit" value="Set Alias" style="width: 20em;"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
{% if p.Miner.Alias != "" %}
|
||||
<form action="/miner-options/{%z= p.Miner.Address.ToBase58() %}/unset_miner_alias" method="get" target="_blank">
|
||||
<h3>Unset Miner Alias</h3>
|
||||
<p><small>You can remove your miner alias below if you don't want one set.</small></p>
|
||||
<div style="margin-top: 10px">
|
||||
<input type="submit" value="Unset Alias" style="width: 20em;"/>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center;">
|
||||
<h2 id="webhooks">Notification WebHooks</h2>
|
||||
|
||||
<div>
|
||||
<form action="/miner-options/{%z= p.Miner.Address.ToBase58() %}/add_webhook" method="get" target="_blank">
|
||||
<h3>Add WebHook</h3>
|
||||
<p><small>
|
||||
Configure notification URLs that can be used to post messages or other actions in other platforms or services.
|
||||
</br>
|
||||
Only one URL of each type is allowed. To adjust an existing hook settings you will need to add it with the new preference.
|
||||
</small></p>
|
||||
<div>
|
||||
<label for="webhook-type">WebHook Type</label><br/>
|
||||
<select type="text" name="type" id="webhook-type" required>
|
||||
<option value=""></option>
|
||||
<optgroup label="Chat Services">
|
||||
<option value="{%s string(index.WebHookDiscord) %}">Discord</option>
|
||||
<option value="{%s string(index.WebHookSlack) %}" disabled>Slack (coming soon)</option>
|
||||
<option value="{%s string(index.WebHookTelegram) %}" disabled>Telegram (coming soon)</option>
|
||||
<option value="{%s string(index.WebHookMatrixHookshot) %}" disabled>Matrix Hookshot (coming soon)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Other">
|
||||
<option value="{%s string(index.WebHookCustom) %}">Custom</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</br>
|
||||
<div>
|
||||
<label for="webhook-url">WebHook URL</label><br/>
|
||||
<input type="url" name="url" id="webhook-url" size="60" class="mono" value="" required/>
|
||||
</div>
|
||||
</br>
|
||||
<fieldset>
|
||||
<legend>WebHook Events</legend>
|
||||
<div>
|
||||
<input type="checkbox" id="webhook-side_blocks" name="side_blocks" checked />
|
||||
<label for="webhook-side_blocks">Miner Side blocks</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="webhook-payouts" name="payouts" checked />
|
||||
<label for="webhook-payouts">Miner Payouts</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="webhook-found_blocks" name="found_blocks" />
|
||||
<label for="webhook-found_blocks">Found Monero blocks</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="webhook-orphaned_blocks" name="orphaned_blocks" />
|
||||
<label for="webhook-orphaned_blocks">Orphaned Monero blocks</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="webhook-other" name="other" checked />
|
||||
<label for="webhook-other">Other (notices, updates, events)</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div style="margin-top: 10px">
|
||||
<input type="submit" value="Add WebHook" style="width: 20em;"/>
|
||||
</div>
|
||||
</form>
|
||||
{% if len(p.WebHooks) > 0 %}
|
||||
|
||||
<h3>Current WebHooks</h3>
|
||||
|
||||
<table class="center datatable" style="max-width: calc(8em + 28em + 8em + 8em + 8em + 8em + 8em + 8em)">
|
||||
<tr>
|
||||
<th style="width: 8em;">Type</th>
|
||||
<th style="width: 28em;" title="URL SHA-256 Hash">URL Hash</th>
|
||||
<th style="width: 8em;">Shares</th>
|
||||
<th style="width: 8em;">Payouts</th>
|
||||
<th style="width: 8em;">Found Blocks</th>
|
||||
<th style="width: 8em;">Orphaned Blocks</th>
|
||||
<th style="width: 8em;">Other</th>
|
||||
<th style="width: 8em;"></th>
|
||||
</tr>
|
||||
{% for _, w := range p.WebHooks %}
|
||||
<tr>
|
||||
<th>{%s string(w.Type) %}</th>
|
||||
<td class="mono small">{%s w.Url %}</td>
|
||||
<td>{% if cmdutils.CanSendToHook(w, "side_blocks") %}Yes{% else %}No{% endif %}</td>
|
||||
<td>{% if cmdutils.CanSendToHook(w, "payouts") %}Yes{% else %}No{% endif %}</td>
|
||||
<td>{% if cmdutils.CanSendToHook(w, "found_blocks") %}Yes{% else %}No{% endif %}</td>
|
||||
<td>{% if cmdutils.CanSendToHook(w, "orphaned_blocks") %}Yes{% else %}No{% endif %}</td>
|
||||
<td>{% if cmdutils.CanSendToHook(w, "other") %}Yes{% else %}No{% endif %}</td>
|
||||
<td><a href="/miner-options/{%z= p.Miner.Address.ToBase58() %}/remove_webhook?type={%s string(w.Type) %}&url_hash={%s w.Url %}">[Remove]</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endfunc %}
|
|
@ -1,277 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index" %}
|
||||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain" %}
|
||||
|
||||
{% import p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types" %}
|
||||
{% import cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils" %}
|
||||
|
||||
{% code
|
||||
type MinerPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
Refresh int
|
||||
Positions struct {
|
||||
Resolution int
|
||||
ResolutionWindow int
|
||||
SeparatorIndex int
|
||||
Blocks *cmdutils.PositionChart
|
||||
Uncles *cmdutils.PositionChart
|
||||
BlocksInWindow *cmdutils.PositionChart
|
||||
UnclesInWindow *cmdutils.PositionChart
|
||||
Payouts *cmdutils.PositionChart
|
||||
}
|
||||
ExpectedRewardPerWindow uint64
|
||||
ExpectedRewardPerDay uint64
|
||||
WindowWeight uint64
|
||||
Weight uint64
|
||||
Miner *cmdutils.MinerInfoResult
|
||||
LastPoolBlock *sidechain.PoolBlock
|
||||
LastShares []*index.SideBlock
|
||||
LastSharesEfforts []float64
|
||||
LastOrphanedShares []*index.SideBlock
|
||||
LastFound []*index.FoundBlock
|
||||
LastPayouts []*index.Payout
|
||||
LastSweeps <-chan *index.MainLikelySweepTransaction
|
||||
|
||||
HashrateSubmit bool
|
||||
HashrateLocal float64
|
||||
MagnitudeLocal float64
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *MinerPage) Title() %}
|
||||
{%= p.BasePage.Title() %} - Miner {%z= p.Miner.Address.ToBase58() %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *MinerPage) Content() %}
|
||||
<div style="text-align: center; font-weight: bold;">
|
||||
{% if p.Refresh > 0 %}
|
||||
<a href="/miner/{%z= p.Miner.Address.ToBase58() %}">Autorefresh is ON ({%d p.Refresh %} s)</a>
|
||||
{% else %}
|
||||
<a href="/miner/{%z= p.Miner.Address.ToBase58() %}?refresh">Autorefresh is OFF</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div style="text-align: center">
|
||||
{% if p.Miner.LastShareTimestamp == 0 %}
|
||||
<div style="border: #d1d1d1 1px dashed;">
|
||||
<h3 style="color:#FF4433">No shares have been reported to this P2Pool network in the past for this miner.</h3>
|
||||
|
||||
<p>Finding shares is a random process based on your hashrate vs this P2Pool hashrate. This can take several days for low hashrate miners, and depending on your luck.</p>
|
||||
<p>Please use the calculator below to find your average share time (with average luck).</p>
|
||||
<p>You can also verify you are reachable by opening ports with the tool below.</p>
|
||||
<br/>
|
||||
|
||||
<form action="/calculate-share-time" method="get">
|
||||
<h3>Average Share Time Calculator</h3>
|
||||
<label for="hashrate">Your Hashrate</label><br/>
|
||||
<input type="numeric" name="hashrate" id="hashrate" placeholder="100" size="8" class="mono" value=""/>
|
||||
<select name="magnitude">
|
||||
<option value="1" selected>H/s</option>
|
||||
<option value="1000">KH/s</option>
|
||||
<option value="1000000">MH/s</option>
|
||||
<option value="1000000000">GH/s</option>
|
||||
</select>
|
||||
<br/>
|
||||
<input type="submit" value="Calculate" style="width: 20em; margin: 20px;"/>
|
||||
</form>
|
||||
|
||||
|
||||
<form action="/connectivity-check" method="get">
|
||||
<h3>Connectivity Check</h3>
|
||||
<div>
|
||||
<label for="address">IP Address and Port</label><br/>
|
||||
<input type="text" name="address" id="address" placeholder="5.9.17.234:{%dul uint64(p.Context().Consensus.DefaultPort()) %}" size="20" class="mono" value=""/>
|
||||
</div>
|
||||
<br/>
|
||||
<div style="margin-top: 10px">
|
||||
<input type="submit" value="Check" style="width: 20em;"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
{% endif %}
|
||||
|
||||
{% if p.LastPoolBlock != nil && p.LastPoolBlock.ShareVersion() < sidechain.ShareVersion_V2 && p.LastPoolBlock.ShareVersionSignaling() != sidechain.ShareVersion_V2 %}
|
||||
<div style="border: #d1d1d1 1px dashed;">
|
||||
<h3 style="color:#FF4433" title="Share version {%s p.LastPoolBlock.ShareVersion().String() %}, signaling {%s p.LastPoolBlock.ShareVersionSignaling().String() %}">Recent shares indicate you are running an outdated version of P2Pool</h3>
|
||||
|
||||
<p>A new version of <a href="https://github.com/SChernykh/p2pool/releases/tag/v3.3">P2Pool (v3.0+)</a> has been released with several improvements, which requires a consensus change.</p>
|
||||
<p>P2Pool (not Monero!) has hardforked to new consensus rules on <strong>March 18th, 2023 at 21:00 UTC</strong>. All versions before P2Pool v3.0 are incompatible. P2Pool v3.3+ is recommended.</p>
|
||||
<p>If you keep using previous versions, you will keep mining as usual, but become almost a solo miner, as incompatible clients will mine on their own.</p>
|
||||
<p>{% if p.Context().NetServiceAddress == "p2pool.observer" %}After the fork, you can check on <a href="{%s p.Context().GetUrl("old.p2pool.observer") %}/miner/{%z= p.Miner.Address.ToBase58() %}">OLD.P2Pool.Observer</a>.{% elseif p.Context().NetServiceAddress == "mini.p2pool.observer" %}After the fork, you can check on <a href="https://{%s p.Context().GetUrl("old-mini.p2pool.observer") %}/miner/{%z= p.Miner.Address.ToBase58() %}">OLD-MINI.P2Pool.Observer</a>.{% else %}Please check on an observer tracking the old chain.{% endif %}</p>
|
||||
<p>After upgrading to a supported P2Pool version and mining a share, this message will be dismissed.</p>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
{% elseif p.LastPoolBlock != nil && p.LastPoolBlock.ShareVersion() > sidechain.ShareVersion_V1 && p.LastPoolBlock.Side.ExtraBuffer.SoftwareId == p2pooltypes.SoftwareIdP2Pool && p.LastPoolBlock.Side.ExtraBuffer.SoftwareVersion.String() != p.Context().Pool.Versions.P2Pool.ShortVersion().String() %}
|
||||
<div style="border: #d1d1d1 1px dashed;">
|
||||
<h3 style="color:#FF4433">Recent shares indicate you are running an older version of P2Pool</h3>
|
||||
|
||||
<p><a href="{%s p.Context().Pool.Versions.P2Pool.Link %}">P2Pool {%s p.Context().Pool.Versions.P2Pool.Version %}</a> has been released.</p>
|
||||
<p>Your most recent share indicates are currently running {%= software_info(p.LastPoolBlock.Side.ExtraBuffer.SoftwareId, p.LastPoolBlock.Side.ExtraBuffer.SoftwareVersion) %}. It is recommended to upgrade.</p>
|
||||
<p>After upgrading to this P2Pool version and mining a share, this message will be dismissed.</p>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
{% endif %}
|
||||
|
||||
<p><strong>Payout Address:</strong> <span class="mono small">{%z= p.Miner.Address.ToBase58() %}</span></p>
|
||||
|
||||
{% if p.Miner.Alias != "" %}
|
||||
<p><strong>Miner Alias:</strong> <span class="mono">{%s p.Miner.Alias %}</span></p>
|
||||
<p><small>Miner Alias is user content and not verified. This value should only be used for vanity purposes.</small></p>
|
||||
{% endif %}
|
||||
|
||||
<p><small><a href="/miner-options/{%z= p.Miner.Address.ToBase58() %}#mineralias">[Change Miner Alias]</a> :: <a href="/miner-options/{%z= p.Miner.Address.ToBase58() %}#webhooks">[Configure WebHook notifications]</a></small></p>
|
||||
|
||||
<table class="center" style="max-width: calc(15em + 15em + 15em + 15em + 15em)">
|
||||
<tr>
|
||||
<th>Last Share</th>
|
||||
<th>Current Shares</th>
|
||||
<th>Estimated Hashrate</th>
|
||||
<th>Pool Share %</th>
|
||||
<th>Estimated Window Reward</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td title="{%s utc_date(p.Miner.LastShareTimestamp) %}">{%s time_elapsed_short(p.Miner.LastShareTimestamp) %}</td>
|
||||
<td>{%dul p.Positions.BlocksInWindow.Total() %} blocks (+{%dul p.Positions.UnclesInWindow.Total() %} uncles)</td>
|
||||
{% code windowWeightRatio := float64(p.WindowWeight) / float64(p.Context().Pool.SideChain.Window.Weight.Lo) %}
|
||||
<td>{%s si_units(windowWeightRatio * float64(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime)), 3) %}H/s</td>
|
||||
<td>{%f.3 windowWeightRatio*100 %}%</td>
|
||||
<td>{%s monero_to_xmr(p.ExpectedRewardPerWindow) %} XMR</td>
|
||||
</tr>
|
||||
<tr><td colspan="5"> </td></tr>
|
||||
<tr>
|
||||
<th>Estimated Total Shares</th>
|
||||
<th>Day Shares</th>
|
||||
<th>Day Hashrate</th>
|
||||
<th>Day Share %</th>
|
||||
<th>Estimated Daily Reward</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% if p.LastPoolBlock != nil %}Around {%dul p.Miner.Shares[1].ShareCount %} blocks (+{%dul p.Miner.Shares[1].UncleCount %} uncles{% if p.Miner.Shares[0].ShareCount > 0 %}, +{%dul p.Miner.Shares[0].ShareCount %} orphans{% endif %}){% else %}No shares reported{% endif %}</td>
|
||||
<td>{%dul p.Positions.Blocks.Total() %} blocks (+{%dul p.Positions.Uncles.Total() %} uncles)</td>
|
||||
{% code weightRatio := (float64(p.Weight) / (float64(p.Context().Pool.SideChain.Window.Weight.Mul64(4).Lo) * (float64(p.Context().Pool.SideChain.Consensus.ChainWindowSize) / float64(p.Context().Pool.SideChain.Window.Blocks)))) %}
|
||||
<td>{%s si_units(weightRatio * float64(diff_hashrate(p.Context().Pool.SideChain.LastBlock.Difficulty, p.Context().Consensus.TargetBlockTime)), 3) %}H/s</td>
|
||||
<td>{%f.3 weightRatio*100 %}%</td>
|
||||
<td>{%s monero_to_xmr(p.ExpectedRewardPerDay) %} XMR</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<h2>Share positions</h2>
|
||||
<p class="small">
|
||||
Shares appear on the right, and get older towards the left.
|
||||
<br/>
|
||||
Number denotes the amount of shares per slice, with the plus (<code>+</code>) character being more than 9, and dot (<code>.</code>) being none.
|
||||
<br/>
|
||||
Each slice accounts for {%d p.Positions.Resolution %} P2Pool blocks, or around {%dul (uint64(p.Positions.Resolution) * p.Context().Consensus.TargetBlockTime) / 60 %} minutes.
|
||||
</p>
|
||||
<h3>Shares and uncles in PPLNS window</h3>
|
||||
<p class="small">
|
||||
Each slice accounts for {%d p.Positions.ResolutionWindow %} P2Pool block heights, or around {%dul (uint64(p.Positions.ResolutionWindow) * p.Context().Consensus.TargetBlockTime) / 60 %} minutes.
|
||||
<br/>
|
||||
Shares within the PPLNS window will be weighted towards receiving a payout when any Monero block is found by any P2Pool miner.
|
||||
</p>
|
||||
<code class="mono">{%s p.Positions.BlocksInWindow.String() %}</code>
|
||||
<br/>
|
||||
<code class="mono">{%s p.Positions.UnclesInWindow.String() %}</code>
|
||||
|
||||
<h3>Shares and uncles during last day</h3>
|
||||
<p class="small">
|
||||
Each slice accounts for {%d p.Positions.Resolution %} P2Pool block heights, or around {%dul (uint64(p.Positions.Resolution) * p.Context().Consensus.TargetBlockTime) / 60 %} minutes.
|
||||
<br/>
|
||||
The pipe (<code>|</code>) character denotes roughly the PPLNS window end.
|
||||
</p>
|
||||
<code class="mono">{%s p.Positions.Blocks.StringWithSeparator(p.Positions.SeparatorIndex) %}</code>
|
||||
<br/>
|
||||
<code class="mono">{%s p.Positions.Uncles.StringWithSeparator(p.Positions.SeparatorIndex) %}</code>
|
||||
|
||||
{% if p.Positions.Payouts.Total() > 0 %}
|
||||
<h3>Payouts during last day</h3>
|
||||
<code class="mono">{%s p.Positions.Payouts.StringWithSeparator(p.Positions.SeparatorIndex) %}</code>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2>Most recent payouts</h2>
|
||||
{%= TemplatePayoutsSlice(p.Context(), p.LastPayouts) %}
|
||||
<div class="center"><a href="/payouts/{%z= p.Miner.Address.ToBase58() %}">[show all historical payouts]</a></div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2>Most recent shares</h2>
|
||||
{% if p.HashrateSubmit || (p.Positions.Blocks.Total() + p.Positions.Uncles.Total()) > 2 %}
|
||||
{%= TemplateShares(p.Context(), p.LastShares, true, &p.LastSharesEfforts) %}
|
||||
{% else %}
|
||||
{%= TemplateShares(p.Context(), p.LastShares, true, nil) %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
{% if len(p.LastOrphanedShares) > 0 %}
|
||||
<div style="text-align: center">
|
||||
<h2>Most recent orphaned shares</h2>
|
||||
{%= TemplateShares(p.Context(), p.LastOrphanedShares, true, nil) %}
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
{% endif %}
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2>Most recent Monero blocks found</h2>
|
||||
{%= TemplateFoundBlocks(p.Context(), p.LastFound, true) %}
|
||||
<div class="center"><a href="/blocks?miner={%z= p.Miner.Address.ToBase58() %}">[show more found blocks]</a></div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2>Most recent likely sweeps</h2>
|
||||
{%= TemplateSweeps(p.Context(), p.LastSweeps, true) %}
|
||||
<div class="center"><a href="/sweeps?miner={%z= p.Miner.Address.ToBase58() %}">[show more likely sweeps]</a></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if p.Miner.LastShareTimestamp != 0 %}
|
||||
<hr/>
|
||||
|
||||
<div style="text-align: center">
|
||||
<form action="/calculate-share-time" method="get">
|
||||
<h3>Effort Calculation</h3>
|
||||
<p>Local hashrate of each P2Pool miner is not known by the network. A guess is calculated based on daily estimation. If you provide a value here, it will be more accurate for effort calculation.</p>
|
||||
<p>This data will not be saved.</p>
|
||||
<label for="hashrate_local">Your Local Hashrate</label><br/>
|
||||
<input type="numeric" name="hashrate" id="hashrate_local" placeholder="100" size="8" class="mono" value="{% if p.HashrateLocal > 0 %}{%s str(p.HashrateLocal) %}{% endif %}"/>
|
||||
<select name="magnitude">
|
||||
<option value="1"{% if p.MagnitudeLocal == 1 %} selected{% endif %}>H/s</option>
|
||||
<option value="1000"{% if p.MagnitudeLocal == 1000 %} selected{% endif %}>KH/s</option>
|
||||
<option value="1000000"{% if p.MagnitudeLocal == 1000000 %} selected{% endif %}>MH/s</option>
|
||||
<option value="1000000000"{% if p.MagnitudeLocal == 1000000000 %} selected{% endif %}>GH/s</option>
|
||||
</select>
|
||||
<br/>
|
||||
<input type="submit" value="Calculate" style="width: 20em; margin: 20px;"/>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfunc %}
|
|
@ -1,100 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/monero/address" %}
|
||||
{% import cmdutils "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/utils" %}
|
||||
{% import types "git.gammaspectra.live/P2Pool/p2pool-observer/types" %}
|
||||
{% import p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types" %}
|
||||
|
||||
{% code
|
||||
type MinersPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
Refresh int
|
||||
Weekly bool
|
||||
Miners []*MinersPageMinerEntry
|
||||
WindowWeight types.Difficulty
|
||||
}
|
||||
%}
|
||||
|
||||
{% code
|
||||
type MinersPageMinerEntry struct {
|
||||
Address *address.Address
|
||||
Alias string
|
||||
SoftwareId p2pooltypes.SoftwareId
|
||||
SoftwareVersion p2pooltypes.SoftwareVersion
|
||||
Weight types.Difficulty
|
||||
Shares *cmdutils.PositionChart
|
||||
Uncles *cmdutils.PositionChart
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *MinersPage) Title() %}
|
||||
{% if p.Weekly %}
|
||||
{%= p.BasePage.Title() %} - Current Window Miners
|
||||
{% else %}
|
||||
{%= p.BasePage.Title() %} - Weekly Miners
|
||||
{% endif %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *MinersPage) Content() %}
|
||||
{% if p.Weekly %}
|
||||
<div style="text-align: center; font-weight: bold;">
|
||||
{% if p.Refresh > 0 %}
|
||||
<a href="/miners?week">Autorefresh is ON ({%d p.Refresh %} s)</a>
|
||||
{% else %}
|
||||
<a href="/blocks?week&refresh">Autorefresh is OFF</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="text-align: center; font-weight: bold;">
|
||||
{% if p.Refresh > 0 %}
|
||||
<a href="/miners">Autorefresh is ON ({%d p.Refresh %} s)</a>
|
||||
{% else %}
|
||||
<a href="/blocks?refresh">Autorefresh is OFF</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div style="text-align: center">
|
||||
|
||||
{% if p.Weekly %}
|
||||
<h2>Weekly Miners</h2>
|
||||
<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 + 10em + 8em + 8em + 12em + 32em)">
|
||||
{% else %}
|
||||
<h2>Current Window Miners</h2>
|
||||
<p>This is a list of the miners that have shares in the current window, and would be rewarded when a Monero block is found within the window.</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 + 10em + 8em + 8em + 12em + 24em)">
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th style="width: 4em;">#</th>
|
||||
<th style="width: 12em;">Miner</th>
|
||||
<th style="width: 10em;">Software</th>
|
||||
<th style="width: 8em;">Pool share %</th>
|
||||
<th style="width: 8em;">Estimated Hashrate</th>
|
||||
<th style="width: 12em;">Shares found</th>
|
||||
<th style="width: {% if p.Weekly %}32{% else %}24{% endif %}em;">Shares position</th>
|
||||
</tr>
|
||||
{% for i, m := range p.Miners %}
|
||||
<tr style="padding-bottom: 10px; border-bottom: solid #aaa 1px">
|
||||
<td style="vertical-align: middle">{%d i+1 %}</td>
|
||||
{%= TemplateRowMinerWithTag(p.Context(), m.Address, m.Alias, "th") %}
|
||||
<td style="vertical-align: middle">{%= software_info(m.SoftwareId, m.SoftwareVersion) %}</td>
|
||||
{% code minerRatio := float64(m.Weight.Lo) / float64(p.WindowWeight.Lo) %}
|
||||
<td style="vertical-align: middle">{%f.3 minerRatio*100 %}%</td>
|
||||
<td style="vertical-align: middle">{%s si_units(minerRatio * (float64(p.Context().Pool.SideChain.LastBlock.Difficulty) / float64(p.Context().Consensus.TargetBlockTime)), 3) %}H/s</td>
|
||||
<td style="vertical-align: middle">{%dul m.Shares.Total() %} block(s) +{%dul m.Uncles.Total() %} uncle(s)</td>
|
||||
<td>
|
||||
<code class="mono small">{%s m.Shares.String() %}</code>
|
||||
<br/>
|
||||
<code class="mono small">{%s m.Uncles.String() %}</code>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endfunc %}
|
|
@ -1,40 +0,0 @@
|
|||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/cmd/index" %}
|
||||
{% import "git.gammaspectra.live/P2Pool/p2pool-observer/monero/address" %}
|
||||
|
||||
{% code
|
||||
type PayoutsPage struct {
|
||||
// inherit from base page, so its' title is used in error page.
|
||||
BasePage
|
||||
Refresh int
|
||||
Miner *address.Address
|
||||
Payouts <-chan *index.Payout
|
||||
}
|
||||
%}
|
||||
|
||||
{% func (p *PayoutsPage) Title() %}
|
||||
{%= p.BasePage.Title() %} - Historical miner payouts {%z= p.Miner.ToBase58() %}
|
||||
{% endfunc %}
|
||||
|
||||
{% func (p *PayoutsPage) Content() %}
|
||||
|
||||
|
||||
<div style="text-align: center; font-weight: bold;">
|
||||
{% if p.Refresh > 0 %}
|
||||
<a href="/payouts/{%z= p.Miner.ToBase58() %}">Autorefresh is ON ({%d p.Refresh %} s)</a>
|
||||
{% else %}
|
||||
<a href="/payouts/{%z= p.Miner.ToBase58() %}?refresh">Autorefresh is OFF</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div style="text-align: center">
|
||||
<h2>Historical miner payouts</h2>
|
||||
<p><strong>Payout Address:</strong> <a href="/miner/{%z= p.Miner.ToBase58() %}"><span class="mono small">{%z= p.Miner.ToBase58() %}</span></a></p>
|
||||
|
||||
{% code var total uint64 %}
|
||||
|
||||
{%= TemplatePayouts(p.Context(), p.Payouts, &total) %}
|
||||
|
||||
<p><strong>Estimated total:</strong> {%s monero_to_xmr(total) %} XMR</p>
|
||||
</div>
|
||||
|
||||
{% endfunc %}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue