683 lines
19 KiB
Go
683 lines
19 KiB
Go
package sidechain
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
|
mainblock "git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/transaction"
|
|
p2poolcrypto "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/crypto"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
|
"golang.org/x/exp/slices"
|
|
"io"
|
|
"log"
|
|
"sync"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
)
|
|
|
|
type CoinbaseExtraTag int
|
|
|
|
const SideExtraNonceSize = 4
|
|
const SideExtraNonceMaxSize = SideExtraNonceSize + 10
|
|
|
|
const (
|
|
SideCoinbasePublicKey = transaction.TxExtraTagPubKey
|
|
SideExtraNonce = transaction.TxExtraTagNonce
|
|
SideTemplateId = transaction.TxExtraTagMergeMining
|
|
)
|
|
|
|
type ShareVersion int
|
|
|
|
func (v ShareVersion) String() string {
|
|
switch v {
|
|
case ShareVersion_None:
|
|
return "none"
|
|
default:
|
|
return fmt.Sprintf("v%d", v)
|
|
}
|
|
}
|
|
|
|
const (
|
|
ShareVersion_None ShareVersion = 0
|
|
ShareVersion_V1 ShareVersion = 1
|
|
ShareVersion_V2 ShareVersion = 2
|
|
)
|
|
|
|
const ShareVersion_V2MainNetTimestamp uint64 = 1679173200 // 2023-03-18 21:00 UTC
|
|
const ShareVersion_V2TestNetTimestamp uint64 = 1674507600 // 2023-01-23 21:00 UTC
|
|
|
|
type UniquePoolBlockSlice []*PoolBlock
|
|
|
|
func (s UniquePoolBlockSlice) Get(id types.Hash) *PoolBlock {
|
|
if i := slices.IndexFunc(s, func(p *PoolBlock) bool {
|
|
return bytes.Compare(p.CoinbaseExtra(SideTemplateId), id[:]) == 0
|
|
}); i != -1 {
|
|
return s[i]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s UniquePoolBlockSlice) GetHeight(height uint64) (result UniquePoolBlockSlice) {
|
|
for _, b := range s {
|
|
if b.Side.Height == height {
|
|
result = append(result, b)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
type PoolBlock struct {
|
|
Main mainblock.Block `json:"main"`
|
|
|
|
Side SideData `json:"side"`
|
|
|
|
//Temporary data structures
|
|
cache poolBlockCache
|
|
Depth atomic.Uint64 `json:"-"`
|
|
Verified atomic.Bool `json:"-"`
|
|
Invalid atomic.Bool `json:"-"`
|
|
|
|
WantBroadcast atomic.Bool `json:"-"`
|
|
Broadcasted atomic.Bool `json:"-"`
|
|
|
|
NetworkType NetworkType `json:"-"`
|
|
LocalTimestamp uint64 `json:"-"`
|
|
}
|
|
|
|
// NewShareFromExportedBytes TODO deprecate this in favor of standard serialized shares
|
|
// Deprecated
|
|
func NewShareFromExportedBytes(buf []byte, networkType NetworkType, cacheInterface DerivationCacheInterface) (*PoolBlock, error) {
|
|
b := &PoolBlock{
|
|
NetworkType: networkType,
|
|
}
|
|
|
|
if len(buf) < 32 {
|
|
return nil, errors.New("invalid block data")
|
|
}
|
|
|
|
reader := bytes.NewReader(buf)
|
|
|
|
var (
|
|
err error
|
|
version uint64
|
|
|
|
mainDataSize uint64
|
|
mainData []byte
|
|
|
|
sideDataSize uint64
|
|
sideData []byte
|
|
)
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &version); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch version {
|
|
case 1:
|
|
|
|
if _, err = io.ReadFull(reader, b.cache.mainId[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err = io.ReadFull(reader, b.cache.powHash[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &b.cache.mainDifficulty.Hi); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = binary.Read(reader, binary.BigEndian, &b.cache.mainDifficulty.Lo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b.cache.mainDifficulty.ReverseBytes()
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
|
|
return nil, err
|
|
}
|
|
mainData = make([]byte, mainDataSize)
|
|
if _, err = io.ReadFull(reader, mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &sideDataSize); err != nil {
|
|
return nil, err
|
|
}
|
|
sideData = make([]byte, sideDataSize)
|
|
if _, err = io.ReadFull(reader, sideData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
/*
|
|
//Ignore error when unable to read peer
|
|
_ = func() error {
|
|
var peerSize uint64
|
|
|
|
if err = binary.Read(reader, binary.BigEndian, &peerSize); err != nil {
|
|
return err
|
|
}
|
|
b.Extra.Peer = make([]byte, peerSize)
|
|
if _, err = io.ReadFull(reader, b.Extra.Peer); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}()
|
|
*/
|
|
|
|
case 0:
|
|
if err = binary.Read(reader, binary.BigEndian, &mainDataSize); err != nil {
|
|
return nil, err
|
|
}
|
|
mainData = make([]byte, mainDataSize)
|
|
if _, err = io.ReadFull(reader, mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
if sideData, err = io.ReadAll(reader); err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unknown block version %d", version)
|
|
}
|
|
|
|
if err = b.Main.UnmarshalBinary(mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = b.Side.UnmarshalBinary(sideData, b.ShareVersion()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b.FillPrivateKeys(cacheInterface)
|
|
|
|
b.cache.templateId = types.HashFromBytes(b.CoinbaseExtra(SideTemplateId))
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (b *PoolBlock) NeedsCompactTransactionFilling() bool {
|
|
return len(b.Main.TransactionParentIndices) > 0 && len(b.Main.TransactionParentIndices) == len(b.Main.Transactions) && slices.Index(b.Main.Transactions, types.ZeroHash) != -1
|
|
}
|
|
|
|
func (b *PoolBlock) FillTransactionsFromTransactionParentIndices(parent *PoolBlock) error {
|
|
if b.NeedsCompactTransactionFilling() {
|
|
if parent != nil && types.HashFromBytes(parent.CoinbaseExtra(SideTemplateId)) == b.Side.Parent {
|
|
for i, parentIndex := range b.Main.TransactionParentIndices {
|
|
if parentIndex != 0 {
|
|
// p2pool stores coinbase transaction hash as well, decrease
|
|
actualIndex := parentIndex - 1
|
|
if actualIndex > uint64(len(parent.Main.Transactions)) {
|
|
return errors.New("index of parent transaction out of bounds")
|
|
}
|
|
b.Main.Transactions[i] = parent.Main.Transactions[actualIndex]
|
|
}
|
|
}
|
|
} else if parent == nil {
|
|
return errors.New("parent is nil")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *PoolBlock) FillTransactionParentIndices(parent *PoolBlock) bool {
|
|
if len(b.Main.Transactions) != len(b.Main.TransactionParentIndices) {
|
|
if parent != nil && types.HashFromBytes(parent.CoinbaseExtra(SideTemplateId)) == b.Side.Parent {
|
|
b.Main.TransactionParentIndices = make([]uint64, len(b.Main.Transactions))
|
|
//do not fail if not found
|
|
for i, txHash := range b.Main.Transactions {
|
|
if parentIndex := slices.Index(parent.Main.Transactions, txHash); parentIndex != -1 {
|
|
//increase as p2pool stores tx hash as well
|
|
b.Main.TransactionParentIndices[i] = uint64(parentIndex + 1)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (b *PoolBlock) ShareVersion() ShareVersion {
|
|
// P2Pool forks to v2 at 2023-03-18 21:00 UTC
|
|
// Different miners can have different timestamps,
|
|
// so a temporary mix of v1 and v2 blocks is allowed
|
|
switch b.NetworkType {
|
|
case NetworkInvalid:
|
|
log.Panicf("invalid network type for determining share version")
|
|
case NetworkMainnet:
|
|
if b.Main.Timestamp >= ShareVersion_V2MainNetTimestamp {
|
|
return ShareVersion_V2
|
|
}
|
|
case NetworkTestnet:
|
|
if b.Main.Timestamp >= ShareVersion_V2TestNetTimestamp {
|
|
return ShareVersion_V2
|
|
}
|
|
case NetworkStagenet:
|
|
return ShareVersion_V2
|
|
}
|
|
if b.Main.Timestamp >= ShareVersion_V2MainNetTimestamp {
|
|
return ShareVersion_V2
|
|
}
|
|
return ShareVersion_V1
|
|
}
|
|
|
|
func (b *PoolBlock) ShareVersionSignaling() ShareVersion {
|
|
if b.ShareVersion() == ShareVersion_V1 && ((binary.LittleEndian.Uint32(b.CoinbaseExtra(SideExtraNonce)))&0xFF000000 == 0xFF000000) {
|
|
return ShareVersion_V2
|
|
}
|
|
return ShareVersion_None
|
|
}
|
|
|
|
func (b *PoolBlock) CoinbaseExtra(tag CoinbaseExtraTag) []byte {
|
|
switch tag {
|
|
case SideExtraNonce:
|
|
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
|
if len(t.Data) < SideExtraNonceSize || len(t.Data) > SideExtraNonceMaxSize {
|
|
return nil
|
|
}
|
|
return t.Data
|
|
}
|
|
case SideTemplateId:
|
|
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
|
if len(t.Data) != types.HashSize {
|
|
return nil
|
|
}
|
|
return t.Data
|
|
}
|
|
case SideCoinbasePublicKey:
|
|
if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
|
|
if len(t.Data) != crypto.PublicKeySize {
|
|
return nil
|
|
}
|
|
return t.Data
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *PoolBlock) MainId() types.Hash {
|
|
if hash, ok := func() (types.Hash, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.mainId != types.ZeroHash {
|
|
return b.cache.mainId, true
|
|
}
|
|
return types.ZeroHash, false
|
|
}(); ok {
|
|
return hash
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.mainId == types.ZeroHash { //check again for race
|
|
b.cache.mainId = b.Main.Id()
|
|
}
|
|
return b.cache.mainId
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) FullId(consensus *Consensus) FullId {
|
|
if fullId, ok := func() (FullId, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.fullId != zeroFullId {
|
|
return b.cache.fullId, true
|
|
}
|
|
return zeroFullId, false
|
|
}(); ok {
|
|
return fullId
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.fullId == zeroFullId { //check again for race
|
|
b.cache.fullId = b.CalculateFullId(consensus)
|
|
}
|
|
return b.cache.fullId
|
|
}
|
|
}
|
|
|
|
const FullIdSize = int(types.HashSize + unsafe.Sizeof(uint32(0)) + SideExtraNonceSize)
|
|
|
|
var zeroFullId FullId
|
|
|
|
type FullId [FullIdSize]byte
|
|
|
|
func (b *PoolBlock) CalculateFullId(consensus *Consensus) FullId {
|
|
var buf FullId
|
|
sidechainId := b.SideTemplateId(consensus)
|
|
copy(buf[:], sidechainId[:])
|
|
binary.LittleEndian.PutUint32(buf[types.HashSize:], b.Main.Nonce)
|
|
copy(buf[types.HashSize+unsafe.Sizeof(b.Main.Nonce):], b.CoinbaseExtra(SideExtraNonce)[:SideExtraNonceSize])
|
|
return buf
|
|
}
|
|
|
|
func (b *PoolBlock) MainDifficulty(f mainblock.GetDifficultyByHeightFunc) types.Difficulty {
|
|
if difficulty, ok := func() (types.Difficulty, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.mainDifficulty != types.ZeroDifficulty {
|
|
return b.cache.mainDifficulty, true
|
|
}
|
|
return types.ZeroDifficulty, false
|
|
}(); ok {
|
|
return difficulty
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.mainDifficulty == types.ZeroDifficulty { //check again for race
|
|
b.cache.mainDifficulty = b.Main.Difficulty(f)
|
|
}
|
|
return b.cache.mainDifficulty
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) SideTemplateId(consensus *Consensus) types.Hash {
|
|
if hash, ok := func() (types.Hash, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.templateId != types.ZeroHash {
|
|
return b.cache.templateId, true
|
|
}
|
|
return types.ZeroHash, false
|
|
}(); ok {
|
|
return hash
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.templateId == types.ZeroHash { //check again for race
|
|
b.cache.templateId = consensus.CalculateSideTemplateId(b)
|
|
}
|
|
return b.cache.templateId
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) PowHash(f mainblock.GetSeedByHeightFunc) types.Hash {
|
|
h, _ := b.PowHashWithError(f)
|
|
return h
|
|
}
|
|
|
|
func (b *PoolBlock) PowHashWithError(f mainblock.GetSeedByHeightFunc) (powHash types.Hash, err error) {
|
|
if hash, ok := func() (types.Hash, bool) {
|
|
b.cache.lock.RLock()
|
|
defer b.cache.lock.RUnlock()
|
|
|
|
if b.cache.powHash != types.ZeroHash {
|
|
return b.cache.powHash, true
|
|
}
|
|
return types.ZeroHash, false
|
|
}(); ok {
|
|
return hash, nil
|
|
} else {
|
|
b.cache.lock.Lock()
|
|
defer b.cache.lock.Unlock()
|
|
if b.cache.powHash == types.ZeroHash { //check again for race
|
|
b.cache.powHash, err = b.Main.PowHashWithError(f)
|
|
}
|
|
return b.cache.powHash, err
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) UnmarshalBinary(derivationCache DerivationCacheInterface, data []byte) error {
|
|
reader := bytes.NewReader(data)
|
|
return b.FromReader(derivationCache, reader)
|
|
}
|
|
|
|
func (b *PoolBlock) MarshalBinary() ([]byte, error) {
|
|
if mainData, err := b.Main.MarshalBinary(); err != nil {
|
|
return nil, err
|
|
} else if sideData, err := b.Side.MarshalBinary(b.ShareVersion()); err != nil {
|
|
return nil, err
|
|
} else {
|
|
data := make([]byte, 0, len(mainData)+len(sideData))
|
|
data = append(data, mainData...)
|
|
data = append(data, sideData...)
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) MarshalBinaryFlags(pruned, compact bool) ([]byte, error) {
|
|
if mainData, err := b.Main.MarshalBinaryFlags(pruned, compact); err != nil {
|
|
return nil, err
|
|
} else if sideData, err := b.Side.MarshalBinary(b.ShareVersion()); err != nil {
|
|
return nil, err
|
|
} else {
|
|
data := make([]byte, 0, len(mainData)+len(sideData))
|
|
data = append(data, mainData...)
|
|
data = append(data, sideData...)
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) FromReader(derivationCache DerivationCacheInterface, reader readerAndByteReader) (err error) {
|
|
if err = b.Main.FromReader(reader); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = b.Side.FromReader(reader, b.ShareVersion()); err != nil {
|
|
return err
|
|
}
|
|
|
|
b.FillPrivateKeys(derivationCache)
|
|
|
|
return nil
|
|
}
|
|
|
|
// FromCompactReader used in Protocol 1.1 and above
|
|
func (b *PoolBlock) FromCompactReader(derivationCache DerivationCacheInterface, reader readerAndByteReader) (err error) {
|
|
if err = b.Main.FromCompactReader(reader); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = b.Side.FromReader(reader, b.ShareVersion()); err != nil {
|
|
return err
|
|
}
|
|
|
|
b.FillPrivateKeys(derivationCache)
|
|
|
|
return nil
|
|
}
|
|
|
|
// PreProcessBlock processes and fills the block data from either pruned or compact modes
|
|
func (b *PoolBlock) PreProcessBlock(consensus *Consensus, derivationCache DerivationCacheInterface, preAllocatedShares Shares, difficultyByHeight mainblock.GetDifficultyByHeightFunc, getTemplateById GetByTemplateIdFunc) (missingBlocks []types.Hash, err error) {
|
|
|
|
getTemplateByIdFillingTx := func(h types.Hash) *PoolBlock {
|
|
chain := make(UniquePoolBlockSlice, 0, 1)
|
|
|
|
cur := getTemplateById(h)
|
|
for ; cur != nil; cur = getTemplateById(cur.Side.Parent) {
|
|
chain = append(chain, cur)
|
|
if !cur.NeedsCompactTransactionFilling() {
|
|
break
|
|
}
|
|
if len(chain) > 1 {
|
|
if chain[len(chain)-2].FillTransactionsFromTransactionParentIndices(chain[len(chain)-1]) == nil {
|
|
if !chain[len(chain)-2].NeedsCompactTransactionFilling() {
|
|
//early abort if it can all be filled
|
|
chain = chain[:len(chain)-1]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(chain) == 0 {
|
|
return nil
|
|
}
|
|
//skips last entry
|
|
for i := len(chain) - 2; i >= 0; i-- {
|
|
if err := chain[i].FillTransactionsFromTransactionParentIndices(chain[i+1]); err != nil {
|
|
return nil
|
|
}
|
|
}
|
|
return chain[0]
|
|
}
|
|
|
|
var parent *PoolBlock
|
|
if b.NeedsCompactTransactionFilling() {
|
|
parent = getTemplateByIdFillingTx(b.Side.Parent)
|
|
if parent == nil {
|
|
missingBlocks = append(missingBlocks, b.Side.Parent)
|
|
return missingBlocks, errors.New("parent does not exist in compact block")
|
|
}
|
|
if err := b.FillTransactionsFromTransactionParentIndices(parent); err != nil {
|
|
return nil, fmt.Errorf("error filling transactions for block: %w", err)
|
|
}
|
|
}
|
|
|
|
if len(b.Main.Transactions) != len(b.Main.TransactionParentIndices) {
|
|
if parent == nil {
|
|
parent = getTemplateByIdFillingTx(b.Side.Parent)
|
|
}
|
|
b.FillTransactionParentIndices(parent)
|
|
}
|
|
|
|
if len(b.Main.Coinbase.Outputs) == 0 {
|
|
if outputs, _ := CalculateOutputs(b, consensus, difficultyByHeight, getTemplateById, derivationCache, preAllocatedShares); outputs == nil {
|
|
return nil, errors.New("error filling outputs for block: nil outputs")
|
|
} else {
|
|
b.Main.Coinbase.Outputs = outputs
|
|
}
|
|
|
|
if outputBlob, err := b.Main.Coinbase.OutputsBlob(); err != nil {
|
|
return nil, fmt.Errorf("error filling outputs for block: %s", err)
|
|
} else if uint64(len(outputBlob)) != b.Main.Coinbase.OutputsBlobSize {
|
|
return nil, fmt.Errorf("error filling outputs for block: invalid output blob size, got %d, expected %d", b.Main.Coinbase.OutputsBlobSize, len(outputBlob))
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (b *PoolBlock) FillPrivateKeys(derivationCache DerivationCacheInterface) {
|
|
if b.ShareVersion() > ShareVersion_V1 {
|
|
if bytes.Compare(b.Side.CoinbasePrivateKey.AsSlice(), types.ZeroHash[:]) == 0 {
|
|
//Fill Private Key
|
|
kP := derivationCache.GetDeterministicTransactionKey(b.GetPrivateKeySeed(), b.Main.PreviousId)
|
|
b.Side.CoinbasePrivateKey = kP.PrivateKey.AsBytes()
|
|
}
|
|
} else {
|
|
b.Side.CoinbasePrivateKeySeed = b.GetPrivateKeySeed()
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) IsProofHigherThanMainDifficulty(difficultyFunc mainblock.GetDifficultyByHeightFunc, seedFunc mainblock.GetSeedByHeightFunc) bool {
|
|
r, _ := b.IsProofHigherThanMainDifficultyWithError(difficultyFunc, seedFunc)
|
|
return r
|
|
}
|
|
|
|
func (b *PoolBlock) IsProofHigherThanMainDifficultyWithError(difficultyFunc mainblock.GetDifficultyByHeightFunc, seedFunc mainblock.GetSeedByHeightFunc) (bool, error) {
|
|
if mainDifficulty := b.MainDifficulty(difficultyFunc); mainDifficulty == types.ZeroDifficulty {
|
|
return false, errors.New("could not get main difficulty")
|
|
} else if powHash, err := b.PowHashWithError(seedFunc); err != nil {
|
|
return false, err
|
|
} else {
|
|
return mainDifficulty.CheckPoW(powHash), nil
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) IsProofHigherThanDifficulty(f mainblock.GetSeedByHeightFunc) bool {
|
|
r, _ := b.IsProofHigherThanDifficultyWithError(f)
|
|
return r
|
|
}
|
|
|
|
func (b *PoolBlock) IsProofHigherThanDifficultyWithError(f mainblock.GetSeedByHeightFunc) (bool, error) {
|
|
if powHash, err := b.PowHashWithError(f); err != nil {
|
|
return false, err
|
|
} else {
|
|
return b.Side.Difficulty.CheckPoW(powHash), nil
|
|
}
|
|
}
|
|
|
|
func (b *PoolBlock) GetPrivateKeySeed() types.Hash {
|
|
if b.ShareVersion() > ShareVersion_V1 {
|
|
return b.Side.CoinbasePrivateKeySeed
|
|
}
|
|
|
|
oldSeed := types.Hash(b.Side.PublicSpendKey.AsBytes())
|
|
if b.Main.MajorVersion < monero.HardForkViewTagsVersion && p2poolcrypto.GetDeterministicTransactionPrivateKey(oldSeed, b.Main.PreviousId).AsBytes() != b.Side.CoinbasePrivateKey {
|
|
return types.ZeroHash
|
|
}
|
|
|
|
return oldSeed
|
|
}
|
|
|
|
func (b *PoolBlock) CalculateTransactionPrivateKeySeed() types.Hash {
|
|
if b.ShareVersion() > ShareVersion_V1 {
|
|
mainData, _ := b.Main.SideChainHashingBlob(false)
|
|
sideData, _ := b.Side.MarshalBinary(b.ShareVersion())
|
|
return p2poolcrypto.CalculateTransactionPrivateKeySeed(
|
|
mainData,
|
|
sideData,
|
|
)
|
|
}
|
|
|
|
return types.Hash(b.Side.PublicSpendKey.AsBytes())
|
|
}
|
|
|
|
func (b *PoolBlock) GetAddress() *address.PackedAddress {
|
|
a := address.NewPackedAddressFromBytes(b.Side.PublicSpendKey, b.Side.PublicViewKey)
|
|
return &a
|
|
}
|
|
|
|
func (b *PoolBlock) GetTransactionOutputType() uint8 {
|
|
// Both tx types are allowed by Monero consensus during v15 because it needs to process pre-fork mempool transactions,
|
|
// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15
|
|
expectedTxType := uint8(transaction.TxOutToKey)
|
|
if b.Main.MajorVersion >= monero.HardForkViewTagsVersion {
|
|
expectedTxType = transaction.TxOutToTaggedKey
|
|
}
|
|
|
|
return expectedTxType
|
|
}
|
|
|
|
type poolBlockCache struct {
|
|
lock sync.RWMutex
|
|
mainId types.Hash
|
|
mainDifficulty types.Difficulty
|
|
templateId types.Hash
|
|
powHash types.Hash
|
|
fullId FullId
|
|
}
|
|
|
|
func (c *poolBlockCache) FromReader(reader readerAndByteReader) (err error) {
|
|
buf := make([]byte, types.HashSize*3+types.DifficultySize+FullIdSize)
|
|
if _, err = reader.Read(buf); err != nil {
|
|
return err
|
|
}
|
|
return c.UnmarshalBinary(buf)
|
|
}
|
|
|
|
func (c *poolBlockCache) UnmarshalBinary(buf []byte) error {
|
|
if len(buf) < types.HashSize*3+types.DifficultySize+FullIdSize {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
copy(c.mainId[:], buf)
|
|
c.mainDifficulty = types.DifficultyFromBytes(buf[types.HashSize:])
|
|
copy(c.templateId[:], buf[types.HashSize+types.DifficultySize:])
|
|
copy(c.powHash[:], buf[types.HashSize+types.DifficultySize+types.HashSize:])
|
|
copy(c.fullId[:], buf[types.HashSize+types.DifficultySize+types.HashSize+types.HashSize:])
|
|
return nil
|
|
}
|
|
|
|
func (c *poolBlockCache) MarshalBinary() ([]byte, error) {
|
|
buf := make([]byte, 0, types.HashSize*3+types.DifficultySize+FullIdSize)
|
|
buf = append(buf, c.mainId[:]...)
|
|
buf = append(buf, c.mainDifficulty.Bytes()...)
|
|
buf = append(buf, c.templateId[:]...)
|
|
buf = append(buf, c.powHash[:]...)
|
|
buf = append(buf, c.fullId[:]...)
|
|
|
|
return buf, nil
|
|
}
|