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

     1  package sidechain
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"git.gammaspectra.live/P2Pool/consensus/monero"
     9  	mainblock "git.gammaspectra.live/P2Pool/consensus/monero/block"
    10  	"git.gammaspectra.live/P2Pool/consensus/monero/client"
    11  	"git.gammaspectra.live/P2Pool/consensus/monero/crypto"
    12  	"git.gammaspectra.live/P2Pool/consensus/monero/randomx"
    13  	"git.gammaspectra.live/P2Pool/consensus/monero/transaction"
    14  	p2pooltypes "git.gammaspectra.live/P2Pool/consensus/p2pool/types"
    15  	"git.gammaspectra.live/P2Pool/consensus/types"
    16  	"git.gammaspectra.live/P2Pool/consensus/utils"
    17  	"git.gammaspectra.live/P2Pool/sha3"
    18  	"github.com/dolthub/swiss"
    19  	"slices"
    20  	"sync"
    21  	"sync/atomic"
    22  	"time"
    23  )
    24  
    25  type Cache interface {
    26  	GetBlob(key []byte) (blob []byte, err error)
    27  	SetBlob(key, blob []byte) (err error)
    28  	RemoveBlob(key []byte) (err error)
    29  }
    30  
    31  type P2PoolInterface interface {
    32  	ConsensusProvider
    33  	Cache
    34  	Context() context.Context
    35  	UpdateTip(tip *PoolBlock)
    36  	Broadcast(block *PoolBlock)
    37  	ClientRPC() *client.Client
    38  	GetChainMainByHeight(height uint64) *ChainMain
    39  	GetChainMainByHash(hash types.Hash) *ChainMain
    40  	GetMinimalBlockHeaderByHeight(height uint64) *mainblock.Header
    41  	GetMinimalBlockHeaderByHash(hash types.Hash) *mainblock.Header
    42  	GetDifficultyByHeight(height uint64) types.Difficulty
    43  	UpdateBlockFound(data *ChainMain, block *PoolBlock)
    44  	SubmitBlock(block *mainblock.Block)
    45  	GetChainMainTip() *ChainMain
    46  	GetMinerDataTip() *p2pooltypes.MinerData
    47  	Store(block *PoolBlock)
    48  	ClearCachedBlocks()
    49  }
    50  
    51  type ChainMain struct {
    52  	Difficulty types.Difficulty
    53  	Height     uint64
    54  	Timestamp  uint64
    55  	Reward     uint64
    56  	Id         types.Hash
    57  }
    58  
    59  type SideChain struct {
    60  	derivationCache *DerivationCache
    61  	server          P2PoolInterface
    62  
    63  	seenBlocksLock sync.Mutex
    64  	seenBlocks     *swiss.Map[FullId, struct{}]
    65  
    66  	sidechainLock sync.RWMutex
    67  
    68  	watchBlock            *ChainMain
    69  	watchBlockSidechainId types.Hash
    70  
    71  	blocksByTemplateId       *swiss.Map[types.Hash, *PoolBlock]
    72  	blocksByHeight           *swiss.Map[uint64, []*PoolBlock]
    73  	blocksByHeightKeysSorted bool
    74  	blocksByHeightKeys       []uint64
    75  
    76  	preAllocatedBuffer []byte
    77  
    78  	syncTip           atomic.Pointer[PoolBlock]
    79  	chainTip          atomic.Pointer[PoolBlock]
    80  	currentDifficulty atomic.Pointer[types.Difficulty]
    81  
    82  	precalcFinished atomic.Bool
    83  
    84  	preAllocatedShares         Shares
    85  	preAllocatedRewards        []uint64
    86  	preAllocatedSharesPool     *PreAllocatedSharesPool
    87  	preAllocatedDifficultyData []DifficultyData
    88  	preAllocatedTimestampData  []uint64
    89  	preAllocatedMinedBlocks    []types.Hash
    90  }
    91  
    92  func NewSideChain(server P2PoolInterface) *SideChain {
    93  	s := &SideChain{
    94  		derivationCache:            NewDerivationMapCache(),
    95  		server:                     server,
    96  		blocksByTemplateId:         swiss.NewMap[types.Hash, *PoolBlock](uint32(server.Consensus().ChainWindowSize*2 + 300)),
    97  		blocksByHeight:             swiss.NewMap[uint64, []*PoolBlock](uint32(server.Consensus().ChainWindowSize*2 + 300)),
    98  		preAllocatedShares:         PreAllocateShares(server.Consensus().ChainWindowSize * 2),
    99  		preAllocatedRewards:        make([]uint64, 0, server.Consensus().ChainWindowSize*2),
   100  		preAllocatedDifficultyData: make([]DifficultyData, 0, server.Consensus().ChainWindowSize*2),
   101  		preAllocatedTimestampData:  make([]uint64, 0, server.Consensus().ChainWindowSize*2),
   102  		preAllocatedSharesPool:     NewPreAllocatedSharesPool(server.Consensus().ChainWindowSize * 2),
   103  		preAllocatedBuffer:         make([]byte, 0, PoolBlockMaxTemplateSize),
   104  		preAllocatedMinedBlocks:    make([]types.Hash, 0, 6*UncleBlockDepth*2+1),
   105  		seenBlocks:                 swiss.NewMap[FullId, struct{}](uint32(server.Consensus().ChainWindowSize*2 + 300)),
   106  	}
   107  	minDiff := types.DifficultyFrom64(server.Consensus().MinimumDifficulty)
   108  	s.currentDifficulty.Store(&minDiff)
   109  	return s
   110  }
   111  
   112  func (c *SideChain) Consensus() *Consensus {
   113  	return c.server.Consensus()
   114  }
   115  
   116  func (c *SideChain) DerivationCache() *DerivationCache {
   117  	return c.derivationCache
   118  }
   119  
   120  func (c *SideChain) Difficulty() types.Difficulty {
   121  	return *c.currentDifficulty.Load()
   122  }
   123  
   124  func (c *SideChain) PreCalcFinished() bool {
   125  	return c.precalcFinished.Load()
   126  }
   127  
   128  func (c *SideChain) PreprocessBlock(block *PoolBlock) (missingBlocks []types.Hash, err error) {
   129  	var preAllocatedShares Shares
   130  	if len(block.Main.Coinbase.Outputs) == 0 {
   131  		//cannot use SideTemplateId() as it might not be proper to calculate yet. fetch from coinbase only here
   132  		if b := c.GetPoolBlockByTemplateId(types.HashFromBytes(block.CoinbaseExtra(SideTemplateId))); b != nil {
   133  			block.Main.Coinbase.Outputs = b.Main.Coinbase.Outputs
   134  		} else {
   135  			preAllocatedShares = c.preAllocatedSharesPool.Get()
   136  			defer c.preAllocatedSharesPool.Put(preAllocatedShares)
   137  		}
   138  	}
   139  
   140  	return block.PreProcessBlock(c.Consensus(), c.derivationCache, preAllocatedShares, c.server.GetDifficultyByHeight, c.GetPoolBlockByTemplateId)
   141  }
   142  
   143  func (c *SideChain) fillPoolBlockTransactionParentIndices(block *PoolBlock) {
   144  	block.FillTransactionParentIndices(c.getParent(block))
   145  }
   146  
   147  func (c *SideChain) isPoolBlockTransactionKeyIsDeterministic(block *PoolBlock) bool {
   148  	kP := c.derivationCache.GetDeterministicTransactionKey(block.GetPrivateKeySeed(), block.Main.PreviousId)
   149  	return bytes.Compare(block.CoinbaseExtra(SideCoinbasePublicKey), kP.PublicKey.AsSlice()) == 0 && block.Side.CoinbasePrivateKey == kP.PrivateKey.AsBytes()
   150  }
   151  
   152  func (c *SideChain) getSeedByHeightFunc() mainblock.GetSeedByHeightFunc {
   153  	//TODO: do not make this return a function
   154  	return func(height uint64) (hash types.Hash) {
   155  		seedHeight := randomx.SeedHeight(height)
   156  		if h := c.server.GetMinimalBlockHeaderByHeight(seedHeight); h != nil {
   157  			return h.Id
   158  		} else {
   159  			return types.ZeroHash
   160  		}
   161  	}
   162  }
   163  
   164  func (c *SideChain) GetPossibleUncles(tip *PoolBlock, forHeight uint64) (uncles []types.Hash) {
   165  	minedBlocks := make([]types.Hash, 0, UncleBlockDepth*2+1)
   166  	tmp := tip
   167  	c.sidechainLock.RLock()
   168  	defer c.sidechainLock.RUnlock()
   169  	for i, n := uint64(0), min(UncleBlockDepth, tip.Side.Height+1); tmp != nil && (i < n); i++ {
   170  		minedBlocks = append(minedBlocks, tmp.SideTemplateId(c.Consensus()))
   171  		for _, uncleId := range tmp.Side.Uncles {
   172  			minedBlocks = append(minedBlocks, uncleId)
   173  		}
   174  		tmp = c.getParent(tmp)
   175  	}
   176  
   177  	for i, n := uint64(0), min(UncleBlockDepth, tip.Side.Height+1); i < n; i++ {
   178  		for _, uncle := range c.getPoolBlocksByHeight(tip.Side.Height - i) {
   179  			// Only add verified and valid blocks
   180  			if !uncle.Verified.Load() || uncle.Invalid.Load() {
   181  				continue
   182  			}
   183  
   184  			// Only add it if it hasn't been mined already
   185  			if slices.Contains(minedBlocks, uncle.SideTemplateId(c.Consensus())) {
   186  				continue
   187  			}
   188  
   189  			if sameChain := func() bool {
   190  				tmp = tip
   191  				for tmp != nil && tmp.Side.Height > uncle.Side.Height {
   192  					tmp = c.getParent(tmp)
   193  				}
   194  				if tmp == nil || tmp.Side.Height < uncle.Side.Height {
   195  					return false
   196  				}
   197  				tmp2 := uncle
   198  				for j := 0; j < UncleBlockDepth && tmp != nil && tmp2 != nil && (tmp.Side.Height+UncleBlockDepth >= forHeight); j++ {
   199  					if tmp.Side.Parent == tmp2.Side.Parent {
   200  						return true
   201  					}
   202  					tmp = c.getParent(tmp)
   203  					tmp2 = c.getParent(tmp2)
   204  				}
   205  				return false
   206  			}(); sameChain {
   207  				uncles = append(uncles, uncle.SideTemplateId(c.Consensus()))
   208  			}
   209  		}
   210  	}
   211  
   212  	if len(uncles) > 0 {
   213  		// Sort hashes, consensus
   214  		slices.SortFunc(uncles, func(a, b types.Hash) int {
   215  			return a.Compare(b)
   216  		})
   217  	}
   218  
   219  	return uncles
   220  }
   221  
   222  func (c *SideChain) BlockSeen(block *PoolBlock) bool {
   223  	tip := c.GetChainTip()
   224  
   225  	//early exit for
   226  	if tip != nil && tip.Side.Height > (block.Side.Height+c.Consensus().ChainWindowSize*2) && block.Side.CumulativeDifficulty.Cmp(tip.Side.CumulativeDifficulty) < 0 {
   227  		return true
   228  	}
   229  
   230  	fullId := block.FullId()
   231  
   232  	c.seenBlocksLock.Lock()
   233  	defer c.seenBlocksLock.Unlock()
   234  	if c.seenBlocks.Has(fullId) {
   235  		return true
   236  	} else {
   237  		c.seenBlocks.Put(fullId, struct{}{})
   238  		return false
   239  	}
   240  }
   241  
   242  func (c *SideChain) BlockUnsee(block *PoolBlock) {
   243  	fullId := block.FullId()
   244  
   245  	c.seenBlocksLock.Lock()
   246  	defer c.seenBlocksLock.Unlock()
   247  	c.seenBlocks.Delete(fullId)
   248  }
   249  
   250  func (c *SideChain) AddPoolBlockExternal(block *PoolBlock) (missingBlocks []types.Hash, err error, ban bool) {
   251  	defer func() {
   252  		if e := recover(); e != nil {
   253  			//recover from panics
   254  			missingBlocks = nil
   255  			if panicError, ok := e.(error); ok {
   256  				err = fmt.Errorf("panic: %w", panicError)
   257  			} else {
   258  				err = fmt.Errorf("panic: %v", e)
   259  			}
   260  			ban = true
   261  			utils.Errorf("SideChain", "add_external_block: panic %v, block %+v", e, block)
   262  		}
   263  	}()
   264  
   265  	// Technically some p2pool node could keep stuffing block with transactions until reward is less than 0.6 XMR
   266  	// But default transaction picking algorithm never does that. It's better to just ban such nodes
   267  	if block.Main.Coinbase.TotalReward < monero.TailEmissionReward {
   268  		return nil, errors.New("block reward too low"), true
   269  	}
   270  
   271  	// Enforce deterministic tx keys starting from v15
   272  	if block.Main.MajorVersion >= monero.HardForkViewTagsVersion {
   273  		if !c.isPoolBlockTransactionKeyIsDeterministic(block) {
   274  			return nil, errors.New("invalid deterministic transaction keys"), true
   275  		}
   276  	}
   277  
   278  	// Both tx types are allowed by Monero consensus during v15 because it needs to process pre-fork mempool transactions,
   279  	// but P2Pool can switch to using only TXOUT_TO_TAGGED_KEY for miner payouts starting from v15
   280  	expectedTxType := block.GetTransactionOutputType()
   281  
   282  	if missingBlocks, err = c.PreprocessBlock(block); err != nil {
   283  		return missingBlocks, err, true
   284  	}
   285  	for _, o := range block.Main.Coinbase.Outputs {
   286  		if o.Type != expectedTxType {
   287  			return nil, errors.New("unexpected transaction type"), true
   288  		}
   289  	}
   290  
   291  	templateId := types.HashFromBytes(block.CoinbaseExtra(SideTemplateId))
   292  	if templateId != block.SideTemplateId(c.Consensus()) {
   293  		return nil, fmt.Errorf("invalid template id %s, expected %s", templateId.String(), block.SideTemplateId(c.Consensus()).String()), true
   294  	}
   295  
   296  	if block.Side.Difficulty.Cmp64(c.Consensus().MinimumDifficulty) < 0 {
   297  		return nil, fmt.Errorf("block mined by %s has invalid difficulty %s, expected >= %d", block.GetAddress().ToBase58(c.Consensus().NetworkType.AddressNetwork()), block.Side.Difficulty.StringNumeric(), c.Consensus().MinimumDifficulty), true
   298  	}
   299  
   300  	expectedDifficulty := c.Difficulty()
   301  	tooLowDiff := block.Side.Difficulty.Cmp(expectedDifficulty) < 0
   302  
   303  	if otherBlock := c.GetPoolBlockByTemplateId(templateId); otherBlock != nil {
   304  		//already added
   305  		newMainId := block.MainId()
   306  		oldMainId := block.MainId()
   307  		utils.Logf("SideChain", "add_external_block: block id = %s is already added. New main id = %s, old main id = %s", templateId, newMainId, oldMainId)
   308  		if newMainId != oldMainId && otherBlock.Verified.Load() && !otherBlock.Invalid.Load() {
   309  			//other sections have been verified already, check PoW for new Main blocks
   310  
   311  			//specifically check Main id for nonce changes! p2pool does not do this
   312  
   313  			if _, err := block.PowHashWithError(c.Consensus().GetHasher(), c.getSeedByHeightFunc()); err != nil {
   314  				return nil, err, false
   315  			} else {
   316  				if isHigherMainChain, err := block.IsProofHigherThanMainDifficultyWithError(c.Consensus().GetHasher(), c.server.GetDifficultyByHeight, c.getSeedByHeightFunc()); err != nil {
   317  					utils.Logf("SideChain", "add_external_block: couldn't get mainchain difficulty for height = %d: %s", block.Main.Coinbase.GenHeight, err)
   318  				} else if isHigherMainChain {
   319  					utils.Logf("SideChain", "add_external_block: ALTERNATE block %s has enough PoW for Monero height %d, submitting it", templateId.String(), block.Main.Coinbase.GenHeight)
   320  					c.server.SubmitBlock(&block.Main)
   321  				}
   322  				if isHigher, err := block.IsProofHigherThanDifficultyWithError(c.Consensus().GetHasher(), c.getSeedByHeightFunc()); err != nil {
   323  					return nil, err, true
   324  				} else if !isHigher {
   325  					return nil, fmt.Errorf("not enough PoW for id %s, height = %d, mainchain height %d", templateId.String(), block.Side.Height, block.Main.Coinbase.GenHeight), true
   326  				}
   327  
   328  				{
   329  					c.sidechainLock.Lock()
   330  					defer c.sidechainLock.Unlock()
   331  
   332  					utils.Logf("SideChain", "add_external_block: ALTERNATE height = %d, id = %s, mainchain height = %d, verified = %t, total = %d", block.Side.Height, block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.Verified.Load(), c.blocksByTemplateId.Count())
   333  
   334  					block.Verified.Store(true)
   335  					block.Invalid.Store(false)
   336  					if block.SideTemplateId(c.Consensus()) == c.watchBlockSidechainId {
   337  						c.server.UpdateBlockFound(c.watchBlock, block)
   338  						c.watchBlockSidechainId = types.ZeroHash
   339  					}
   340  
   341  					block.Depth.Store(otherBlock.Depth.Load())
   342  					if block.WantBroadcast.Load() && !block.Broadcasted.Swap(true) {
   343  						//re-broadcast alternate blocks
   344  						c.server.Broadcast(block)
   345  					}
   346  					c.server.Store(block)
   347  				}
   348  			}
   349  		}
   350  		return nil, nil, false
   351  	}
   352  
   353  	// This is mainly an anti-spam measure, not an actual verification step
   354  	if tooLowDiff {
   355  		// Reduce required diff by 50% (by doubling this block's diff) to account for alternative chains
   356  		diff2 := block.Side.Difficulty.Mul64(2)
   357  		tip := c.GetChainTip()
   358  		for tmp := tip; tmp != nil && (tmp.Side.Height+c.Consensus().ChainWindowSize > tip.Side.Height); tmp = c.GetParent(tmp) {
   359  			if diff2.Cmp(tmp.Side.Difficulty) >= 0 {
   360  				tooLowDiff = false
   361  				break
   362  			}
   363  		}
   364  	}
   365  
   366  	if tooLowDiff {
   367  		return nil, fmt.Errorf("block mined by %s has too low difficulty %s, expected >= %s", block.GetAddress().ToBase58(c.Consensus().NetworkType.AddressNetwork()), block.Side.Difficulty.StringNumeric(), expectedDifficulty.StringNumeric()), false
   368  	}
   369  
   370  	// This check is not always possible to perform because of mainchain reorgs
   371  	if data := c.server.GetChainMainByHash(block.Main.PreviousId); data != nil {
   372  		if (data.Height + 1) != block.Main.Coinbase.GenHeight {
   373  			return nil, fmt.Errorf("wrong mainchain height %d, expected %d", block.Main.Coinbase.GenHeight, data.Height+1), true
   374  		}
   375  	} else {
   376  		//TODO warn unknown block, reorg
   377  	}
   378  
   379  	if _, err := block.PowHashWithError(c.Consensus().GetHasher(), c.getSeedByHeightFunc()); err != nil {
   380  		c.BlockUnsee(block)
   381  		return nil, err, false
   382  	} else {
   383  		if isHigherMainChain, err := block.IsProofHigherThanMainDifficultyWithError(c.Consensus().GetHasher(), c.server.GetDifficultyByHeight, c.getSeedByHeightFunc()); err != nil {
   384  			utils.Logf("SideChain", "add_external_block: couldn't get mainchain difficulty for height = %d: %s", block.Main.Coinbase.GenHeight, err)
   385  		} else if isHigherMainChain {
   386  			utils.Logf("SideChain", "add_external_block: block %s has enough PoW for Monero height %d, submitting it", templateId.String(), block.Main.Coinbase.GenHeight)
   387  			c.server.SubmitBlock(&block.Main)
   388  		}
   389  		if isHigher, err := block.IsProofHigherThanDifficultyWithError(c.Consensus().GetHasher(), c.getSeedByHeightFunc()); err != nil {
   390  			return nil, err, true
   391  		} else if !isHigher {
   392  			return nil, fmt.Errorf("not enough PoW for id %s, height = %d, mainchain height %d", templateId.String(), block.Side.Height, block.Main.Coinbase.GenHeight), true
   393  		}
   394  	}
   395  
   396  	//TODO: block found section
   397  
   398  	return func() []types.Hash {
   399  		c.sidechainLock.RLock()
   400  		defer c.sidechainLock.RUnlock()
   401  		missing := make([]types.Hash, 0, 4)
   402  		if block.Side.Parent != types.ZeroHash && c.getPoolBlockByTemplateId(block.Side.Parent) == nil {
   403  			missing = append(missing, block.Side.Parent)
   404  		}
   405  
   406  		for _, uncleId := range block.Side.Uncles {
   407  			if uncleId != types.ZeroHash && c.getPoolBlockByTemplateId(uncleId) == nil {
   408  				missing = append(missing, uncleId)
   409  			}
   410  		}
   411  		return missing
   412  	}(), c.AddPoolBlock(block), true
   413  }
   414  
   415  func (c *SideChain) AddPoolBlock(block *PoolBlock) (err error) {
   416  
   417  	c.sidechainLock.Lock()
   418  	defer c.sidechainLock.Unlock()
   419  	if c.blocksByTemplateId.Has(block.SideTemplateId(c.Consensus())) {
   420  		//already inserted
   421  		//TODO WARN
   422  		return nil
   423  	}
   424  
   425  	if _, err := block.AppendBinaryFlags(c.preAllocatedBuffer, false, false); err != nil {
   426  		return fmt.Errorf("encoding block error: %w", err)
   427  	}
   428  
   429  	c.blocksByTemplateId.Put(block.SideTemplateId(c.Consensus()), block)
   430  
   431  	utils.Logf("SideChain", "add_block: height = %d, id = %s, mainchain height = %d, verified = %t, total = %d", block.Side.Height, block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.Verified.Load(), c.blocksByTemplateId.Count())
   432  
   433  	if block.SideTemplateId(c.Consensus()) == c.watchBlockSidechainId {
   434  		c.server.UpdateBlockFound(c.watchBlock, block)
   435  		c.watchBlockSidechainId = types.ZeroHash
   436  	}
   437  
   438  	if l, ok := c.blocksByHeight.Get(block.Side.Height); ok {
   439  		c.blocksByHeight.Put(block.Side.Height, append(l, block))
   440  	} else {
   441  		c.blocksByHeight.Put(block.Side.Height, []*PoolBlock{block})
   442  		if !(c.blocksByHeightKeysSorted && len(c.blocksByHeightKeys) > 0 && c.blocksByHeightKeys[len(c.blocksByHeightKeys)-1]+1 == block.Side.Height) {
   443  			c.blocksByHeightKeysSorted = false
   444  		}
   445  		c.blocksByHeightKeys = append(c.blocksByHeightKeys, block.Side.Height)
   446  	}
   447  
   448  	c.updateDepths(block)
   449  
   450  	defer func() {
   451  		if !block.Invalid.Load() && block.Depth.Load() == 0 {
   452  			c.syncTip.Store(block)
   453  		}
   454  	}()
   455  
   456  	if block.Verified.Load() {
   457  		if !block.Invalid.Load() {
   458  			c.updateChainTip(block)
   459  		}
   460  
   461  		return nil
   462  	} else {
   463  		return c.verifyLoop(block)
   464  	}
   465  }
   466  
   467  func (c *SideChain) verifyLoop(blockToVerify *PoolBlock) (err error) {
   468  	// PoW is already checked at this point
   469  
   470  	blocksToVerify := make([]*PoolBlock, 1, 8)
   471  	blocksToVerify[0] = blockToVerify
   472  	var highestBlock *PoolBlock
   473  	for len(blocksToVerify) != 0 {
   474  		block := blocksToVerify[len(blocksToVerify)-1]
   475  		blocksToVerify = blocksToVerify[:len(blocksToVerify)-1]
   476  
   477  		if block.Verified.Load() {
   478  			continue
   479  		}
   480  
   481  		if verification, invalid := c.verifyBlock(block); invalid != nil {
   482  			utils.Logf("SideChain", "block at height = %d, id = %s, mainchain height = %d, mined by %s is invalid: %s", block.Side.Height, block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.GetAddress().ToBase58(c.Consensus().NetworkType.AddressNetwork()), invalid.Error())
   483  			block.Invalid.Store(true)
   484  			block.Verified.Store(verification == nil)
   485  			if block == blockToVerify {
   486  				//Save error for return
   487  				err = invalid
   488  			}
   489  		} else if verification != nil {
   490  			//utils.Logf("SideChain", "can't verify block at height = %d, id = %s, mainchain height = %d, mined by %s: %s", block.Side.Height, block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.GetAddress().ToBase58(), verification.Error())
   491  			block.Verified.Store(false)
   492  			block.Invalid.Store(false)
   493  		} else {
   494  			block.Verified.Store(true)
   495  			block.Invalid.Store(false)
   496  
   497  			if block.ShareVersion() > ShareVersion_V1 {
   498  				utils.Logf("SideChain", "verified block at height = %d, depth = %d, id = %s, mainchain height = %d, mined by %s via %s %s", block.Side.Height, block.Depth.Load(), block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.GetAddress().ToBase58(c.Consensus().NetworkType.AddressNetwork()), block.Side.ExtraBuffer.SoftwareId, block.Side.ExtraBuffer.SoftwareVersion)
   499  			} else {
   500  				if signalingVersion := block.ShareVersionSignaling(); signalingVersion > ShareVersion_None {
   501  					utils.Logf("SideChain", "verified block at height = %d, depth = %d, id = %s, mainchain height = %d, mined by %s, signaling v%d", block.Side.Height, block.Depth.Load(), block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.GetAddress().ToBase58(c.Consensus().NetworkType.AddressNetwork()), signalingVersion)
   502  				} else {
   503  					utils.Logf("SideChain", "verified block at height = %d, depth = %d, id = %s, mainchain height = %d, mined by %s", block.Side.Height, block.Depth.Load(), block.SideTemplateId(c.Consensus()), block.Main.Coinbase.GenHeight, block.GetAddress().ToBase58(c.Consensus().NetworkType.AddressNetwork()))
   504  				}
   505  			}
   506  
   507  			// This block is now verified
   508  
   509  			// Fill cache here
   510  
   511  			if parent := c.getParent(block); parent != nil {
   512  				block.iterationCache = &IterationCache{
   513  					Parent: parent,
   514  					Uncles: nil,
   515  				}
   516  
   517  				if len(block.Side.Uncles) > 0 {
   518  					block.iterationCache.Uncles = make([]*PoolBlock, 0, len(block.Side.Uncles))
   519  					for _, uncleId := range block.Side.Uncles {
   520  						if uncle := c.getPoolBlockByTemplateId(uncleId); uncle == nil {
   521  							block.iterationCache = nil
   522  							break
   523  						} else {
   524  							block.iterationCache.Uncles = append(block.iterationCache.Uncles, uncle)
   525  						}
   526  					}
   527  				}
   528  			}
   529  
   530  			c.fillPoolBlockTransactionParentIndices(block)
   531  
   532  			if isLongerChain, _ := c.isLongerChain(highestBlock, block); isLongerChain {
   533  				highestBlock = block
   534  			} else if highestBlock != nil && highestBlock.Side.Height > block.Side.Height {
   535  				utils.Logf("SideChain", "block at height = %d, id = %s, is not a longer chain than height = %d, id = %s", block.Side.Height, block.SideTemplateId(c.Consensus()), highestBlock.Side.Height, highestBlock.SideTemplateId(c.Consensus()))
   536  			}
   537  
   538  			if block.WantBroadcast.Load() && !block.Broadcasted.Swap(true) {
   539  				if block.Depth.Load() < UncleBlockDepth {
   540  					c.server.Broadcast(block)
   541  				}
   542  			}
   543  
   544  			//store for faster startup
   545  			c.saveBlock(block)
   546  
   547  			// Try to verify blocks on top of this one
   548  			for i := uint64(1); i <= UncleBlockDepth; i++ {
   549  				blocksAtHeight, _ := c.blocksByHeight.Get(block.Side.Height + i)
   550  				blocksToVerify = append(blocksToVerify, blocksAtHeight...)
   551  			}
   552  		}
   553  	}
   554  
   555  	if highestBlock != nil {
   556  		c.updateChainTip(highestBlock)
   557  	}
   558  
   559  	return
   560  }
   561  
   562  func (c *SideChain) verifyBlock(block *PoolBlock) (verification error, invalid error) {
   563  	// Genesis
   564  	if block.Side.Height == 0 {
   565  		if block.Side.Parent != types.ZeroHash ||
   566  			len(block.Side.Uncles) != 0 ||
   567  			block.Side.Difficulty.Cmp64(c.Consensus().MinimumDifficulty) != 0 ||
   568  			block.Side.CumulativeDifficulty.Cmp64(c.Consensus().MinimumDifficulty) != 0 ||
   569  			(block.ShareVersion() > ShareVersion_V1 && block.Side.CoinbasePrivateKeySeed != c.Consensus().Id) {
   570  			return nil, errors.New("genesis block has invalid parameters")
   571  		}
   572  		//this does not verify coinbase outputs, but that's fine
   573  		return nil, nil
   574  	}
   575  
   576  	// Deep block
   577  	//
   578  	// Blocks in PPLNS window (m_chainWindowSize) require up to m_chainWindowSize earlier blocks to verify
   579  	// If a block is deeper than (m_chainWindowSize - 1) * 2 + UNCLE_BLOCK_DEPTH it can't influence blocks in PPLNS window
   580  	// Also, having so many blocks on top of this one means it was verified by the network at some point
   581  	// We skip checks in this case to make pruning possible
   582  	if block.Depth.Load() > ((c.Consensus().ChainWindowSize-1)*2 + UncleBlockDepth) {
   583  		utils.Logf("SideChain", "block at height = %d, id = %s skipped verification", block.Side.Height, block.SideTemplateId(c.Consensus()))
   584  		return nil, nil
   585  	}
   586  
   587  	//Regular block
   588  	//Must have parent
   589  	if block.Side.Parent == types.ZeroHash {
   590  		return nil, errors.New("block must have a parent")
   591  	}
   592  
   593  	if parent := c.getParent(block); parent != nil {
   594  		// If it's invalid then this block is also invalid
   595  		if !parent.Verified.Load() {
   596  			return errors.New("parent is not verified"), nil
   597  		}
   598  		if parent.Invalid.Load() {
   599  			return nil, errors.New("parent is invalid")
   600  		}
   601  
   602  		if block.ShareVersion() > ShareVersion_V1 {
   603  			expectedSeed := parent.Side.CoinbasePrivateKeySeed
   604  			if parent.Main.PreviousId != block.Main.PreviousId {
   605  				expectedSeed = parent.CalculateTransactionPrivateKeySeed()
   606  			}
   607  			if block.Side.CoinbasePrivateKeySeed != expectedSeed {
   608  				return nil, fmt.Errorf("invalid tx key seed: expected %s, got %s", expectedSeed.String(), block.Side.CoinbasePrivateKeySeed.String())
   609  			}
   610  		}
   611  
   612  		expectedHeight := parent.Side.Height + 1
   613  		if expectedHeight != block.Side.Height {
   614  			return nil, fmt.Errorf("wrong height, expected %d", expectedHeight)
   615  		}
   616  
   617  		// Uncle hashes must be sorted in the ascending order to prevent cheating when the same hash is repeated multiple times
   618  		for i, uncleId := range block.Side.Uncles {
   619  			if i == 0 {
   620  				continue
   621  			}
   622  			if block.Side.Uncles[i-1].Compare(uncleId) != -1 {
   623  				return nil, errors.New("invalid uncle order")
   624  			}
   625  		}
   626  
   627  		expectedCumulativeDifficulty := parent.Side.CumulativeDifficulty.Add(block.Side.Difficulty)
   628  
   629  		//check uncles
   630  
   631  		minedBlocks := c.preAllocatedMinedBlocks[:0]
   632  		{
   633  			tmp := parent
   634  			n := min(UncleBlockDepth, block.Side.Height+1)
   635  			for i := uint64(0); tmp != nil && i < n; i++ {
   636  				minedBlocks = append(minedBlocks, tmp.SideTemplateId(c.Consensus()))
   637  				for _, uncleId := range tmp.Side.Uncles {
   638  					minedBlocks = append(minedBlocks, uncleId)
   639  				}
   640  				tmp = c.getParent(tmp)
   641  			}
   642  		}
   643  
   644  		for _, uncleId := range block.Side.Uncles {
   645  			// Empty hash is only used in the genesis block and only for its parent
   646  			// Uncles can't be empty
   647  			if uncleId == types.ZeroHash {
   648  				return nil, errors.New("empty uncle hash")
   649  			}
   650  
   651  			// Can't mine the same uncle block twice
   652  			if slices.Index(minedBlocks, uncleId) != -1 {
   653  				return nil, fmt.Errorf("uncle %s has already been mined", uncleId.String())
   654  			}
   655  
   656  			if uncle := c.getPoolBlockByTemplateId(uncleId); uncle == nil {
   657  				return errors.New("uncle does not exist"), nil
   658  			} else if !uncle.Verified.Load() {
   659  				// If it's invalid then this block is also invalid
   660  				return errors.New("uncle is not verified"), nil
   661  			} else if uncle.Invalid.Load() {
   662  				// If it's invalid then this block is also invalid
   663  				return nil, errors.New("uncle is invalid")
   664  			} else if uncle.Side.Height >= block.Side.Height || (uncle.Side.Height+UncleBlockDepth < block.Side.Height) {
   665  				return nil, fmt.Errorf("uncle at the wrong height (%d)", uncle.Side.Height)
   666  			} else {
   667  				// Check that uncle and parent have the same ancestor (they must be on the same chain)
   668  				tmp := parent
   669  				for tmp.Side.Height > uncle.Side.Height {
   670  					tmp = c.getParent(tmp)
   671  					if tmp == nil {
   672  						return nil, errors.New("uncle from different chain (check 1)")
   673  					}
   674  				}
   675  
   676  				if tmp.Side.Height < uncle.Side.Height {
   677  					return nil, errors.New("uncle from different chain (check 2)")
   678  				}
   679  
   680  				if sameChain := func() bool {
   681  					tmp2 := uncle
   682  					for j := uint64(0); j < UncleBlockDepth && tmp != nil && tmp2 != nil && (tmp.Side.Height+UncleBlockDepth >= block.Side.Height); j++ {
   683  						if tmp.Side.Parent == tmp2.Side.Parent {
   684  							return true
   685  						}
   686  						tmp = c.getParent(tmp)
   687  						tmp2 = c.getParent(tmp2)
   688  					}
   689  					return false
   690  				}(); !sameChain {
   691  					return nil, errors.New("uncle from different chain (check 3)")
   692  				}
   693  
   694  				expectedCumulativeDifficulty = expectedCumulativeDifficulty.Add(uncle.Side.Difficulty)
   695  
   696  			}
   697  
   698  		}
   699  
   700  		// We can verify this block now (all previous blocks in the window are verified and valid)
   701  		// It can still turn out to be invalid
   702  
   703  		if !block.Side.CumulativeDifficulty.Equals(expectedCumulativeDifficulty) {
   704  			return nil, fmt.Errorf("wrong cumulative difficulty, got %s, expected %s", block.Side.CumulativeDifficulty.StringNumeric(), expectedCumulativeDifficulty.StringNumeric())
   705  		}
   706  
   707  		// Verify difficulty and miner rewards only for blocks in PPLNS window
   708  		if block.Depth.Load() >= c.Consensus().ChainWindowSize {
   709  			utils.Logf("SideChain", "block at height = %d, id = %s skipped diff/reward verification", block.Side.Height, block.SideTemplateId(c.Consensus()))
   710  			return
   711  		}
   712  
   713  		var diff types.Difficulty
   714  
   715  		if parent == c.GetChainTip() {
   716  			// built on top of the current chain tip, using current difficulty for verification
   717  			diff = c.Difficulty()
   718  		} else if diff, verification, invalid = c.getDifficulty(parent); verification != nil || invalid != nil {
   719  			return verification, invalid
   720  		} else if diff == types.ZeroDifficulty {
   721  			return nil, errors.New("could not get difficulty")
   722  		}
   723  		if diff != block.Side.Difficulty {
   724  			return nil, fmt.Errorf("wrong difficulty, got %s, expected %s", block.Side.Difficulty.StringNumeric(), diff.StringNumeric())
   725  		}
   726  
   727  		if shares, _ := c.getShares(block, c.preAllocatedShares); len(shares) == 0 {
   728  			return nil, errors.New("could not get outputs")
   729  		} else if len(shares) != len(block.Main.Coinbase.Outputs) {
   730  			return nil, fmt.Errorf("invalid number of outputs, got %d, expected %d", len(block.Main.Coinbase.Outputs), len(shares))
   731  		} else if totalReward := func() (result uint64) {
   732  			for _, o := range block.Main.Coinbase.Outputs {
   733  				result += o.Reward
   734  			}
   735  			return
   736  		}(); totalReward != block.Main.Coinbase.TotalReward {
   737  			return nil, fmt.Errorf("invalid total reward, got %d, expected %d", block.Main.Coinbase.TotalReward, totalReward)
   738  		} else if rewards := SplitReward(c.preAllocatedRewards, totalReward, shares); len(rewards) != len(block.Main.Coinbase.Outputs) {
   739  			return nil, fmt.Errorf("invalid number of outputs, got %d, expected %d", len(block.Main.Coinbase.Outputs), len(rewards))
   740  		} else {
   741  
   742  			//prevent multiple allocations
   743  			txPrivateKeySlice := block.Side.CoinbasePrivateKey.AsSlice()
   744  			txPrivateKeyScalar := block.Side.CoinbasePrivateKey.AsScalar()
   745  
   746  			var hashers []*sha3.HasherState
   747  
   748  			var anyErr atomic.Value
   749  
   750  			defer func() {
   751  				for _, h := range hashers {
   752  					crypto.PutKeccak256Hasher(h)
   753  				}
   754  			}()
   755  
   756  			if !utils.SplitWork(-2, uint64(len(rewards)), func(workIndex uint64, workerIndex int) error {
   757  				out := block.Main.Coinbase.Outputs[workIndex]
   758  				if rewards[workIndex] != out.Reward {
   759  					return fmt.Errorf("has invalid reward at index %d, got %d, expected %d", workIndex, out.Reward, rewards[workIndex])
   760  				}
   761  
   762  				if ephPublicKey, viewTag := c.derivationCache.GetEphemeralPublicKey(&shares[workIndex].Address, txPrivateKeySlice, txPrivateKeyScalar, workIndex, hashers[workerIndex]); ephPublicKey != out.EphemeralPublicKey {
   763  					return fmt.Errorf("has incorrect eph_public_key at index %d, got %s, expected %s", workIndex, out.EphemeralPublicKey.String(), ephPublicKey.String())
   764  				} else if out.Type == transaction.TxOutToTaggedKey && viewTag != out.ViewTag {
   765  					return fmt.Errorf("has incorrect view tag at index %d, got %d, expected %d", workIndex, out.ViewTag, viewTag)
   766  				}
   767  				return nil
   768  			}, func(routines, routineIndex int) error {
   769  				hashers = append(hashers, crypto.GetKeccak256Hasher())
   770  				return nil
   771  			}, func(routineIndex int, err error) {
   772  				anyErr.Store(err)
   773  			}) {
   774  				return nil, anyErr.Load().(error)
   775  			}
   776  		}
   777  
   778  		// All checks passed
   779  		return nil, nil
   780  	} else {
   781  		return errors.New("parent does not exist"), nil
   782  	}
   783  }
   784  
   785  func (c *SideChain) updateDepths(block *PoolBlock) {
   786  	preCalcDepth := c.Consensus().ChainWindowSize + UncleBlockDepth - 1
   787  
   788  	updateDepth := func(b *PoolBlock, newDepth uint64) {
   789  		oldDepth := b.Depth.Load()
   790  		if oldDepth < newDepth {
   791  			b.Depth.Store(newDepth)
   792  			if oldDepth < preCalcDepth && newDepth >= preCalcDepth {
   793  				//TODO launchPrecalc
   794  			}
   795  		}
   796  	}
   797  
   798  	for i := uint64(1); i <= UncleBlockDepth; i++ {
   799  		blocksAtHeight, _ := c.blocksByHeight.Get(block.Side.Height + i)
   800  		for _, child := range blocksAtHeight {
   801  			if child.Side.Parent == block.SideTemplateId(c.Consensus()) {
   802  				if i != 1 {
   803  					utils.Logf("SideChain", "Block %s side height %d is inconsistent with child's side_height %d", block.SideTemplateId(c.Consensus()), block.Side.Height, child.Side.Height)
   804  					return
   805  				} else {
   806  					updateDepth(block, child.Depth.Load()+1)
   807  				}
   808  			}
   809  
   810  			if ix := slices.Index(child.Side.Uncles, block.SideTemplateId(c.Consensus())); ix != 1 {
   811  				updateDepth(block, child.Depth.Load()+1)
   812  			}
   813  		}
   814  	}
   815  
   816  	blocksToUpdate := make([]*PoolBlock, 1, 8)
   817  	blocksToUpdate[0] = block
   818  
   819  	for len(blocksToUpdate) != 0 {
   820  		block = blocksToUpdate[len(blocksToUpdate)-1]
   821  		blocksToUpdate = blocksToUpdate[:len(blocksToUpdate)-1]
   822  
   823  		blockDepth := block.Depth.Load()
   824  		// Verify this block and possibly other blocks on top of it when we're sure it will get verified
   825  		if !block.Verified.Load() && (blockDepth >= c.Consensus().ChainWindowSize*2 || block.Side.Height == 0) {
   826  			_ = c.verifyLoop(block)
   827  		}
   828  
   829  		for i := uint64(1); i <= UncleBlockDepth; i++ {
   830  			for _, child := range c.getPoolBlocksByHeight(block.Side.Height + i) {
   831  				oldDepth := child.Depth.Load()
   832  
   833  				if child.Side.Parent == block.SideTemplateId(c.Consensus()) {
   834  					if i != 1 {
   835  						utils.Logf("SideChain", "Block %s side height %d is inconsistent with child's side_height %d", block.SideTemplateId(c.Consensus()), block.Side.Height, child.Side.Height)
   836  						return
   837  					} else if blockDepth > 0 {
   838  						updateDepth(child, blockDepth-1)
   839  					}
   840  				}
   841  
   842  				if slices.Contains(child.Side.Uncles, block.SideTemplateId(c.Consensus())) {
   843  					if blockDepth > i {
   844  						updateDepth(child, blockDepth-i)
   845  					}
   846  				}
   847  
   848  				if child.Depth.Load() > oldDepth {
   849  					blocksToUpdate = append(blocksToUpdate, child)
   850  				}
   851  			}
   852  		}
   853  
   854  		if parent := block.iteratorGetParent(c.getPoolBlockByTemplateId); parent != nil {
   855  			if parent.Side.Height+1 != block.Side.Height {
   856  				utils.Logf("SideChain", "Block %s side height %d is inconsistent with parent's side_height %d", block.SideTemplateId(c.Consensus()), block.Side.Height, parent.Side.Height)
   857  				return
   858  			}
   859  
   860  			if parent.Depth.Load() < blockDepth+1 {
   861  				updateDepth(parent, blockDepth+1)
   862  				blocksToUpdate = append(blocksToUpdate, parent)
   863  			}
   864  		}
   865  
   866  		var returnFromUncles bool
   867  
   868  		_ = block.iteratorUncles(c.getPoolBlockByTemplateId, func(uncle *PoolBlock) {
   869  			if uncle.Side.Height >= block.Side.Height || (uncle.Side.Height+UncleBlockDepth < block.Side.Height) {
   870  				utils.Logf("SideChain", "Block %s side height %d is inconsistent with uncle's side_height %d", block.SideTemplateId(c.Consensus()), block.Side.Height, uncle.Side.Height)
   871  				returnFromUncles = true
   872  				return
   873  			}
   874  
   875  			d := block.Side.Height - uncle.Side.Height
   876  			if uncle.Depth.Load() < blockDepth+d {
   877  				updateDepth(uncle, blockDepth+d)
   878  				blocksToUpdate = append(blocksToUpdate, uncle)
   879  			}
   880  		})
   881  		if returnFromUncles {
   882  			return
   883  		}
   884  	}
   885  }
   886  
   887  func (c *SideChain) updateChainTip(block *PoolBlock) {
   888  	if !block.Verified.Load() || block.Invalid.Load() {
   889  		//todo err
   890  		return
   891  	}
   892  
   893  	if block.Depth.Load() >= c.Consensus().ChainWindowSize {
   894  		//TODO err
   895  		return
   896  	}
   897  
   898  	tip := c.GetChainTip()
   899  
   900  	if block == tip {
   901  		utils.Logf("SideChain", "Trying to update chain tip to the same block again. Ignoring it.")
   902  		return
   903  	}
   904  
   905  	if isLongerChain, isAlternative := c.isLongerChain(tip, block); isLongerChain {
   906  		if diff, _, _ := c.getDifficulty(block); diff != types.ZeroDifficulty {
   907  			c.chainTip.Store(block)
   908  			c.syncTip.Store(block)
   909  			c.currentDifficulty.Store(&diff)
   910  			//TODO log
   911  
   912  			block.WantBroadcast.Store(true)
   913  			c.server.UpdateTip(block)
   914  
   915  			if isAlternative {
   916  				c.precalcFinished.Store(true)
   917  				c.derivationCache.Clear()
   918  
   919  				utils.Logf("SideChain", "SYNCHRONIZED to tip %s", block.SideTemplateId(c.Consensus()))
   920  			}
   921  
   922  			c.pruneOldBlocks()
   923  		}
   924  	} else if block.Side.Height > tip.Side.Height {
   925  		utils.Logf("SideChain", "block %s, height = %d, is not a longer chain than %s, height = %d", block.SideTemplateId(c.Consensus()), block.Side.Height, tip.SideTemplateId(c.Consensus()), tip.Side.Height)
   926  	} else if block.Side.Height+UncleBlockDepth > tip.Side.Height {
   927  		utils.Logf("SideChain", "possible uncle block: id = %s, height = %d", block.SideTemplateId(c.Consensus()), block.Side.Height)
   928  	}
   929  
   930  	if block.WantBroadcast.Load() && !block.Broadcasted.Swap(true) {
   931  		c.server.Broadcast(block)
   932  	}
   933  
   934  }
   935  
   936  func (c *SideChain) pruneOldBlocks() {
   937  
   938  	// Leave 2 minutes worth of spare blocks in addition to 2xPPLNS window for lagging nodes which need to sync
   939  	pruneDistance := c.Consensus().ChainWindowSize*2 + monero.BlockTime/c.Consensus().TargetBlockTime
   940  
   941  	curTime := uint64(time.Now().Unix())
   942  
   943  	// Remove old blocks from alternative unconnected chains after long enough time
   944  	pruneDelay := c.Consensus().ChainWindowSize * 4 * c.Consensus().TargetBlockTime
   945  
   946  	tip := c.GetChainTip()
   947  	if tip == nil || tip.Side.Height < pruneDistance {
   948  		return
   949  	}
   950  
   951  	h := tip.Side.Height - pruneDistance
   952  
   953  	if !c.blocksByHeightKeysSorted {
   954  		slices.Sort(c.blocksByHeightKeys)
   955  		c.blocksByHeightKeysSorted = true
   956  	}
   957  
   958  	numBlocksPruned := 0
   959  
   960  	for keyIndex, height := range c.blocksByHeightKeys {
   961  		// Early exit
   962  		if height > h {
   963  			break
   964  		}
   965  
   966  		v, _ := c.blocksByHeight.Get(height)
   967  
   968  		// loop backwards for proper deletions
   969  		for i := len(v) - 1; i >= 0; i-- {
   970  			block := v[i]
   971  			if block.Depth.Load() >= pruneDistance || (curTime >= (block.LocalTimestamp + pruneDelay)) {
   972  				templateId := block.SideTemplateId(c.Consensus())
   973  				if c.blocksByTemplateId.Has(templateId) {
   974  					c.blocksByTemplateId.Delete(templateId)
   975  					numBlocksPruned++
   976  				} else {
   977  					utils.Logf("SideChain", "blocksByHeight and blocksByTemplateId are inconsistent at height = %d, id = %s", height, block.SideTemplateId(c.Consensus()))
   978  				}
   979  				v = slices.Delete(v, i, i+1)
   980  
   981  				// Empty cache here
   982  				block.iterationCache = nil
   983  			}
   984  		}
   985  
   986  		if len(v) == 0 {
   987  			c.blocksByHeight.Delete(height)
   988  			c.blocksByHeightKeys = slices.Delete(c.blocksByHeightKeys, keyIndex, keyIndex+1)
   989  		} else {
   990  			c.blocksByHeight.Put(height, v)
   991  		}
   992  	}
   993  
   994  	if numBlocksPruned > 0 {
   995  		utils.Logf("SideChain", "pruned %d old blocks at heights <= %d", numBlocksPruned, h)
   996  		if !c.precalcFinished.Swap(true) {
   997  			c.derivationCache.Clear()
   998  		}
   999  
  1000  		numSeenBlocksPruned := c.cleanupSeenBlocks()
  1001  		if numSeenBlocksPruned > 0 {
  1002  			//utils.Logf("SideChain", "pruned %d seen blocks", numBlocksPruned)
  1003  		}
  1004  	}
  1005  }
  1006  
  1007  func (c *SideChain) cleanupSeenBlocks() (cleaned int) {
  1008  	c.seenBlocksLock.Lock()
  1009  	defer c.seenBlocksLock.Unlock()
  1010  
  1011  	c.seenBlocks.Iter(func(k FullId, _ struct{}) (stop bool) {
  1012  		if c.getPoolBlockByTemplateId(k.TemplateId()) == nil {
  1013  			c.seenBlocks.Delete(k)
  1014  			cleaned++
  1015  		}
  1016  		return false
  1017  	})
  1018  	return cleaned
  1019  }
  1020  
  1021  func (c *SideChain) GetMissingBlocks() []types.Hash {
  1022  	c.sidechainLock.RLock()
  1023  	defer c.sidechainLock.RUnlock()
  1024  
  1025  	missingBlocks := make([]types.Hash, 0)
  1026  
  1027  	c.blocksByTemplateId.Iter(func(_ types.Hash, b *PoolBlock) (stop bool) {
  1028  		if b.Verified.Load() {
  1029  			return false
  1030  		}
  1031  
  1032  		if b.Side.Parent != types.ZeroHash && c.getPoolBlockByTemplateId(b.Side.Parent) == nil {
  1033  			missingBlocks = append(missingBlocks, b.Side.Parent)
  1034  		}
  1035  
  1036  		missingUncles := 0
  1037  
  1038  		for _, uncleId := range b.Side.Uncles {
  1039  			if uncleId != types.ZeroHash && c.getPoolBlockByTemplateId(uncleId) == nil {
  1040  				missingBlocks = append(missingBlocks, uncleId)
  1041  				missingUncles++
  1042  
  1043  				// Get no more than 2 first missing uncles at a time from each block
  1044  				// Blocks with more than 2 uncles are very rare and they will be processed in several steps
  1045  				if missingUncles >= 2 {
  1046  					return true
  1047  				}
  1048  			}
  1049  		}
  1050  		return false
  1051  	})
  1052  
  1053  	return missingBlocks
  1054  }
  1055  
  1056  // calculateOutputs
  1057  // Deprecated
  1058  func (c *SideChain) calculateOutputs(block *PoolBlock) (outputs transaction.Outputs, bottomHeight uint64) {
  1059  	preAllocatedShares := c.preAllocatedSharesPool.Get()
  1060  	defer c.preAllocatedSharesPool.Put(preAllocatedShares)
  1061  	return CalculateOutputs(block, c.Consensus(), c.server.GetDifficultyByHeight, c.getPoolBlockByTemplateId, c.derivationCache, preAllocatedShares, c.preAllocatedRewards)
  1062  }
  1063  
  1064  func (c *SideChain) Server() P2PoolInterface {
  1065  	return c.server
  1066  }
  1067  
  1068  func (c *SideChain) getShares(tip *PoolBlock, preAllocatedShares Shares) (shares Shares, bottomHeight uint64) {
  1069  	return GetShares(tip, c.Consensus(), c.server.GetDifficultyByHeight, c.getPoolBlockByTemplateId, preAllocatedShares)
  1070  }
  1071  
  1072  func (c *SideChain) GetDifficulty(tip *PoolBlock) (difficulty types.Difficulty, verifyError, invalidError error) {
  1073  	c.sidechainLock.RLock()
  1074  	defer c.sidechainLock.RUnlock()
  1075  	return c.getDifficulty(tip)
  1076  }
  1077  
  1078  func (c *SideChain) getDifficulty(tip *PoolBlock) (difficulty types.Difficulty, verifyError, invalidError error) {
  1079  	return GetDifficultyForNextBlock(tip, c.Consensus(), c.getPoolBlockByTemplateId, c.preAllocatedDifficultyData, c.preAllocatedTimestampData)
  1080  }
  1081  
  1082  func (c *SideChain) GetParent(block *PoolBlock) *PoolBlock {
  1083  	c.sidechainLock.RLock()
  1084  	defer c.sidechainLock.RUnlock()
  1085  	return c.getParent(block)
  1086  }
  1087  
  1088  func (c *SideChain) getParent(block *PoolBlock) *PoolBlock {
  1089  	return block.iteratorGetParent(c.getPoolBlockByTemplateId)
  1090  }
  1091  
  1092  func (c *SideChain) GetPoolBlockByTemplateId(id types.Hash) *PoolBlock {
  1093  	c.sidechainLock.RLock()
  1094  	defer c.sidechainLock.RUnlock()
  1095  	return c.getPoolBlockByTemplateId(id)
  1096  }
  1097  
  1098  func (c *SideChain) getPoolBlockByTemplateId(id types.Hash) *PoolBlock {
  1099  	b, _ := c.blocksByTemplateId.Get(id)
  1100  	return b
  1101  }
  1102  
  1103  func (c *SideChain) GetPoolBlocksByHeight(height uint64) []*PoolBlock {
  1104  	c.sidechainLock.RLock()
  1105  	defer c.sidechainLock.RUnlock()
  1106  	return slices.Clone(c.getPoolBlocksByHeight(height))
  1107  }
  1108  
  1109  func (c *SideChain) getPoolBlocksByHeight(height uint64) []*PoolBlock {
  1110  	b, _ := c.blocksByHeight.Get(height)
  1111  	return b
  1112  }
  1113  
  1114  func (c *SideChain) GetPoolBlocksFromTip(id types.Hash) (chain, uncles UniquePoolBlockSlice) {
  1115  	chain = make([]*PoolBlock, 0, c.Consensus().ChainWindowSize*2+monero.BlockTime/c.Consensus().TargetBlockTime)
  1116  	uncles = make([]*PoolBlock, 0, len(chain)/20)
  1117  
  1118  	c.sidechainLock.RLock()
  1119  	defer c.sidechainLock.RUnlock()
  1120  	for cur := c.getPoolBlockByTemplateId(id); cur != nil; cur = c.getPoolBlockByTemplateId(cur.Side.Parent) {
  1121  		for i, uncleId := range cur.Side.Uncles {
  1122  			if u := c.getPoolBlockByTemplateId(uncleId); u == nil {
  1123  				//return few uncles than necessary
  1124  				return chain, uncles[:len(uncles)-i]
  1125  			} else {
  1126  				uncles = append(uncles, u)
  1127  			}
  1128  		}
  1129  		chain = append(chain, cur)
  1130  	}
  1131  
  1132  	return chain, uncles
  1133  }
  1134  
  1135  func (c *SideChain) GetPoolBlocksFromTipWithDepth(id types.Hash, depth uint64) (chain, uncles UniquePoolBlockSlice) {
  1136  	chain = make([]*PoolBlock, 0, min(depth, c.Consensus().ChainWindowSize*2+monero.BlockTime/c.Consensus().TargetBlockTime))
  1137  	uncles = make([]*PoolBlock, 0, len(chain)/20)
  1138  
  1139  	c.sidechainLock.RLock()
  1140  	defer c.sidechainLock.RUnlock()
  1141  	for cur := c.getPoolBlockByTemplateId(id); cur != nil && len(chain) < int(depth); cur = c.getPoolBlockByTemplateId(cur.Side.Parent) {
  1142  		for i, uncleId := range cur.Side.Uncles {
  1143  			if u := c.getPoolBlockByTemplateId(uncleId); u == nil {
  1144  				//return few uncles than necessary
  1145  				return chain, uncles[:len(uncles)-i]
  1146  			} else {
  1147  				uncles = append(uncles, u)
  1148  			}
  1149  		}
  1150  		chain = append(chain, cur)
  1151  	}
  1152  
  1153  	return chain, uncles
  1154  }
  1155  
  1156  func (c *SideChain) GetPoolBlockCount() int {
  1157  	c.sidechainLock.RLock()
  1158  	defer c.sidechainLock.RUnlock()
  1159  	return c.blocksByTemplateId.Count()
  1160  }
  1161  
  1162  func (c *SideChain) WatchMainChainBlock(mainData *ChainMain, possibleId types.Hash) {
  1163  	c.sidechainLock.Lock()
  1164  	defer c.sidechainLock.Unlock()
  1165  
  1166  	c.watchBlock = mainData
  1167  	c.watchBlockSidechainId = possibleId
  1168  }
  1169  
  1170  func (c *SideChain) GetHighestKnownTip() *PoolBlock {
  1171  	if t := c.chainTip.Load(); t != nil {
  1172  		return t
  1173  	}
  1174  	return c.syncTip.Load()
  1175  }
  1176  
  1177  func (c *SideChain) GetChainTip() *PoolBlock {
  1178  	return c.chainTip.Load()
  1179  }
  1180  
  1181  func (c *SideChain) LastUpdated() uint64 {
  1182  	if tip := c.chainTip.Load(); tip != nil {
  1183  		return tip.LocalTimestamp
  1184  	}
  1185  	return 0
  1186  }
  1187  
  1188  func (c *SideChain) IsLongerChain(block, candidate *PoolBlock) (isLonger, isAlternative bool) {
  1189  	c.sidechainLock.RLock()
  1190  	defer c.sidechainLock.RUnlock()
  1191  	return c.isLongerChain(block, candidate)
  1192  }
  1193  
  1194  func (c *SideChain) isLongerChain(block, candidate *PoolBlock) (isLonger, isAlternative bool) {
  1195  	return IsLongerChain(block, candidate, c.Consensus(), c.getPoolBlockByTemplateId, func(h types.Hash) *ChainMain {
  1196  		if h == types.ZeroHash {
  1197  			return c.server.GetChainMainTip()
  1198  		}
  1199  		return c.server.GetChainMainByHash(h)
  1200  	})
  1201  }