/// <summary>Persists rewind data into the repository.</summary>
 /// <param name="rewindData">The data to be persisted.</param>
 public void RecordRewindData(AddressIndexerRewindData rewindData)
 {
     lock (this.LockObject)
     {
         this.addressIndexerRewindData.Upsert(rewindData);
     }
 }
Esempio n. 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>
        bool ProcessBlock(Block block, ChainedHeader header)
        {
            lock (this.lockObject)
            {
                // Record outpoints.
                foreach (var tx in block.Transactions)
                {
                    for (var 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 excluding coinbases.
            foreach (var inputsCollection in block.Transactions.Where(x => !x.IsCoinBase).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 (var input in inputs)
                {
                    var consumedOutput = input.PrevOut;

                    if (!this.outpointsRepository.TryGetOutPointData(consumedOutput, out var 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;
                    }

                    var 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;
                    }

                    ProcessBalanceChangeLocked(header.Height, address, amountSpent, false);
                }

                // Process outputs.
                foreach (var tx in block.Transactions)
                {
                    foreach (var txOut in tx.Outputs)
                    {
                        var amountReceived = txOut.Value;

                        // Transactions that don't actually change the balance just bloat the database.
                        if (amountReceived == 0 || txOut.IsEmpty || txOut.ScriptPubKey.IsUnspendable)
                        {
                            continue;
                        }

                        var 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;
                        }

                        ProcessBalanceChangeLocked(header.Height, address, amountReceived, true);
                    }
                }

                this.outpointsRepository.RecordRewindData(rewindData);

                var 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 (var consumedOutPoint in inputs.Select(x => x.PrevOut))
                {
                    this.outpointsRepository.RemoveOutPointData(consumedOutPoint);
                }
            }

            return(true);
        }