Update to consensus v3.6.0

This commit is contained in:
DataHoarder 2024-04-13 05:43:18 +02:00
parent 4caa6b12bc
commit 0d1ebd3913
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
14 changed files with 1216 additions and 80 deletions

286
api/api_types.go Normal file
View file

@ -0,0 +1,286 @@
package api
import (
"git.gammaspectra.live/P2Pool/consensus/v3/monero"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/address"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
p2pooltypes "git.gammaspectra.live/P2Pool/consensus/v3/p2pool/types"
"git.gammaspectra.live/P2Pool/consensus/v3/types"
"git.gammaspectra.live/P2Pool/observer-cmd-utils/index"
"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() p2pooltypes.SemanticVersion {
parts := strings.Split(v.Version, ".")
for len(parts) < 2 {
parts = append(parts, "0")
}
for len(parts) > 2 {
parts = parts[:len(parts)-1]
}
return p2pooltypes.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 p2pooltypes.SoftwareId `json:"software_id"`
SoftwareVersion p2pooltypes.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 []monero.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"`
}
type P2PoolSideChainStateResult struct {
TipHeight uint64 `json:"tip_height"`
TipId types.Hash `json:"tip_id"`
Chain []P2PoolBinaryBlockResult `json:"chain"`
Uncles []P2PoolBinaryBlockResult `json:"uncles"`
}
type P2PoolBinaryBlockResult struct {
Version int `json:"version"`
Blob types.Bytes `json:"blob"`
Error string `json:"error,omitempty"`
}
type P2PoolSpecialBinaryBlockResult struct {
Version int `json:"version"`
Blob types.Bytes `json:"blob"`
Error string `json:"error,omitempty"`
}
type P2PoolSideChainStatusResult struct {
Synchronized bool `json:"synchronized"`
Height uint64 `json:"tip_height"`
Id types.Hash `json:"tip_id"`
Difficulty types.Difficulty `json:"difficulty"`
CumulativeDifficulty types.Difficulty `json:"cumulative_difficulty"`
Blocks int `json:"blocks"`
}
type P2PoolServerStatusResult struct {
PeerId uint64 `json:"peer_id"`
SoftwareId string `json:"software_id"`
SoftwareVersion string `json:"software_version"`
ProtocolVersion string `json:"protocol_version"`
ListenPort uint16 `json:"listen_port"`
}
type P2PoolServerPeerResult struct {
PeerId uint64 `json:"peer_id"`
Incoming bool `json:"incoming"`
Address string `json:"address"`
SoftwareId string `json:"software_id"`
SoftwareVersion string `json:"software_version"`
ProtocolVersion string `json:"protocol_version"`
ConnectionTime uint64 `json:"connection_time"`
ListenPort uint32 `json:"listen_port"`
Latency uint64 `json:"latency"`
}
type P2PoolConnectionCheckInformation[TipType any] struct {
Address string `json:"address"`
Port uint16 `json:"port"`
ListenPort uint16 `json:"listen_port"`
PeerId uint64 `json:"peer_id"`
SoftwareId string `json:"software_id"`
SoftwareVersion string `json:"software_version"`
ProtocolVersion string `json:"protocol_version"`
ConnectionTime uint64 `json:"connection_time"`
Latency uint64 `json:"latency"`
LastActive uint64 `json:"last_active"`
Incoming bool `json:"incoming"`
BroadcastTime uint64 `json:"broadcast_time"`
BroadcastHeight uint64 `json:"broadcast_height"`
// Tip is a sidechain.PoolBlock
Tip TipType `json:"tip,omitempty"`
Closed bool `json:"closed"`
AlreadyConnected bool `json:"already_connected"`
HandshakeComplete bool `json:"handshake_complete"`
Banned bool `json:"banned"`
Error string `json:"error,omitempty"`
BanError string `json:"ban_error,omitempty"`
}

739
api/p2pool.go Normal file
View file

