/// <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); } }
/// <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) { 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 excluding coinbases. foreach (TxInList 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 (TxIn input in inputs) { OutPoint consumedOutput = input.PrevOut; 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.Select(x => x.PrevOut)) { this.outpointsRepository.RemoveOutPointData(consumedOutPoint); } } return(true); }
/// <summary>Persists rewind data into the repository.</summary> /// <param name="rewindData">The data to be persisted.</param> public void RecordRewindData(AddressIndexerRewindData rewindData) { this.addressIndexerRewindData.Upsert(rewindData); }