/// <summary> /// Determines the block that a checkpoint is at. /// </summary> /// <param name="indexerCheckpoints">The type of checkpoint (wallets, blocks, transactions or balances).</param> /// <returns>The block that a checkpoint is at.</returns> private ChainedHeader GetCheckPointBlock(IndexerCheckpoints indexerCheckpoints) { this.logger.LogTrace("()"); Checkpoint checkpoint = this.AzureIndexer.GetCheckpointInternal(indexerCheckpoints); ChainedHeader fork = this.Chain.FindFork(checkpoint.BlockLocator); this.logger.LogTrace("(-):{0}", fork?.ToString()); return(fork); }
/// <summary>Processes a block that was added or removed from the consensus chain.</summary> /// <param name="block">The block to process.</param> /// <param name="header">The chained header associated to the block being processed.</param> /// <returns><c>true</c> if block was sucessfully processed.</returns> private bool ProcessBlock(Block block, ChainedHeader header) { this.logger.LogTrace("Processing block " + header.ToString()); lock (this.lockObject) { // Record outpoints. foreach (Transaction tx in block.Transactions) { for (int i = 0; i < tx.Outputs.Count; i++) { // OP_RETURN outputs and empty outputs cannot be spent and therefore do not need to be put into the cache. if (tx.Outputs[i].IsEmpty || tx.Outputs[i].ScriptPubKey.IsUnspendable) { continue; } var outPoint = new OutPoint(tx, i); var outPointData = new OutPointData() { Outpoint = outPoint.ToString(), ScriptPubKeyBytes = tx.Outputs[i].ScriptPubKey.ToBytes(), Money = tx.Outputs[i].Value }; // TODO: When the outpoint cache is full, adding outpoints singly causes overhead writing evicted entries out to the repository this.outpointsRepository.AddOutPointData(outPointData); } } } // Process inputs. var inputs = new List <TxIn>(); // Collect all inputs. foreach (TxInList inputsCollection in block.Transactions.Select(x => x.Inputs)) { inputs.AddRange(inputsCollection); } lock (this.lockObject) { var rewindData = new AddressIndexerRewindData() { BlockHash = header.HashBlock.ToString(), BlockHeight = header.Height, SpentOutputs = new List <OutPointData>() }; foreach (TxIn input in inputs) { OutPoint consumedOutput = input.PrevOut; // Ignore coinbase. if (consumedOutput.Hash == uint256.Zero) { continue; } if (!this.outpointsRepository.TryGetOutPointData(consumedOutput, out OutPointData consumedOutputData)) { this.logger.LogError("Missing outpoint data for {0}.", consumedOutput); this.logger.LogTrace("(-)[MISSING_OUTPOINTS_DATA]"); throw new Exception($"Missing outpoint data for {consumedOutput}"); } Money amountSpent = consumedOutputData.Money; rewindData.SpentOutputs.Add(consumedOutputData); // Transactions that don't actually change the balance just bloat the database. if (amountSpent == 0) { continue; } string address = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, new Script(consumedOutputData.ScriptPubKeyBytes)); if (string.IsNullOrEmpty(address)) { // This condition need not be logged, as the address reader should be aware of all possible address formats already. continue; } this.ProcessBalanceChangeLocked(header.Height, address, amountSpent, false); } // Process outputs. foreach (Transaction tx in block.Transactions) { foreach (TxOut txOut in tx.Outputs) { Money amountReceived = txOut.Value; // Transactions that don't actually change the balance just bloat the database. if (amountReceived == 0 || txOut.IsEmpty || txOut.ScriptPubKey.IsUnspendable) { continue; } string address = this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, txOut.ScriptPubKey); if (string.IsNullOrEmpty(address)) { // This condition need not be logged, as the address reader should be aware of all // possible address formats already. continue; } this.ProcessBalanceChangeLocked(header.Height, address, amountReceived, true); } } this.outpointsRepository.RecordRewindData(rewindData); int purgeRewindDataThreshold = Math.Min(this.consensusManager.Tip.Height - this.compactionTriggerDistance, this.lastSavedHeight); if ((this.dateTimeProvider.GetUtcNow() - this.lastPurgeTime).TotalSeconds > PurgeIntervalSeconds) { this.outpointsRepository.PurgeOldRewindData(purgeRewindDataThreshold); this.lastPurgeTime = this.dateTimeProvider.GetUtcNow(); } // Remove outpoints that were consumed. foreach (OutPoint consumedOutPoint in inputs.Where(x => x.PrevOut.Hash != uint256.Zero).Select(x => x.PrevOut)) { this.outpointsRepository.RemoveOutPointData(consumedOutPoint); } } this.logger.LogTrace("Block processed."); return(true); }