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 }