consensus/p2pool/api/api.go

322 lines
8.2 KiB
Go

package api
import (
"encoding/hex"
"encoding/json"
"fmt"
"git.gammaspectra.live/P2Pool/p2pool-observer/database"
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool"
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"io"
"os"
"path"
"strconv"
)
// Api
// Deprecated
type Api struct {
db *database.Database
path string
}
func New(db *database.Database, p string) (*Api, error) {
api := &Api{
db: db,
path: path.Clean(p),
}
if info, err := os.Stat(api.path); err != nil {
return nil, err
} else if !info.IsDir() {
return nil, fmt.Errorf("directory path does not exist %s %s", p, api.path)
}
return api, nil
}
func (a *Api) getBlockPath(height uint64) string {
index := strconv.FormatInt(int64(height), 10)
return fmt.Sprintf("%s/share/%s/%s", a.path, index[len(index)-1:], index)
}
func (a *Api) getRawBlockPath(id types.Hash) string {
index := id.String()
return fmt.Sprintf("%s/blocks/%s/%s", a.path, index[:1], index)
}
func (a *Api) getFailedRawBlockPath(id types.Hash) string {
index := id.String()
return fmt.Sprintf("%s/failed_blocks/%s/%s", a.path, index[:1], index)
}
func (a *Api) BlockExists(height uint64) bool {
_, err := os.Stat(a.getBlockPath(height))
return err == nil
}
func (a *Api) GetShareEntry(height uint64) (*database.Block, []*database.UncleBlock, error) {
if f, err := os.Open(a.getBlockPath(height)); err != nil {
return nil, nil, err
} else {
defer f.Close()
if buf, err := io.ReadAll(f); err != nil {
return nil, nil, err
} else {
return database.NewBlockFromJSONBlock(a.db, buf)
}
}
}
func (a *Api) GetShareFromFailedRawEntry(id types.Hash) (*database.Block, error) {
if b, err := a.GetFailedRawBlock(id); err != nil {
return nil, err
} else {
b, _, err := database.NewBlockFromBinaryBlock(a.db, b, nil, false)
return b, err
}
}
func (a *Api) GetFailedRawBlockBytes(id types.Hash) (buf []byte, err error) {
if f, err := os.Open(a.getFailedRawBlockPath(id)); err != nil {
return nil, err
} else {
defer f.Close()
return io.ReadAll(f)
}
}
func (a *Api) GetFailedRawBlock(id types.Hash) (b *sidechain.PoolBlock, err error) {
if buf, err := a.GetFailedRawBlockBytes(id); err != nil {
return nil, err
} else {
data := make([]byte, len(buf)/2)
_, _ = hex.Decode(data, buf)
return sidechain.NewShareFromExportedBytes(data, sidechain.NetworkMainnet)
}
}
func (a *Api) GetRawBlockBytes(id types.Hash) (buf []byte, err error) {
if f, err := os.Open(a.getRawBlockPath(id)); err != nil {
return nil, err
} else {
defer f.Close()
return io.ReadAll(f)
}
}
func (a *Api) GetRawBlock(id types.Hash) (b *sidechain.PoolBlock, err error) {
if buf, err := a.GetRawBlockBytes(id); err != nil {
return nil, err
} else {
data := make([]byte, len(buf)/2)
_, _ = hex.Decode(data, buf)
return sidechain.NewShareFromExportedBytes(data, sidechain.NetworkMainnet)
}
}
func (a *Api) GetDatabase() *database.Database {
return a.db
}
func (a *Api) GetShareFromRawEntry(id types.Hash, errOnUncles bool) (b *database.Block, uncles []*database.UncleBlock, err error) {
var raw *sidechain.PoolBlock
if raw, err = a.GetRawBlock(id); err != nil {
return
} else {
u := make([]*sidechain.PoolBlock, 0, len(raw.Side.Uncles))
for _, uncleId := range raw.Side.Uncles {
if uncle, err := a.GetRawBlock(uncleId); err != nil {
return nil, nil, err
} else {
u = append(u, uncle)
}
}
return database.NewBlockFromBinaryBlock(a.db, raw, u, errOnUncles)
}
}
func (a *Api) GetBlockWindowPayouts(tip *database.Block) (shares map[uint64]types.Difficulty) {
shares = make(map[uint64]types.Difficulty)
blockCount := 0
block := tip
blockCache := make(map[uint64]*database.Block, p2pool.PPLNSWindow)
for b := range a.db.GetBlocksInWindow(&tip.Height, p2pool.PPLNSWindow) {
blockCache[b.Height] = b
}
for {
if _, ok := shares[block.MinerId]; !ok {
shares[block.MinerId] = types.DifficultyFrom64(0)
}
shares[block.MinerId] = shares[block.MinerId].Add(block.Difficulty)
for uncle := range a.db.GetUnclesByParentId(block.Id) {
if (tip.Height - uncle.Block.Height) >= p2pool.PPLNSWindow {
continue
}
if _, ok := shares[uncle.Block.MinerId]; !ok {
shares[uncle.Block.MinerId] = types.DifficultyFrom64(0)
}
product := uncle.Block.Difficulty.Mul64(p2pool.UnclePenalty)
unclePenalty := product.Div64(100)
shares[block.MinerId] = shares[block.MinerId].Add(unclePenalty)
shares[uncle.Block.MinerId] = shares[uncle.Block.MinerId].Add(uncle.Block.Difficulty.Sub(unclePenalty))
}
blockCount++
if b, ok := blockCache[block.Height-1]; ok && b.Id == block.PreviousId {
block = b
} else {
block = a.db.GetBlockById(block.PreviousId)
}
if block == nil || blockCount >= p2pool.PPLNSWindow {
break
}
}
totalReward := tip.Coinbase.Reward
if totalReward > 0 {
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(totalReward).Div(totalWeight)
shares[miner] = nextValue.Sub(rewardGiven)
rewardGiven = nextValue
}
}
if blockCount != p2pool.PPLNSWindow {
return nil
}
return shares
}
func (a *Api) GetWindowPayouts(height, totalReward *uint64) (shares map[uint64]types.Difficulty) {
shares = make(map[uint64]types.Difficulty)
var tip uint64
if height != nil {
tip = *height
} else {
tip = a.db.GetChainTip().Height
}
blockCount := 0
for block := range a.db.GetBlocksInWindow(&tip, p2pool.PPLNSWindow) {
if _, ok := shares[block.MinerId]; !ok {
shares[block.MinerId] = types.DifficultyFrom64(0)
}
shares[block.MinerId] = shares[block.MinerId].Add(block.Difficulty)
for uncle := range a.db.GetUnclesByParentId(block.Id) {
if (tip - uncle.Block.Height) >= p2pool.PPLNSWindow {
continue
}
if _, ok := shares[uncle.Block.MinerId]; !ok {
shares[uncle.Block.MinerId] = types.DifficultyFrom64(0)
}
product := uncle.Block.Difficulty.Mul64(p2pool.UnclePenalty)
unclePenalty := product.Div64(100)
shares[block.MinerId] = shares[block.MinerId].Add(unclePenalty)
shares[uncle.Block.MinerId] = shares[uncle.Block.MinerId].Add(uncle.Block.Difficulty.Sub(unclePenalty))
}
blockCount++
}
if totalReward != nil && *totalReward > 0 {
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(*totalReward).Div(totalWeight)
shares[miner] = nextValue.Sub(rewardGiven)
rewardGiven = nextValue
}
}
if blockCount != p2pool.PPLNSWindow {
return nil
}
return shares
}
func (a *Api) GetPoolBlocks() (result []struct {
Height uint64 `json:"height"`
Hash types.Hash `json:"hash"`
Difficulty uint64 `json:"difficulty"`
TotalHashes uint64 `json:"totalHashes"`
Ts uint64 `json:"ts"`
}, err error) {
f, err := os.Open(fmt.Sprintf("%s/pool/blocks", a.path))
if err != nil {
return result, err
}
defer f.Close()
if buf, err := io.ReadAll(f); err != nil {
return result, err
} else {
err = json.Unmarshal(buf, &result)
return result, err
}
}
func (a *Api) GetPoolStats() (result struct {
PoolList []string `json:"pool_list"`
PoolStatistics struct {
HashRate uint64 `json:"hashRate"`
Difficulty uint64 `json:"difficulty"`
Hash types.Hash `json:"hash"`
Height uint64 `json:"height"`
Miners uint64 `json:"miners"`
TotalHashes uint64 `json:"totalHashes"`
LastBlockFoundTime uint64 `json:"lastBlockFoundTime"`
LastBlockFound uint64 `json:"lastBlockFound"`
TotalBlocksFound uint64 `json:"totalBlocksFound"`
} `json:"pool_statistics"`
}, err error) {
f, err := os.Open(fmt.Sprintf("%s/pool/stats", a.path))
if err != nil {
return result, err
}
defer f.Close()
if buf, err := io.ReadAll(f); err != nil {
return result, err
} else {
err = json.Unmarshal(buf, &result)
return result, err
}
}