Пример #1
0
        /// <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);
        }
Пример #2
0
        /// <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);
        }