git.gammaspectra.live/P2Pool/consensus@v0.0.0-20240403173234-a039820b20c9/p2pool/sidechain/poolblock.go (about)

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