@ -0,0 +1,739 @@
package api
import (
"bytes"
"errors"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/block"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/randomx"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
p2pooltypes "git.gammaspectra.live/P2Pool/consensus/v3/p2pool/types"
"git.gammaspectra.live/P2Pool/consensus/v3/types"
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
"github.com/hashicorp/golang-lru/v2"
"io"
"net/http"
"net/netip"
"net/url"
"strconv"
"sync/atomic"
"time"
)
type P2PoolApi struct {
Host string
Client *http.Client
consensus atomic.Pointer[sidechain.Consensus]
derivationCache sidechain.DerivationCacheInterface
difficultyByHeightCache *lru.Cache[uint64, types.Difficulty]
}
func NewP2PoolApi(host string) *P2PoolApi {
cache, err := lru.New[uint64, types.Difficulty](1024)
if err != nil {
return nil
}
return &P2PoolApi{
Host: host,
Client: &http.Client{
Timeout: time.Second * 15,
},
derivationCache: sidechain.NewDerivationLRUCache(),
difficultyByHeightCache: cache,
}
}
func (p *P2PoolApi) WaitSync() (err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New("panicked")
}
}()
status := p.Status()
for ; p == nil || !p.Status().Synchronized; status = p.Status() {
if p == nil {
utils.Logf("API", "Not synchronized (nil), waiting five seconds")
} else {
utils.Logf("API", "Not synchronized (height %d, id %s, blocks %d), waiting five seconds", status.Height, status.Id, status.Blocks)
}
time.Sleep(time.Second * 5)
}
utils.Logf("API", "SYNCHRONIZED (height %d, id %s, blocks %d)", status.Height, status.Id, status.Blocks)
utils.Logf("API", "Consensus id = %s\n", p.Consensus().Id)
return nil
}
func (p *P2PoolApi) WaitSyncStart() (err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New("panicked")
}
}()
status := p.Status()
for ; p == nil || !p.Status().Synchronized; status = p.Status() {
if p == nil {
utils.Logf("API", "Not synchronized (nil), waiting one seconds")
} else {
utils.Logf("API", "Not synchronized (height %d, id %s, blocks %d)", status.Height, status.Id, status.Blocks)
break
}
time.Sleep(time.Second * 1)
}
if status.Synchronized {
utils.Logf("API", "SYNCHRONIZED (height %d, id %s, blocks %d)", status.Height, status.Id, status.Blocks)
}
utils.Logf("API", "Consensus id = %s\n", p.Consensus().Id)
return nil
}
func (p *P2PoolApi) InsertAlternate(b *sidechain.PoolBlock) {
buf, _ := b.MarshalBinary()
uri, _ := url.Parse(p.Host + "/archive/insert_alternate")
response, err := p.Client.Do(&http.Request{
Method: "POST",
URL: uri,
Body: io.NopCloser(bytes.NewReader(buf)),
})
if err != nil {
return
}
defer response.Body.Close()
}
func (p *P2PoolApi) LightByMainId(id types.Hash) *sidechain.PoolBlock {
if response, err := p.Client.Get(p.Host + "/archive/light_block_by_main_id/" + id.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
b := &sidechain.PoolBlock{}
if err = utils.UnmarshalJSON(buf, &b); err != nil || b.ShareVersion() == sidechain.ShareVersion_None {
return nil
}
return b
}
}
}
func (p *P2PoolApi) LightByMainIdWithHint(id, templateIdHint types.Hash) *sidechain.PoolBlock {
if response, err := p.Client.Get(p.Host + "/archive/light_block_by_main_id/" + id.String() + "?templateIdHint=" + templateIdHint.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
b := &sidechain.PoolBlock{}
if err = utils.UnmarshalJSON(buf, &b); err != nil || b.ShareVersion() == sidechain.ShareVersion_None {
return nil
}
return b
}
}
}
func (p *P2PoolApi) ByMainId(id types.Hash) *sidechain.PoolBlock {
if response, err := p.Client.Get(p.Host + "/archive/block_by_main_id/" + id.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result P2PoolBinaryBlockResult
if err = utils.UnmarshalJSON(buf, &result); err != nil || result.Version == 0 {
return nil
}
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, result.Blob); err != nil || int(b.ShareVersion()) != result.Version {
return nil
}
return b
}
}
}
func (p *P2PoolApi) ByMainIdWithHint(id, templateIdHint types.Hash) *sidechain.PoolBlock {
if response, err := p.Client.Get(p.Host + "/archive/block_by_main_id/" + id.String() + "?templateIdHint=" + templateIdHint.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result P2PoolBinaryBlockResult
if err = utils.UnmarshalJSON(buf, &result); err != nil || result.Version == 0 {
return nil
}
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, result.Blob); err != nil || int(b.ShareVersion()) != result.Version {
return nil
}
return b
}
}
}
func (p *P2PoolApi) LightByTemplateId(id types.Hash) sidechain.UniquePoolBlockSlice {
if response, err := p.Client.Get(p.Host + "/archive/light_blocks_by_template_id/" + id.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result sidechain.UniquePoolBlockSlice
if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
return nil
}
return result
}
}
}
func (p *P2PoolApi) ByTemplateId(id types.Hash) *sidechain.PoolBlock {
if response, err := p.Client.Get(p.Host + "/sidechain/block_by_template_id/" + id.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result P2PoolBinaryBlockResult
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil
} else if result.Version == 0 {
// Fallback into archive
if response, err := p.Client.Get(p.Host + "/archive/blocks_by_template_id/" + id.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result []P2PoolBinaryBlockResult
if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
return nil
}
for _, r := range result {
//Get first block that matches
if r.Version == 0 {
continue
}
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil || int(b.ShareVersion()) != r.Version {
continue
}
return b
}
return nil
}
}
}
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, result.Blob); err != nil {
return nil
}
return b
}
}
}
func (p *P2PoolApi) LightBySideHeight(height uint64) sidechain.UniquePoolBlockSlice {
if response, err := p.Client.Get(p.Host + "/archive/light_blocks_by_side_height/" + strconv.FormatUint(height, 10)); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result sidechain.UniquePoolBlockSlice
if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
return nil
}
return result
}
}
}
func (p *P2PoolApi) BySideHeight(height uint64) sidechain.UniquePoolBlockSlice {
if response, err := p.Client.Get(p.Host + "/sidechain/blocks_by_height/" + strconv.FormatUint(height, 10)); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result []P2PoolBinaryBlockResult
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil
} else if len(result) == 0 {
// Fallback into archive
if response, err := p.Client.Get(p.Host + "/archive/blocks_by_side_height/" + strconv.FormatUint(height, 10)); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result []P2PoolBinaryBlockResult
if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
return nil
}
results := make([]*sidechain.PoolBlock, 0, len(result))
for _, r := range result {
if r.Version == 0 {
return nil
}
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil
}
results = append(results, b)
}
return results
}
}
}
results := make([]*sidechain.PoolBlock, 0, len(result))
for _, r := range result {
if r.Version == 0 {
return nil
}
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil
}
results = append(results, b)
}
return results
}
}
}
func (p *P2PoolApi) LightByMainHeight(height uint64) sidechain.UniquePoolBlockSlice {
if response, err := p.Client.Get(p.Host + "/archive/light_blocks_by_main_height/" + strconv.FormatUint(height, 10)); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result sidechain.UniquePoolBlockSlice
if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
return nil
}
return result
}
}
}
func (p *P2PoolApi) ByMainHeight(height uint64) sidechain.UniquePoolBlockSlice {
if response, err := p.Client.Get(p.Host + "/archive/blocks_by_main_height/" + strconv.FormatUint(height, 10)); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result []P2PoolBinaryBlockResult
if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
return nil
}
results := make([]*sidechain.PoolBlock, 0, len(result))
for _, r := range result {
if r.Version == 0 {
return nil
}
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil
}
results = append(results, b)
}
return results
}
}
}
func (p *P2PoolApi) DifficultyByHeight(height uint64) types.Difficulty {
if v, ok := p.difficultyByHeightCache.Get(height); !ok {
if diff := p.MainDifficultyByHeight(height); diff != types.ZeroDifficulty {
p.difficultyByHeightCache.Add(height, diff)
return diff
}
return types.ZeroDifficulty
} else {
return v
}
}
func (p *P2PoolApi) SeedByHeight(height uint64) types.Hash {
seedHeight := randomx.SeedHeight(height)
if v := p.MainHeaderByHeight(seedHeight); v != nil {
return v.Id
}
return types.ZeroHash
}
func (p *P2PoolApi) PeerList() []byte {
if response, err := p.Client.Get(p.Host + "/server/peerlist"); err != nil {
return nil
} else {
defer response.Body.Close()
buf, err := io.ReadAll(response.Body)
if err == nil {
return buf
}
}
return nil
}
func (p *P2PoolApi) ConnectionCheck(addrPort netip.AddrPort) *P2PoolConnectionCheckInformation[*sidechain.PoolBlock] {
if response, err := p.Client.Get(p.Host + "/server/connection_check/" + addrPort.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result P2PoolConnectionCheckInformation[*sidechain.PoolBlock]
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil
}
return &result
}
}
}
func (p *P2PoolApi) MinerData() *p2pooltypes.MinerData {
if response, err := p.Client.Get(p.Host + "/mainchain/miner_data"); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result p2pooltypes.MinerData
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil
}
return &result
}
}
}
func (p *P2PoolApi) MainTip() *block.Header {
if response, err := p.Client.Get(p.Host + "/mainchain/tip"); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result block.Header
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil
}
return &result
}
}
}
func (p *P2PoolApi) MainHeaderById(id types.Hash) *block.Header {
if response, err := p.Client.Get(p.Host + "/mainchain/header_by_id/" + id.String()); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result block.Header
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil
}
return &result
}
}
}
func (p *P2PoolApi) MainHeaderByHeight(height uint64) *block.Header {
if response, err := p.Client.Get(p.Host + "/mainchain/header_by_height/" + strconv.FormatUint(height, 10)); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result block.Header
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil
}
return &result
}
}
}
func (p *P2PoolApi) MainDifficultyByHeight(height uint64) types.Difficulty {
if response, err := p.Client.Get(p.Host + "/mainchain/difficulty_by_height/" + strconv.FormatUint(height, 10)); err != nil {
return types.ZeroDifficulty
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return types.ZeroDifficulty
} else {
var result types.Difficulty
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return types.ZeroDifficulty
}
return result
}
}
}
func (p *P2PoolApi) StateFromTemplateId(id types.Hash) (chain, uncles sidechain.UniquePoolBlockSlice) {
if response, err := p.Client.Get(p.Host + "/sidechain/state/" + id.String()); err != nil {
return nil, nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil, nil
} else {
var result P2PoolSideChainStateResult
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil, nil
}
chain = make([]*sidechain.PoolBlock, 0, len(result.Chain))
uncles = make([]*sidechain.PoolBlock, 0, len(result.Uncles))
for _, r := range result.Chain {
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil, nil
}
chain = append(chain, b)
}
for _, r := range result.Uncles {
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil, nil
}
uncles = append(uncles, b)
}
return chain, uncles
}
}
}
func (p *P2PoolApi) WindowFromTemplateId(id types.Hash) (chain, uncles sidechain.UniquePoolBlockSlice) {
if response, err := p.Client.Get(p.Host + "/archive/window_from_template_id/" + id.String()); err != nil {
return nil, nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil, nil
} else {
var result P2PoolSideChainStateResult
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil, nil
}
chain = make([]*sidechain.PoolBlock, 0, len(result.Chain))
uncles = make([]*sidechain.PoolBlock, 0, len(result.Uncles))
for _, r := range result.Chain {
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil, nil
}
chain = append(chain, b)
}
for _, r := range result.Uncles {
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil, nil
}
uncles = append(uncles, b)
}
return chain, uncles
}
}
}
func (p *P2PoolApi) StateFromTip() (chain, uncles sidechain.UniquePoolBlockSlice) {
if response, err := p.Client.Get(p.Host + "/sidechain/state/tip"); err != nil {
return nil, nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil, nil
} else {
var result P2PoolSideChainStateResult
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil, nil
}
chain = make([]*sidechain.PoolBlock, 0, len(result.Chain))
uncles = make([]*sidechain.PoolBlock, 0, len(result.Uncles))
for _, r := range result.Chain {
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil, nil
}
chain = append(chain, b)
}
for _, r := range result.Uncles {
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
return nil, nil
}
uncles = append(uncles, b)
}
return chain, uncles
}
}
}
func (p *P2PoolApi) Tip() *sidechain.PoolBlock {
if response, err := p.Client.Get(p.Host + "/sidechain/tip"); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
var result P2PoolBinaryBlockResult
if err = utils.UnmarshalJSON(buf, &result); err != nil {
return nil
}
if result.Version == 0 {
return nil
}
b := &sidechain.PoolBlock{}
if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, result.Blob); err != nil {
return nil
}
return b
}
}
}
func (p *P2PoolApi) getConsensus() *sidechain.Consensus {
if response, err := p.Client.Get(p.Host + "/sidechain/consensus"); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
c, _ := sidechain.NewConsensusFromJSON(buf)
return c
}
}
}
func (p *P2PoolApi) Consensus() *sidechain.Consensus {
if c := p.consensus.Load(); c == nil {
if c = p.getConsensus(); c != nil {
p.consensus.Store(c)
}
return c
} else {
return c
}
}
func (p *P2PoolApi) Status() *P2PoolSideChainStatusResult {
if response, err := p.Client.Get(p.Host + "/sidechain/status"); err != nil {
return nil
} else {
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
result := &P2PoolSideChainStatusResult{}
if err = utils.UnmarshalJSON(buf, result); err != nil {
return nil
}
return result
}
}
}

