git.gammaspectra.live/P2Pool/consensus/v3@v3.8.0/p2pool/sidechain/poolblock.go (about)

     1  package sidechain
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"git.gammaspectra.live/P2Pool/consensus/v3/merge_mining"
     9  	"git.gammaspectra.live/P2Pool/consensus/v3/monero"
    10  	"git.gammaspectra.live/P2Pool/consensus/v3/monero/address"
    11  	mainblock "git.gammaspectra.live/P2Pool/consensus/v3/monero/block"
    12  	"git.gammaspectra.live/P2Pool/consensus/v3/monero/crypto"
    13  	"git.gammaspectra.live/P2Pool/consensus/v3/monero/randomx"
    14  	"git.gammaspectra.live/P2Pool/consensus/v3/monero/transaction"
    15  	p2poolcrypto "git.gammaspectra.live/P2Pool/consensus/v3/p2pool/crypto"
    16  	"git.gammaspectra.live/P2Pool/consensus/v3/types"
    17  	"git.gammaspectra.live/P2Pool/consensus/v3/utils"
    18  	fasthex "github.com/tmthrgd/go-hex"
    19  	"io"
    20  	"net/netip"
    21  	"slices"
    22  	"sync/atomic"
    23  	"time"
    24  	"unsafe"
    25  )
    26  
    27  type CoinbaseExtraTag int
    28  
    29  const SideExtraNonceSize = 4
    30  const SideExtraNonceMaxSize = SideExtraNonceSize + 10
    31  
    32  const (
    33  	SideCoinbasePublicKey = transaction.TxExtraTagPubKey
    34  	SideExtraNonce        = transaction.TxExtraTagNonce
    35  
    36  	// SideIdentifierHash Depending on version, this can be a PoolBlock TemplateId or Merkle Root Hash
    37  	SideIdentifierHash = transaction.TxExtraTagMergeMining
    38  )
    39  
    40  // PoolBlockMaxTemplateSize Max P2P message size (128 KB) minus BLOCK_RESPONSE header (5 bytes)
    41  const PoolBlockMaxTemplateSize = 128*1024 - (1 + 4)
    42  
    43  type UniquePoolBlockSlice []*PoolBlock
    44  
    45  func (s UniquePoolBlockSlice) Get(consensus *Consensus, id types.Hash) *PoolBlock {
    46  	if i := slices.IndexFunc(s, func(p *PoolBlock) bool {
    47  		return p.FastSideTemplateId(consensus) == id
    48  	}); i != -1 {
    49  		return s[i]
    50  	}
    51  	return nil
    52  }
    53  
    54  func (s UniquePoolBlockSlice) GetHeight(height uint64) (result UniquePoolBlockSlice) {
    55  	for _, b := range s {
    56  		if b.Side.Height == height {
    57  			result = append(result, b)
    58  		}
    59  	}
    60  	return result
    61  }
    62  
    63  // IterationCache Used for fast scan backwards, and of uncles
    64  // Only maybe available in verified blocks
    65  type IterationCache struct {
    66  	Parent *PoolBlock
    67  	Uncles []*PoolBlock
    68  }
    69  
    70  type PoolBlock struct {
    71  	Main mainblock.Block `json:"main"`
    72  
    73  	Side SideData `json:"side"`
    74  
    75  	//Temporary data structures
    76  	mergeMiningTag merge_mining.Tag
    77  
    78  	cache    poolBlockCache
    79  	Depth    atomic.Uint64 `json:"-"`
    80  	Verified atomic.Bool   `json:"-"`
    81  	Invalid  atomic.Bool   `json:"-"`
    82  
    83  	WantBroadcast atomic.Bool `json:"-"`
    84  	Broadcasted   atomic.Bool `json:"-"`
    85  
    86  	Metadata PoolBlockReceptionMetadata `json:"metadata"`
    87  
    88  	CachedShareVersion ShareVersion `json:"share_version"`
    89  
    90  	iterationCache *IterationCache
    91  }
    92  
    93  type PoolBlockReceptionMetadata struct {
    94  	// LocalTime Moment the block was received from a source
    95  	LocalTime time.Time `json:"local_time,omitempty"`
    96  	// AddressPort The address and port of the peer who broadcasted or sent us this block
    97  	// If the peer specified a listen port, the port will be that instead of current connection one
    98  	AddressPort netip.AddrPort `json:"address_port,omitempty"`
    99  	// PeerId The peer id of the peer who broadcasted or sent us this block
   100  	PeerId uint64 `json:"peer_id,omitempty"`
   101  
   102  	SoftwareId      uint32 `json:"software_id"`
   103  	SoftwareVersion uint32 `json:"software_version"`
   104  }
   105  
   106  func (m *PoolBlockReceptionMetadata) UnmarshalBinary(buf []byte) error {
   107  	r := bytes.NewReader(buf)
   108  	s, err := binary.ReadUvarint(r)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	ns, err := binary.ReadUvarint(r)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	m.LocalTime = time.Unix(int64(s), int64(ns)).UTC()
   118  
   119  	l, err := binary.ReadUvarint(r)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	ip := make([]byte, l)
   124  	_, err = io.ReadFull(r, ip)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	err = m.AddressPort.UnmarshalBinary(ip)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	m.PeerId, err = binary.ReadUvarint(r)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	sId, err := binary.ReadUvarint(r)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	sVer, err := binary.ReadUvarint(r)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	m.SoftwareId = uint32(sId)
   150  	m.SoftwareVersion = uint32(sVer)
   151  
   152  	return nil
   153  }
   154  
   155  func (m *PoolBlockReceptionMetadata) MarshalBinary() ([]byte, error) {
   156  	s := uint64(m.LocalTime.Unix())
   157  	ns := uint64(m.LocalTime.Nanosecond())
   158  
   159  	ip, _ := m.AddressPort.MarshalBinary()
   160  	out := make([]byte, 0,
   161  		utils.UVarInt64Size(s)+utils.UVarInt64Size(ns)+
   162  			utils.UVarInt64Size(len(ip))+len(ip)+
   163  			utils.UVarInt64Size(m.PeerId)+
   164  			utils.UVarInt64Size(uint64(m.SoftwareId))+
   165  			utils.UVarInt64Size(uint64(m.SoftwareVersion)))
   166  
   167  	out = binary.AppendUvarint(out, s)
   168  	out = binary.AppendUvarint(out, ns)
   169  	out = binary.AppendUvarint(out, uint64(len(ip)))
   170  	out = append(out, ip...)
   171  	out = binary.AppendUvarint(out, m.PeerId)
   172  	out = binary.AppendUvarint(out, uint64(m.SoftwareId))
   173  	out = binary.AppendUvarint(out, uint64(m.SoftwareVersion))
   174  	return out, nil
   175  }
   176  
   177  func (b *PoolBlock) iteratorGetParent(getByTemplateId GetByTemplateIdFunc) *PoolBlock {
   178  	if b.iterationCache == nil {
   179  		return getByTemplateId(b.Side.Parent)
   180  	}
   181  	return b.iterationCache.Parent
   182  }
   183  
   184  func (b *PoolBlock) iteratorUncles(getByTemplateId GetByTemplateIdFunc, uncleFunc func(uncle *PoolBlock)) error {
   185  	if len(b.Side.Uncles) == 0 {
   186  		return nil
   187  	}
   188  	if b.iterationCache == nil {
   189  		for _, uncleId := range b.Side.Uncles {
   190  			uncle := getByTemplateId(uncleId)
   191  			if uncle == nil {
   192  				return fmt.Errorf("could not find uncle %s", uncleId)
   193  			}
   194  			uncleFunc(uncle)
   195  		}
   196  	} else {
   197  		for _, uncle := range b.iterationCache.Uncles {
   198  			uncleFunc(uncle)
   199  		}
   200  	}
   201  
   202  	return nil
   203  }
   204  
   205  func (b *PoolBlock) NeedsCompactTransactionFilling() bool {
   206  	return len(b.Main.TransactionParentIndices) > 0 && len(b.Main.TransactionParentIndices) == len(b.Main.Transactions) && slices.Index(b.Main.Transactions, types.ZeroHash) != -1
   207  }
   208  
   209  func (b *PoolBlock) FillTransactionsFromTransactionParentIndices(consensus *Consensus, parent *PoolBlock) error {
   210  	if b.NeedsCompactTransactionFilling() {
   211  		if parent != nil && parent.FastSideTemplateId(consensus) == b.Side.Parent {
   212  			for i, parentIndex := range b.Main.TransactionParentIndices {
   213  				if parentIndex != 0 {
   214  					// p2pool stores coinbase transaction hash as well, decrease
   215  					actualIndex := parentIndex - 1
   216  					if actualIndex > uint64(len(parent.Main.Transactions)) {
   217  						return errors.New("index of parent transaction out of bounds")
   218  					}
   219  					b.Main.Transactions[i] = parent.Main.Transactions[actualIndex]
   220  				}
   221  			}
   222  		} else if parent == nil {
   223  			return errors.New("parent is nil")
   224  		}
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  func (b *PoolBlock) FillTransactionParentIndices(consensus *Consensus, parent *PoolBlock) bool {
   231  	if len(b.Main.Transactions) != len(b.Main.TransactionParentIndices) {
   232  		if parent != nil && parent.FastSideTemplateId(consensus) == b.Side.Parent {
   233  			b.Main.TransactionParentIndices = make([]uint64, len(b.Main.Transactions))
   234  			//do not fail if not found
   235  			for i, txHash := range b.Main.Transactions {
   236  				if parentIndex := slices.Index(parent.Main.Transactions, txHash); parentIndex != -1 {
   237  					//increase as p2pool stores tx hash as well
   238  					b.Main.TransactionParentIndices[i] = uint64(parentIndex + 1)
   239  				}
   240  			}
   241  			return true
   242  		}
   243  
   244  		return false
   245  	}
   246  
   247  	return true
   248  }
   249  
   250  func (b *PoolBlock) CalculateShareVersion(consensus *Consensus) ShareVersion {
   251  	return P2PoolShareVersion(consensus, b.Main.Timestamp)
   252  }
   253  
   254  func (b *PoolBlock) ShareVersion() ShareVersion {
   255  	return b.CachedShareVersion
   256  }
   257  
   258  func (b *PoolBlock) ShareVersionSignaling() ShareVersion {
   259  	if b.ShareVersion() == ShareVersion_V1 && (b.ExtraNonce()&0xFF000000 == 0xFF000000) {
   260  		return ShareVersion_V2
   261  	}
   262  	return ShareVersion_None
   263  }
   264  
   265  func (b *PoolBlock) ExtraNonce() uint32 {
   266  	extraNonce := b.CoinbaseExtra(SideExtraNonce)
   267  	if len(extraNonce) < SideExtraNonceSize {
   268  		return 0
   269  	}
   270  	return binary.LittleEndian.Uint32(extraNonce)
   271  }
   272  
   273  // FastSideTemplateId Returns SideTemplateId from either coinbase extra tags or pruned data, or main block if not pruned
   274  func (b *PoolBlock) FastSideTemplateId(consensus *Consensus) types.Hash {
   275  	if b.ShareVersion() > ShareVersion_V2 {
   276  		if b.Main.Coinbase.AuxiliaryData.WasPruned {
   277  			return b.Main.Coinbase.AuxiliaryData.TemplateId
   278  		} else {
   279  			//fallback to full calculation
   280  			return b.SideTemplateId(consensus)
   281  		}
   282  	} else {
   283  		return types.HashFromBytes(b.CoinbaseExtra(SideIdentifierHash))
   284  	}
   285  }
   286  
   287  func (b *PoolBlock) CoinbaseExtra(tag CoinbaseExtraTag) []byte {
   288  	switch tag {
   289  	case SideExtraNonce:
   290  		if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
   291  			if len(t.Data) < SideExtraNonceSize || len(t.Data) > SideExtraNonceMaxSize {
   292  				return nil
   293  			}
   294  			return t.Data
   295  		}
   296  	case SideIdentifierHash:
   297  		if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
   298  			if b.ShareVersion() > ShareVersion_V2 {
   299  				mergeMineReader := bytes.NewReader(t.Data)
   300  				var mergeMiningTag merge_mining.Tag
   301  				if err := mergeMiningTag.FromReader(mergeMineReader); err != nil || mergeMineReader.Len() != 0 {
   302  					return nil
   303  				}
   304  
   305  				return mergeMiningTag.RootHash[:]
   306  			} else {
   307  				if t.VarInt != types.HashSize || len(t.Data) != types.HashSize {
   308  					return nil
   309  				}
   310  				return t.Data
   311  			}
   312  		}
   313  	case SideCoinbasePublicKey:
   314  		if t := b.Main.Coinbase.Extra.GetTag(uint8(tag)); t != nil {
   315  			if len(t.Data) != crypto.PublicKeySize {
   316  				return nil
   317  			}
   318  			return t.Data
   319  		}
   320  	}
   321  
   322  	return nil
   323  }
   324  
   325  func (b *PoolBlock) MainId() types.Hash {
   326  	return b.Main.Id()
   327  }
   328  
   329  func (b *PoolBlock) FullId(consensus *Consensus) FullId {
   330  	var buf FullId
   331  
   332  	sidechainId := b.FastSideTemplateId(consensus)
   333  	copy(buf[:], sidechainId[:])
   334  	binary.LittleEndian.PutUint32(buf[types.HashSize:], b.Main.Nonce)
   335  	copy(buf[types.HashSize+unsafe.Sizeof(b.Main.Nonce):], b.CoinbaseExtra(SideExtraNonce)[:SideExtraNonceSize])
   336  	return buf
   337  }
   338  
   339  const FullIdSize = int(types.HashSize + unsafe.Sizeof(uint32(0)) + SideExtraNonceSize)
   340  
   341  var zeroFullId FullId
   342  
   343  type FullId [FullIdSize]byte
   344  
   345  func FullIdFromString(s string) (FullId, error) {
   346  	var h FullId
   347  	if buf, err := fasthex.DecodeString(s); err != nil {
   348  		return h, err
   349  	} else {
   350  		if len(buf) != FullIdSize {
   351  			return h, errors.New("wrong hash size")
   352  		}
   353  		copy(h[:], buf)
   354  		return h, nil
   355  	}
   356  }
   357  
   358  func (id FullId) TemplateId() (h types.Hash) {
   359  	return types.Hash(id[:types.HashSize])
   360  }
   361  
   362  func (id FullId) Nonce() uint32 {
   363  	return binary.LittleEndian.Uint32(id[types.HashSize:])
   364  }
   365  
   366  func (id FullId) ExtraNonce() uint32 {
   367  	return binary.LittleEndian.Uint32(id[types.HashSize+unsafe.Sizeof(uint32(0)):])
   368  }
   369  
   370  func (id FullId) String() string {
   371  	return fasthex.EncodeToString(id[:])
   372  }
   373  
   374  func (b *PoolBlock) CalculateFullId(consensus *Consensus) FullId {
   375  	var buf FullId
   376  	sidechainId := b.SideTemplateId(consensus)
   377  	copy(buf[:], sidechainId[:])
   378  	binary.LittleEndian.PutUint32(buf[types.HashSize:], b.Main.Nonce)
   379  	copy(buf[types.HashSize+unsafe.Sizeof(b.Main.Nonce):], b.CoinbaseExtra(SideExtraNonce)[:SideExtraNonceSize])
   380  	return buf
   381  }
   382  
   383  func (b *PoolBlock) MainDifficulty(f mainblock.GetDifficultyByHeightFunc) types.Difficulty {
   384  	return b.Main.Difficulty(f)
   385  }
   386  
   387  func (b *PoolBlock) SideTemplateId(consensus *Consensus) types.Hash {
   388  	if h := b.cache.templateId.Load(); h != nil {
   389  		return *h
   390  	} else {
   391  		hash := consensus.CalculateSideTemplateId(b)
   392  		if hash == types.ZeroHash {
   393  			return types.ZeroHash
   394  		}
   395  		b.cache.templateId.Store(&hash)
   396  		return hash
   397  	}
   398  }
   399  
   400  func (b *PoolBlock) CoinbaseId() types.Hash {
   401  	if h := b.cache.coinbaseId.Load(); h != nil {
   402  		return *h
   403  	} else {
   404  		hash := b.Main.Coinbase.CalculateId()
   405  		if hash == types.ZeroHash {
   406  			return types.ZeroHash
   407  		}
   408  		b.cache.coinbaseId.Store(&hash)
   409  		return hash
   410  	}
   411  }
   412  
   413  func (b *PoolBlock) MergeMiningTag() merge_mining.Tag {
   414  	return b.mergeMiningTag
   415  }
   416  
   417  func (b *PoolBlock) PowHash(hasher randomx.Hasher, f mainblock.GetSeedByHeightFunc) types.Hash {
   418  	h, _ := b.PowHashWithError(hasher, f)
   419  	return h
   420  }
   421  
   422  func (b *PoolBlock) PowHashWithError(hasher randomx.Hasher, f mainblock.GetSeedByHeightFunc) (powHash types.Hash, err error) {
   423  	if h := b.cache.powHash.Load(); h != nil {
   424  		powHash = *h
   425  	} else {
   426  		powHash, err = b.Main.PowHashWithError(hasher, f)
   427  		if powHash == types.ZeroHash {
   428  			return types.ZeroHash, err
   429  		}
   430  		b.cache.powHash.Store(&powHash)
   431  	}
   432  
   433  	return powHash, nil
   434  }
   435  
   436  func (b *PoolBlock) UnmarshalBinary(consensus *Consensus, derivationCache DerivationCacheInterface, data []byte) error {
   437  	if len(data) > PoolBlockMaxTemplateSize {
   438  		return errors.New("buffer too large")
   439  	}
   440  	reader := bytes.NewReader(data)
   441  	return b.FromReader(consensus, derivationCache, reader)
   442  }
   443  
   444  func (b *PoolBlock) BufferLength() int {
   445  	return b.Main.BufferLength() + b.Side.BufferLength(b.ShareVersion())
   446  }
   447  
   448  func (b *PoolBlock) MarshalBinary() ([]byte, error) {
   449  	return b.MarshalBinaryFlags(false, false)
   450  }
   451  
   452  func (b *PoolBlock) MarshalBinaryFlags(pruned, compact bool) ([]byte, error) {
   453  	return b.AppendBinaryFlags(make([]byte, 0, b.BufferLength()), pruned, compact)
   454  }
   455  
   456  func (b *PoolBlock) AppendBinaryFlags(preAllocatedBuf []byte, pruned, compact bool) (buf []byte, err error) {
   457  	buf = preAllocatedBuf
   458  
   459  	if buf, err = b.Main.AppendBinaryFlags(buf, compact, pruned, b.ShareVersion() > ShareVersion_V2); err != nil {
   460  		return nil, err
   461  	} else if buf, err = b.Side.AppendBinary(buf, b.ShareVersion()); err != nil {
   462  		return nil, err
   463  	} else {
   464  		if len(buf) > PoolBlockMaxTemplateSize {
   465  			return nil, errors.New("buffer too large")
   466  		}
   467  		return buf, nil
   468  	}
   469  }
   470  
   471  func (b *PoolBlock) FromReader(consensus *Consensus, derivationCache DerivationCacheInterface, reader utils.ReaderAndByteReader) (err error) {
   472  	if err = b.Main.FromReader(reader, true, func() (containsAuxiliaryTemplateId bool) {
   473  		return b.CalculateShareVersion(consensus) > ShareVersion_V2
   474  	}); err != nil {
   475  		return err
   476  	}
   477  
   478  	return b.consensusDecode(consensus, derivationCache, reader)
   479  }
   480  
   481  // FromCompactReader used in Protocol 1.1 and above
   482  func (b *PoolBlock) FromCompactReader(consensus *Consensus, derivationCache DerivationCacheInterface, reader utils.ReaderAndByteReader) (err error) {
   483  	if err = b.Main.FromCompactReader(reader, true, func() (containsAuxiliaryTemplateId bool) {
   484  		return b.CalculateShareVersion(consensus) > ShareVersion_V2
   485  	}); err != nil {
   486  		return err
   487  	}
   488  
   489  	return b.consensusDecode(consensus, derivationCache, reader)
   490  }
   491  
   492  func (b *PoolBlock) consensusDecode(consensus *Consensus, derivationCache DerivationCacheInterface, reader utils.ReaderAndByteReader) (err error) {
   493  	if expectedMajorVersion := monero.NetworkMajorVersion(consensus.NetworkType.MustAddressNetwork(), b.Main.Coinbase.GenHeight); expectedMajorVersion != b.Main.MajorVersion {
   494  		return fmt.Errorf("expected major version %d at height %d, got %d", expectedMajorVersion, b.Main.Coinbase.GenHeight, b.Main.MajorVersion)
   495  	}
   496  
   497  	if b.CachedShareVersion == ShareVersion_None {
   498  		b.CachedShareVersion = b.CalculateShareVersion(consensus)
   499  	}
   500  
   501  	mergeMineTag := b.Main.Coinbase.Extra.GetTag(transaction.TxExtraTagMergeMining)
   502  
   503  	if mergeMineTag == nil {
   504  		return errors.New("missing merge mining tag")
   505  	}
   506  
   507  	if b.ShareVersion() < ShareVersion_V3 {
   508  		//TODO: this is to comply with non-standard p2pool serialization, see https://github.com/SChernykh/p2pool/issues/249
   509  		if mergeMineTag.VarInt != types.HashSize {
   510  			return errors.New("wrong merge mining tag depth")
   511  		}
   512  	} else {
   513  		//properly decode merge mining tag
   514  		mergeMineReader := bytes.NewReader(mergeMineTag.Data)
   515  		if err = b.mergeMiningTag.FromReader(mergeMineReader); err != nil {
   516  			return err
   517  		}
   518  		if mergeMineReader.Len() != 0 {
   519  			return errors.New("wrong merge mining tag len")
   520  		}
   521  	}
   522  
   523  	if err = b.Side.FromReader(reader, b.ShareVersion()); err != nil {
   524  		return err
   525  	}
   526  
   527  	b.FillPrivateKeys(derivationCache)
   528  
   529  	return nil
   530  }
   531  
   532  // PreProcessBlock processes and fills the block data from either pruned or compact modes
   533  func (b *PoolBlock) PreProcessBlock(consensus *Consensus, derivationCache DerivationCacheInterface, preAllocatedShares Shares, difficultyByHeight mainblock.GetDifficultyByHeightFunc, getTemplateById GetByTemplateIdFunc) (missingBlocks []types.Hash, err error) {
   534  	return b.PreProcessBlockWithOutputs(consensus, getTemplateById, func() (outputs transaction.Outputs, bottomHeight uint64) {
   535  		return CalculateOutputs(b, consensus, difficultyByHeight, getTemplateById, derivationCache, preAllocatedShares, nil)
   536  	})
   537  }
   538  
   539  // PreProcessBlockWithOutputs processes and fills the block data from either pruned or compact modes
   540  func (b *PoolBlock) PreProcessBlockWithOutputs(consensus *Consensus, getTemplateById GetByTemplateIdFunc, calculateOutputs func() (outputs transaction.Outputs, bottomHeight uint64)) (missingBlocks []types.Hash, err error) {
   541  
   542  	getTemplateByIdFillingTx := func(h types.Hash) *PoolBlock {
   543  		chain := make(UniquePoolBlockSlice, 0, 1)
   544  
   545  		cur := getTemplateById(h)
   546  		for ; cur != nil; cur = getTemplateById(cur.Side.Parent) {
   547  			chain = append(chain, cur)
   548  			if !cur.NeedsCompactTransactionFilling() {
   549  				break
   550  			}
   551  			if len(chain) > 1 {
   552  				if chain[len(chain)-2].FillTransactionsFromTransactionParentIndices(consensus, chain[len(chain)-1]) == nil {
   553  					if !chain[len(chain)-2].NeedsCompactTransactionFilling() {
   554  						//early abort if it can all be filled
   555  						chain = chain[:len(chain)-1]
   556  						break
   557  					}
   558  				}
   559  			}
   560  		}
   561  		if len(chain) == 0 {
   562  			return nil
   563  		}
   564  		//skips last entry
   565  		for i := len(chain) - 2; i >= 0; i-- {
   566  			if err := chain[i].FillTransactionsFromTransactionParentIndices(consensus, chain[i+1]); err != nil {
   567  				return nil
   568  			}
   569  		}
   570  		return chain[0]
   571  	}
   572  
   573  	var parent *PoolBlock
   574  	if b.NeedsCompactTransactionFilling() {
   575  		parent = getTemplateByIdFillingTx(b.Side.Parent)
   576  		if parent == nil {
   577  			missingBlocks = append(missingBlocks, b.Side.Parent)
   578  			return missingBlocks, errors.New("parent does not exist in compact block")
   579  		}
   580  		if err := b.FillTransactionsFromTransactionParentIndices(consensus, parent); err != nil {
   581  			return nil, fmt.Errorf("error filling transactions for block: %w", err)
   582  		}
   583  	}
   584  
   585  	if len(b.Main.Transactions) != len(b.Main.TransactionParentIndices) {
   586  		if parent == nil {
   587  			parent = getTemplateByIdFillingTx(b.Side.Parent)
   588  		}
   589  		b.FillTransactionParentIndices(consensus, parent)
   590  	}
   591  
   592  	if len(b.Main.Coinbase.Outputs) == 0 {
   593  		if outputs, _ := calculateOutputs(); outputs == nil {
   594  			return nil, errors.New("error filling outputs for block: nil outputs")
   595  		} else {
   596  			b.Main.Coinbase.Outputs = outputs
   597  		}
   598  
   599  		if outputBlob, err := b.Main.Coinbase.Outputs.AppendBinary(make([]byte, 0, b.Main.Coinbase.Outputs.BufferLength())); err != nil {
   600  			return nil, fmt.Errorf("error filling outputs for block: %s", err)
   601  		} else if uint64(len(outputBlob)) != b.Main.Coinbase.AuxiliaryData.OutputsBlobSize {
   602  			return nil, fmt.Errorf("error filling outputs for block: invalid output blob size, got %d, expected %d", b.Main.Coinbase.AuxiliaryData.OutputsBlobSize, len(outputBlob))
   603  		}
   604  	}
   605  
   606  	return nil, nil
   607  }
   608  
   609  func (b *PoolBlock) NeedsPreProcess() bool {
   610  	return b.NeedsCompactTransactionFilling() || len(b.Main.Transactions) != len(b.Main.TransactionParentIndices) || len(b.Main.Coinbase.Outputs) == 0
   611  }
   612  
   613  func (b *PoolBlock) FillPrivateKeys(derivationCache DerivationCacheInterface) {
   614  	if b.ShareVersion() > ShareVersion_V1 {
   615  		if b.Side.CoinbasePrivateKey == crypto.ZeroPrivateKeyBytes {
   616  			//Fill Private Key
   617  			kP := derivationCache.GetDeterministicTransactionKey(b.GetPrivateKeySeed(), b.Main.PreviousId)
   618  			b.Side.CoinbasePrivateKey = kP.PrivateKey.AsBytes()
   619  		}
   620  	} else {
   621  		b.Side.CoinbasePrivateKeySeed = b.GetPrivateKeySeed()
   622  	}
   623  }
   624  
   625  func (b *PoolBlock) IsProofHigherThanMainDifficulty(hasher randomx.Hasher, difficultyFunc mainblock.GetDifficultyByHeightFunc, seedFunc mainblock.GetSeedByHeightFunc) bool {
   626  	r, _ := b.IsProofHigherThanMainDifficultyWithError(hasher, difficultyFunc, seedFunc)
   627  	return r
   628  }
   629  
   630  func (b *PoolBlock) IsProofHigherThanMainDifficultyWithError(hasher randomx.Hasher, difficultyFunc mainblock.GetDifficultyByHeightFunc, seedFunc mainblock.GetSeedByHeightFunc) (bool, error) {
   631  	if mainDifficulty := b.MainDifficulty(difficultyFunc); mainDifficulty == types.ZeroDifficulty {
   632  		return false, errors.New("could not get main difficulty")
   633  	} else if powHash, err := b.PowHashWithError(hasher, seedFunc); err != nil {
   634  		return false, err
   635  	} else {
   636  		return mainDifficulty.CheckPoW(powHash), nil
   637  	}
   638  }
   639  
   640  func (b *PoolBlock) IsProofHigherThanDifficulty(hasher randomx.Hasher, f mainblock.GetSeedByHeightFunc) bool {
   641  	r, _ := b.IsProofHigherThanDifficultyWithError(hasher, f)
   642  	return r
   643  }
   644  
   645  func (b *PoolBlock) IsProofHigherThanDifficultyWithError(hasher randomx.Hasher, f mainblock.GetSeedByHeightFunc) (bool, error) {
   646  	if powHash, err := b.PowHashWithError(hasher, f); err != nil {
   647  		return false, err
   648  	} else {
   649  		return b.Side.Difficulty.CheckPoW(powHash), nil
   650  	}
   651  }
   652  
   653  func (b *PoolBlock) GetPrivateKeySeed() types.Hash {
   654  	if b.ShareVersion() > ShareVersion_V1 {
   655  		return b.Side.CoinbasePrivateKeySeed
   656  	}
   657  
   658  	oldSeed := types.Hash(b.Side.PublicKey[address.PackedAddressSpend])
   659  	if b.Main.MajorVersion < monero.HardForkViewTagsVersion && p2poolcrypto.GetDeterministicTransactionPrivateKey(oldSeed, b.Main.PreviousId).AsBytes() != b.Side.CoinbasePrivateKey {
   660  		return types.ZeroHash
   661  	}
   662  
   663  	return oldSeed
   664  }
   665  
   666  func (b *PoolBlock) CalculateTransactionPrivateKeySeed() types.Hash {
   667  	if b.ShareVersion() > ShareVersion_V1 {
   668  		preAllocatedMainData := make([]byte, 0, b.Main.BufferLength())
   669  		preAllocatedSideData := make([]byte, 0, b.Side.BufferLength(b.ShareVersion()))
   670  		mainData, _ := b.Main.SideChainHashingBlob(preAllocatedMainData, false)
   671  		sideData, _ := b.Side.AppendBinary(preAllocatedSideData, b.ShareVersion())
   672  		return p2poolcrypto.CalculateTransactionPrivateKeySeed(
   673  			mainData,
   674  			sideData,
   675  		)
   676  	}
   677  
   678  	return types.Hash(b.Side.PublicKey[address.PackedAddressSpend])
   679  }
   680  
   681  func (b *PoolBlock) GetAddress() address.PackedAddress {
   682  	return b.Side.PublicKey
   683  }
   684  
   685  func (b *PoolBlock) GetTransactionOutputType() uint8 {
   686  	// Both tx types are allowed by Monero consensus during v15 because it needs to process pre-fork mempool transactions,
   687  	// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15
   688  	expectedTxType := uint8(transaction.TxOutToKey)
   689  	if b.Main.MajorVersion >= monero.HardForkViewTagsVersion {
   690  		expectedTxType = transaction.TxOutToTaggedKey
   691  	}
   692  
   693  	return expectedTxType
   694  }
   695  
   696  type poolBlockCache struct {
   697  	templateId atomic.Pointer[types.Hash]
   698  	coinbaseId atomic.Pointer[types.Hash]
   699  	powHash    atomic.Pointer[types.Hash]
   700  }