12
go.mod
View file

@ -3,10 +3,10 @@ module git.gammaspectra.live/P2Pool/observer-cmd-utils
go 1.22
require (
git.gammaspectra.live/P2Pool/consensus/v3 v3.3.0
git.gammaspectra.live/P2Pool/consensus/v3 v3.6.0
git.gammaspectra.live/P2Pool/sha3 v0.17.0
github.com/floatdrop/lru v1.3.0
github.com/goccy/go-json v0.10.2
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/jxskiss/base62 v1.1.0
github.com/lib/pq v1.10.9
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
@ -14,15 +14,17 @@ require (
require (
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20240405085108-e2f706cb5c00 // indirect
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221027085532-f46adfce03a7 // indirect
git.gammaspectra.live/P2Pool/go-randomx/v2 v2.1.0 // indirect
git.gammaspectra.live/P2Pool/monero-base58 v1.0.0 // indirect
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20230514082649-9c5f18cd5a71 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
git.gammaspectra.live/P2Pool/randomx-go-bindings v1.0.0 // indirect
github.com/dolthub/maphash v0.1.0 // indirect
github.com/dolthub/swiss v0.2.2-0.20240312182618-f4b2babd2bc1 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.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
replace github.com/go-zeromq/zmq4 => git.gammaspectra.live/P2Pool/zmq4 v0.16.1-0.20240412221749-a35fa84ca9f4

22
go.sum
View file

@ -1,41 +1,39 @@
git.gammaspectra.live/P2Pool/consensus/v3 v3.3.0 h1:UmdTohNb/KuGJSwR4p0NOIzRaw/do7xRQ0rR9PpSzNQ=
git.gammaspectra.live/P2Pool/consensus/v3 v3.3.0/go.mod h1:pI4X4sieSwKWicOkRZ90UZEQeZbvd2swzYjwKfGp46Y=
git.gammaspectra.live/P2Pool/consensus/v3 v3.6.0 h1:ICTC2jzsnfoXbFlpFK0U3pEIX04GL4ncmQ6MW4Xsz7A=
git.gammaspectra.live/P2Pool/consensus/v3 v3.6.0/go.mod h1:3mjTaEx2Hm9VUs0rZIjCAFV6fToNxnVDj616NEc9Y0I=
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20240405085108-e2f706cb5c00 h1:mDQY337iKB+kle5RYWL5CoAz+3DmnkAh/B2XD8B+PFk=
git.gammaspectra.live/P2Pool/edwards25519 v0.0.0-20240405085108-e2f706cb5c00/go.mod h1:FZsrMWGucMP3SZamzrd7m562geIs5zp1O/9MGoiAKH0=
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/go-randomx/v2 v2.1.0 h1:L1fV2XBYFmpFU+JKP/7fsgDm2Lfh9yFlS+800v+3OsM=
git.gammaspectra.live/P2Pool/go-randomx/v2 v2.1.0/go.mod h1:vNmHlEIRAcU/bA85mxbUKEiBYtrtS4MVwozf29KmoHM=
git.gammaspectra.live/P2Pool/monero-base58 v1.0.0 h1:s8LZxVNc93YEs2NCCNWZ7CKr8RbEb031y6Wkvhn+TS4=
git.gammaspectra.live/P2Pool/monero-base58 v1.0.0/go.mod h1:WWEJy/AdWKxKAruvlKI82brw+DtVlePy0ct3ZiBlc68=
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/randomx-go-bindings v1.0.0 h1:tajr4QFSPrb8NtHmU14JaXdhr+z+0RbOBLIgUDI5Tow=
git.gammaspectra.live/P2Pool/randomx-go-bindings v1.0.0/go.mod h1:S17NNidG5hxqaVLsSykKqDBg/hTPSzP0KcSwXfH8WIA=
git.gammaspectra.live/P2Pool/sha3 v0.17.0 h1:CZpB466LPbNVQrUNjQTtQScGDc30xSMkZ6Bmw0W9VuM=
git.gammaspectra.live/P2Pool/sha3 v0.17.0/go.mod h1:HmrrYa97BZTKklUk2n/wAY+wrY0gHhoSGRd2+lIqXq8=
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.2-0.20240312182618-f4b2babd2bc1 h1:F7u1ZVCidajlPuJJzdL5REPHfLO/O6xLQRUw/YMxrkM=
github.com/dolthub/swiss v0.2.2-0.20240312182618-f4b2babd2bc1/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/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
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.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -27,7 +27,7 @@ type FoundBlock struct {
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.MainBlock.Id, &b.MainBlock.Height, &b.MainBlock.Timestamp, &b.MainBlock.Reward, &b.MainBlock.CoinbaseId, &b.MainBlock.CoinbasePrivateKey, &b.MainBlock.Difficulty, &b.MainBlock.SideTemplateId, &b.MainBlock.RootHash,
&b.SideHeight, &b.Miner, &b.UncleOf, &b.EffectiveHeight, &b.WindowDepth, &b.WindowOutputs, &b.TransactionCount, &b.Difficulty, &b.CumulativeDifficulty, &b.Inclusion,
); err != nil {
return err

View file

@ -13,7 +13,7 @@ import (
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
"git.gammaspectra.live/P2Pool/consensus/v3/types"
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
"github.com/floatdrop/lru"
"github.com/hashicorp/golang-lru/v2"
"github.com/lib/pq"
"io"
"path"
@ -31,7 +31,7 @@ type Index struct {
getSeedByHeight block.GetSeedByHeightFunc
getByTemplateId sidechain.GetByTemplateIdFunc
derivationCache sidechain.DerivationCacheInterface
blockCache *lru.LRU[types.Hash, *sidechain.PoolBlock]
blockCache *lru.Cache[types.Hash, *sidechain.PoolBlock]
handle *sql.DB
statements struct {
@ -63,13 +63,17 @@ var dbSchema string
var dbFunctions embed.FS
func OpenIndex(connStr string, consensus *sidechain.Consensus, difficultyByHeight block.GetDifficultyByHeightFunc, getSeedByHeight block.GetSeedByHeightFunc, getByTemplateId sidechain.GetByTemplateIdFunc) (index *Index, err error) {
cache, err := lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4))
if err != nil {
return nil, err
}
index = &Index{
consensus: consensus,
getDifficultyByHeight: difficultyByHeight,
getSeedByHeight: getSeedByHeight,
getByTemplateId: getByTemplateId,
derivationCache: sidechain.NewDerivationLRUCache(),
blockCache: lru.New[types.Hash, *sidechain.PoolBlock](int(consensus.ChainWindowSize * 4)),
blockCache: cache,
views: make(map[string]string),
}
if index.handle, err = sql.Open("postgres", connStr); err != nil {
@ -208,7 +212,7 @@ func OpenIndex(connStr string, consensus *sidechain.Consensus, difficultyByHeigh
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 {
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, $22) ON CONFLICT (main_id) DO UPDATE SET uncle_of = $7, effective_height = $8, inclusion = $22;"); err != nil {
return nil, err
}
@ -257,11 +261,11 @@ func (i *Index) GetSeedByHeight(height uint64) types.Hash {
}
func (i *Index) GetByTemplateId(id types.Hash) *sidechain.PoolBlock {
if v := i.blockCache.Get(id); v != nil {
return *v
if v, ok := i.blockCache.Get(id); ok && v != nil {
return v
} else {
if b := i.getByTemplateId(id); b != nil {
i.blockCache.Set(id, b)
i.blockCache.Add(id, b)
return b
}
}
@ -270,7 +274,7 @@ func (i *Index) GetByTemplateId(id types.Hash) *sidechain.PoolBlock {
}
func (i *Index) CachePoolBlock(b *sidechain.PoolBlock) {
i.blockCache.Set(b.SideTemplateId(i.consensus), b)
i.blockCache.Add(b.SideTemplateId(i.consensus), b)
}
func (i *Index) Consensus() *sidechain.Consensus {
@ -714,7 +718,9 @@ func (i *Index) InsertOrUpdateSideBlock(b *SideBlock) error {
if oldBlock := i.GetTipSideBlockByHeight(b.SideHeight); oldBlock != nil {
if oldBlock.MainId != b.MainId {
//conflict resolution, change other block status
if oldBlock.TemplateId == oldBlock.TemplateId {
if oldBlock.TemplateId == b.TemplateId {
oldBlock.Inclusion = InclusionAlternateInVerifiedChain
} else if oldBlock.RootHash != types.ZeroHash && oldBlock.RootHash == b.RootHash {
oldBlock.Inclusion = InclusionAlternateInVerifiedChain
} else {
//mark as orphan if templates don't match. if uncle it will be adjusted later
@ -738,6 +744,7 @@ func (i *Index) InsertOrUpdateSideBlock(b *SideBlock) error {
&b.MainId,
b.MainHeight,
&b.TemplateId,
&b.RootHash,
b.SideHeight,
parentId,
b.Miner,
@ -789,7 +796,7 @@ func (i *Index) InsertOrUpdateMainBlock(b *MainBlock) error {
} 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;",
"INSERT INTO main_blocks (id, height, timestamp, reward, coinbase_id, difficulty, metadata, side_template_id, root_hash, coinbase_private_key) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8, $9, $10) ON CONFLICT (id) DO UPDATE SET metadata = $7, side_template_id = $8, root_hash = $9, coinbase_private_key = $10;",
b.Id[:],
b.Height,
b.Timestamp,
@ -798,6 +805,7 @@ func (i *Index) InsertOrUpdateMainBlock(b *MainBlock) error {
b.Difficulty,
metadataJson,
&b.SideTemplateId,
&b.RootHash,
&b.CoinbasePrivateKey,
); err != nil {
return err
@ -1366,7 +1374,7 @@ func (i *Index) preProcessPoolBlock(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 := i.GetByTemplateId(types.HashFromBytes(b.CoinbaseExtra(sidechain.SideTemplateId))); b2 != nil && len(b2.Main.Coinbase.Outputs) != 0 {
if b2 := i.GetByTemplateId(b.FastSideTemplateId(i.Consensus())); b2 != nil && len(b2.Main.Coinbase.Outputs) != 0 {
b.Main.Coinbase.Outputs = b2.Main.Coinbase.Outputs
} else {
preAllocatedShares = sidechain.PreAllocateShares(i.consensus.ChainWindowSize * 2)

View file

@ -7,7 +7,7 @@ import (
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
)
const MainBlockSelectFields = "id, height, timestamp, reward, coinbase_id, difficulty, metadata, side_template_id, coinbase_private_key"
const MainBlockSelectFields = "id, height, timestamp, reward, coinbase_id, difficulty, metadata, side_template_id, root_hash, coinbase_private_key"
type MainBlock struct {
Id types.Hash `json:"id"`
@ -23,6 +23,8 @@ type MainBlock struct {
// sidechain data for blocks we own
// SideTemplateId can be NULL
SideTemplateId types.Hash `json:"side_template_id,omitempty"`
// RootHash can be NULL
RootHash types.Hash `json:"root_hash,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"`
}
@ -38,7 +40,7 @@ func (b *MainBlock) SetMetadata(key string, v any) {
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 {
if err := row.Scan(&b.Id, &b.Height, &b.Timestamp, &b.Reward, &b.CoinbaseId, &b.Difficulty, &metadataBuf, &b.SideTemplateId, &b.RootHash, &b.CoinbasePrivateKey); err != nil {
return err
} else if err = utils.UnmarshalJSON(metadataBuf, &b.Metadata); err != nil {
return err

View file

@ -1,3 +1,11 @@
-- Upgrades
-- v0 to v1
ALTER TABLE IF EXISTS side_blocks ADD COLUMN IF NOT EXISTS root_hash bytea DEFAULT NULL;
ALTER TABLE IF EXISTS main_blocks ADD COLUMN IF NOT EXISTS root_hash bytea UNIQUE DEFAULT NULL;
-- Creation
CREATE TABLE IF NOT EXISTS miners (
id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
alias varchar UNIQUE DEFAULT NULL,
@ -22,6 +30,7 @@ CREATE TABLE IF NOT EXISTS side_blocks (
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
root_hash bytea DEFAULT NULL, -- root hash. Note multiple blocks can exist per root hash, see inclusion. Can be NULL
side_height bigint NOT NULL, -- sidechain height
parent_template_id bytea NOT NULL, -- previous sidechain template id
@ -60,6 +69,7 @@ CREATE TABLE IF NOT EXISTS side_blocks (
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_root_hash_idx ON side_blocks (root_hash);
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);
@ -83,6 +93,7 @@ CREATE TABLE IF NOT EXISTS main_blocks (
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,
root_hash 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
@ -141,7 +152,7 @@ CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Views
CREATE EXTENSION IF NOT EXISTS pg_ivm;
SELECT create_immv('found_main_blocks_v4', $$
SELECT create_immv('found_main_blocks_v5', $$
SELECT
m.id AS main_id,
m.height AS main_height,
@ -151,6 +162,7 @@ SELECT
m.coinbase_private_key AS coinbase_private_key,
m.difficulty AS main_difficulty,
m.side_template_id AS template_id,
m.root_hash AS root_hash,
s.side_height AS side_height,
s.miner AS miner,
s.uncle_of AS uncle_of,
@ -167,9 +179,9 @@ FROM
(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);
CREATE UNIQUE INDEX IF NOT EXISTS found_main_blocks_main_id_idx ON found_main_blocks_v5 (main_id);
CREATE INDEX IF NOT EXISTS found_main_blocks_miner_idx ON found_main_blocks_v5 (miner);
CREATE INDEX IF NOT EXISTS found_main_blocks_side_height_idx ON found_main_blocks_v5 (side_height);
SELECT create_immv('payouts_v4', $$
SELECT

View file

@ -1,7 +1,6 @@
package index
import (
"bytes"
"encoding/binary"
"errors"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/address"
@ -26,7 +25,7 @@ const (
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"
const SideBlockSelectFields = "main_id, main_height, template_id, root_hash, 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
@ -35,7 +34,10 @@ type SideBlock struct {
// 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"`
// RootHash -- root hash. Note multiple blocks can exist per root hash, see Inclusion
RootHash types.Hash `json:"root_hash,omitempty"`
SideHeight uint64 `json:"side_height"`
// ParentTemplateId previous sidechain template id
ParentTemplateId types.Hash `json:"parent_template_id"`
@ -96,11 +98,12 @@ type SideBlockUncleEntry struct {
func (b *SideBlock) FromPoolBlock(i *Index, block *sidechain.PoolBlock, getSeedByHeight mainblock.GetSeedByHeightFunc) error {
b.MainId = block.MainId()
b.TemplateId = block.SideTemplateId(i.consensus)
b.RootHash = block.MergeMiningTag().RootHash
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 {
if b.TemplateId == types.ZeroHash || b.TemplateId != block.FastSideTemplateId(i.Consensus()) {
return errors.New("invalid template id")
}
b.MainHeight = block.Main.Coinbase.GenHeight
@ -164,7 +167,7 @@ func (b *SideBlock) IsOrphan() bool {
}
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 {
if err := row.Scan(&b.MainId, &b.MainHeight, &b.TemplateId, &b.RootHash, &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

View file

@ -427,7 +427,7 @@ func CalculateOutputs(indexDb *Index, block *SideBlock, transactionOutputType ui
}
}()
utils.SplitWork(-2, n, func(workIndex uint64, workerIndex int) error {
err := utils.SplitWork(-2, n, func(workIndex uint64, workerIndex int) error {
output := transaction.Output{
Index: workIndex,
Type: txType,
@ -441,7 +441,11 @@ func CalculateOutputs(indexDb *Index, block *SideBlock, transactionOutputType ui
}, func(routines, routineIndex int) error {
hashers = append(hashers, crypto.GetKeccak256Hasher())
return nil
}, nil)
})
if err != nil {
return nil, 0
}
return outputs, bottomHeight
}

View file

@ -1,6 +1,7 @@
package utils
import (
"git.gammaspectra.live/P2Pool/consensus/v3/monero"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/address"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
@ -171,7 +172,7 @@ type PoolInfoResultMainChainConsensus struct {
// 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"`
HardForks []monero.HardFork `json:"hard_forks,omitempty"`
}
type PoolInfoResultMainChain struct {

View file

@ -1,33 +1,36 @@
package utils
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"git.gammaspectra.live/P2Pool/consensus/v3/merge_mining"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/block"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client/rpc/daemon"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/crypto"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/transaction"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
"git.gammaspectra.live/P2Pool/consensus/v3/types"
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
"git.gammaspectra.live/P2Pool/observer-cmd-utils/index"
"github.com/floatdrop/lru"
"github.com/hashicorp/golang-lru/v2"
"math"
)
// keep last few main blocks
var mainBlockCache = lru.New[types.Hash, *block.Block](100)
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 {
if bb, ok := mainBlockCache.Get(id); !ok {
blockData, err := client.GetBlock(id, context.Background())
if err != nil {
return nil, err
}
mainBlock := &block.Block{}
err = mainBlock.UnmarshalBinary(blockData.Blob)
err = mainBlock.UnmarshalBinary(blockData.Blob, false, nil)
if err != nil {
return nil, err
}
@ -35,10 +38,10 @@ func GetMainBlock(id types.Hash, client *client.Client) (*block.Block, error) {
//should never happen from monero itself
return nil, errors.New("outputs not filled")
}
mainBlockCache.Set(id, mainBlock)
mainBlockCache.Add(id, mainBlock)
return mainBlock, nil
} else {
return *bb, nil
return bb, nil
}
}
@ -65,7 +68,7 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
// 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 == h.PrevHash && b.Main.Coinbase.TotalReward == h.Reward && b.Main.Timestamp == uint64(h.Timestamp) && len(b.Main.Transactions) == int(h.NumTxes) {
if b.Main.PreviousId == h.PrevHash && b.Main.Coinbase.AuxiliaryData.TotalReward == h.Reward && b.Main.Timestamp == uint64(h.Timestamp) && len(b.Main.Transactions) == int(h.NumTxes) {
// candidate for checking
candidates = append(candidates, b)
}
@ -90,7 +93,9 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
}
return nil
}
var sideTemplateId types.Hash
var possibleId types.Hash
var extraNonce []byte
// try to fetch extra nonce if existing
@ -104,14 +109,48 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
// 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)
}
mergeMineTag := mainBlock.Coinbase.Extra.GetTag(transaction.TxExtraTagMergeMining)
if mergeMineTag != nil {
func() {
shareVersion := sidechain.P2PoolShareVersion(indexDb.Consensus(), mainBlock.Timestamp)
if shareVersion < sidechain.ShareVersion_V3 {
//TODO: this is to comply with non-standard p2pool serialization, see https://github.com/SChernykh/p2pool/issues/249
if mergeMineTag.VarInt != types.HashSize {
return
}
if len(mergeMineTag.Data) != types.HashSize {
return
}
// sidechain id
possibleId = types.HashFromBytes(mergeMineTag.Data)
} else {
// fallback for "old" non forked blocks
if len(mergeMineTag.Data) == types.HashSize && mergeMineTag.VarInt == types.HashSize {
// sidechain id
possibleId = types.HashFromBytes(mergeMineTag.Data)
return
}
//properly decode merge mining tag
mergeMineReader := bytes.NewReader(mergeMineTag.Data)
var mergeMiningTag merge_mining.Tag
if err := mergeMiningTag.FromReader(mergeMineReader); err != nil {
return
}
if mergeMineReader.Len() != 0 {
return
}
possibleId = mergeMiningTag.RootHash
}
}()
}
}
if sideTemplateId == types.ZeroHash || len(extraNonce) == 0 {
if possibleId == types.ZeroHash || len(extraNonce) == 0 {
utils.Logf("Monero", "checking block %s at height %d: not a p2pool block", h.Hash, h.Height)
if err := indexDb.InsertOrUpdateMainBlock(indexMain); err != nil {
return err
@ -119,7 +158,7 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
return nil
}
if t := getByTemplateId(sideTemplateId); t != nil {
if t := getByTemplateId(possibleId); t != nil {
//found template
if err := processBlock(t); err != nil {
return fmt.Errorf("could not process template: %w", err)
@ -175,12 +214,14 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
t.Depth.Store(math.MaxUint64)
storeFunc(t)
indexMain.SideTemplateId = sideTemplateId
indexMain.SideTemplateId = t.FastSideTemplateId(indexDb.Consensus())
indexMain.RootHash = t.MergeMiningTag().RootHash
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", h.Hash, h.Height, sideTemplateId)
utils.Logf("Monero", "INSERTED found block %s at height %d, template id %s", h.Hash, h.Height, indexMain.SideTemplateId)
if err := FindAndInsertMainHeaderOutputs(indexMain, indexDb, client, difficultyByHeight, getByTemplateId, getByMainId, getByMainHeight, processBlock); err != nil {
utils.Errorf("", "error inserting coinbase outputs: %s", err)
}
@ -206,19 +247,21 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
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)
return fmt.Errorf("could not process %s, at side height %d, template id %s: %w", t.FastSideTemplateId(indexDb.Consensus()), t.Side.Height, t.FastSideTemplateId(indexDb.Consensus()), 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.SideTemplateId = t.FastSideTemplateId(indexDb.Consensus())
indexMain.RootHash = t.MergeMiningTag().RootHash
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", h.Hash, h.Height, sideTemplateId)
utils.Logf("", "INSERTED ALTERNATE found block %s at height %d, template id %s", h.Hash, h.Height, indexMain.SideTemplateId)
if err := FindAndInsertMainHeaderOutputs(indexMain, indexDb, client, difficultyByHeight, getByTemplateId, getByMainId, getByMainHeight, processBlock); err != nil {
utils.Errorf("", "error inserting coinbase outputs: %s", err)
}
@ -227,13 +270,13 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
}
}
utils.Logf("", "checking block %s at height %d, template id %s: could not find matching template id", h.Hash, h.Height, sideTemplateId)
utils.Logf("", "checking block %s at height %d, possible id %s: could not find matching possible id", h.Hash, h.Height, possibleId)
// 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("merge_mining_tag", possibleId)
indexMain.SetMetadata("extra_nonce", binary.LittleEndian.Uint32(extraNonce))
if err := indexDb.InsertOrUpdateMainBlock(indexMain); err != nil {

View file

@ -5,7 +5,9 @@ import (
"context"
"encoding/binary"
"fmt"
"git.gammaspectra.live/P2Pool/consensus/v3/merge_mining"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/client"
"git.gammaspectra.live/P2Pool/consensus/v3/monero/transaction"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/sidechain"
"git.gammaspectra.live/P2Pool/consensus/v3/types"
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
@ -120,7 +122,6 @@ func init() {
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 {
@ -138,19 +139,55 @@ func ProcessFullBlock(b *index.MainBlock, indexDb *index.Index) error {
}
}
var possibleId types.Hash
// 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)
}
mergeMineTag := mainBlock.Coinbase.Extra.GetTag(transaction.TxExtraTagMergeMining)
if mergeMineTag != nil {
func() {
shareVersion := sidechain.P2PoolShareVersion(indexDb.Consensus(), mainBlock.Timestamp)
if shareVersion < sidechain.ShareVersion_V3 {
//TODO: this is to comply with non-standard p2pool serialization, see https://github.com/SChernykh/p2pool/issues/249
if mergeMineTag.VarInt != types.HashSize {
return
}
if len(mergeMineTag.Data) != types.HashSize {
return
}
// sidechain id
possibleId = types.HashFromBytes(mergeMineTag.Data)
} else {
// fallback for "old" non forked blocks
if len(mergeMineTag.Data) == types.HashSize && mergeMineTag.VarInt == types.HashSize {
// sidechain id
possibleId = types.HashFromBytes(mergeMineTag.Data)
return
}
//properly decode merge mining tag
mergeMineReader := bytes.NewReader(mergeMineTag.Data)
var mergeMiningTag merge_mining.Tag
if err := mergeMiningTag.FromReader(mergeMineReader); err != nil {
return
}
if mergeMineReader.Len() != 0 {
return
}
possibleId = mergeMiningTag.RootHash
}
}()
}
}
if sideTemplateId != types.ZeroHash && len(extraNonce) != 0 {
b.SetMetadata("merge_mining_tag", sideTemplateId)
if possibleId != types.ZeroHash && len(extraNonce) != 0 {
b.SetMetadata("merge_mining_tag", possibleId)
b.SetMetadata("extra_nonce", binary.LittleEndian.Uint32(extraNonce))
utils.Logf("", "p2pool tags found template id %s, extra nonce %d", sideTemplateId, binary.LittleEndian.Uint32(extraNonce))
utils.Logf("", "p2pool tags found possible id %s, extra nonce %d", possibleId, binary.LittleEndian.Uint32(extraNonce))
}
const txInputThreshold = 4

View file

@ -7,6 +7,7 @@ import (
"git.gammaspectra.live/P2Pool/consensus/v3/monero/address"
"git.gammaspectra.live/P2Pool/consensus/v3/p2pool/types"
"git.gammaspectra.live/P2Pool/consensus/v3/utils"
"git.gammaspectra.live/P2Pool/observer-cmd-utils/api"
"git.gammaspectra.live/P2Pool/observer-cmd-utils/index"
"io"
"net/http"
@ -228,8 +229,8 @@ func SendSideBlock(w *index.MinerWebHook, ts int64, source *address.Address, blo
},
})
case index.WebHookCustom:
return SendJsonPost(w, ts, source, &JSONEvent{
Type: JSONEventSideBlock,
return SendJsonPost(w, ts, source, &api.JSONEvent{
Type: api.JSONEventSideBlock,
SideBlock: block,
})
default:
@ -244,8 +245,8 @@ func SendFoundBlock(w *index.MinerWebHook, ts int64, source *address.Address, bl
switch w.Type {
case index.WebHookCustom:
return SendJsonPost(w, ts, source, &JSONEvent{
Type: JSONEventFoundBlock,
return SendJsonPost(w, ts, source, &api.JSONEvent{
Type: api.JSONEventFoundBlock,
FoundBlock: block,
MainCoinbaseOutputs: outputs,
})
@ -331,8 +332,8 @@ func SendPayout(w *index.MinerWebHook, ts int64, source *address.Address, payout
},
})
case index.WebHookCustom:
return SendJsonPost(w, ts, source, &JSONEvent{
Type: JSONEventPayout,
return SendJsonPost(w, ts, source, &api.JSONEvent{
Type: api.JSONEventPayout,
Payout: payout,
})
default:
@ -347,8 +348,8 @@ func SendOrphanedBlock(w *index.MinerWebHook, ts int64, source *address.Address,
switch w.Type {
case index.WebHookCustom:
return SendJsonPost(w, ts, source, &JSONEvent{
Type: JSONEventOrphanedBlock,
return SendJsonPost(w, ts, source, &api.JSONEvent{
Type: api.JSONEventOrphanedBlock,
SideBlock: block,
})
default: