/// <summary>Adds a new balance change entry to to the <see cref="addressIndexRepository"/>.</summary> /// <remarks>Should be protected by <see cref="lockObject"/>.</remarks> private void ProcessBalanceChangeLocked(int height, string address, Money amount, bool deposited) { AddressIndexerData indexData = this.addressIndexRepository.GetOrCreateAddress(address); // Record new balance change into the address index data. indexData.BalanceChanges.Add(new AddressBalanceChange() { BalanceChangedHeight = height, Satoshi = amount.Satoshi, Deposited = deposited }); // Anything less than that should be compacted. int heightThreshold = this.consensusManager.Tip.Height - (int)this.network.Consensus.MaxReorgLength; bool compact = (this.network.Consensus.MaxReorgLength != 0) && (indexData.BalanceChanges.Count > CompactingThreshold) && (indexData.BalanceChanges[1].BalanceChangedHeight < heightThreshold); if (!compact) { this.logger.LogTrace("(-)[TOO_FEW_CHANGE_RECORDS]"); return; } var compacted = new List <AddressBalanceChange>(CompactingThreshold / 2) { new AddressBalanceChange() { BalanceChangedHeight = 0, Satoshi = 0, Deposited = true } }; for (int i = 0; i < indexData.BalanceChanges.Count; i++) { AddressBalanceChange change = indexData.BalanceChanges[i]; if (change.BalanceChangedHeight < heightThreshold) { if (change.Deposited) { compacted[0].Satoshi += change.Satoshi; } else { compacted[0].Satoshi -= change.Satoshi; } } else if (i < indexData.BalanceChanges.Count - 1) { compacted.AddRange(indexData.BalanceChanges.Skip(i + 1)); break; } } indexData.BalanceChanges = compacted; this.addressIndexRepository.AddOrUpdate(indexData.Address, indexData, indexData.BalanceChanges.Count + 1); }
public LastBalanceDecreaseTransactionModel GetLastBalanceDecreaseTransaction(string address) { if (address == null) { return(null); } (bool isQueryable, string reason) = this.IsQueryable(); if (!isQueryable) { return(null); } int lastBalanceHeight; lock (this.lockObject) { AddressIndexerData indexData = this.addressIndexRepository.GetOrCreateAddress(address); AddressBalanceChange lastBalanceUpdate = indexData.BalanceChanges.Where(a => !a.Deposited).OrderByDescending(b => b.BalanceChangedHeight).FirstOrDefault(); if (lastBalanceUpdate == null) { return(null); } lastBalanceHeight = lastBalanceUpdate.BalanceChangedHeight; } // Height 0 is used as a placeholder height for compacted address balance records, so ignore them if they are the only record. if (lastBalanceHeight == 0) { return(null); } ChainedHeader header = this.chainIndexer.GetHeader(lastBalanceHeight); if (header == null) { return(null); } Block block = this.consensusManager.GetBlockData(header.HashBlock).Block; if (block == null) { return(null); } // Get the UTXO snapshot as of one block lower than the last balance change, so that we are definitely able to look up the inputs of each transaction in the next block. ReconstructedCoinviewContext utxos = this.utxoIndexer.GetCoinviewAtHeight(lastBalanceHeight - 1); Transaction foundTransaction = null; foreach (Transaction transaction in block.Transactions) { if (transaction.IsCoinBase) { continue; } foreach (TxIn txIn in transaction.Inputs) { Transaction prevTx = utxos.Transactions[txIn.PrevOut.Hash]; foreach (TxOut txOut in prevTx.Outputs) { if (this.scriptAddressReader.GetAddressFromScriptPubKey(this.network, txOut.ScriptPubKey) == address) { foundTransaction = transaction; } } } } return(foundTransaction == null ? null : new LastBalanceDecreaseTransactionModel() { BlockHeight = lastBalanceHeight, Transaction = new TransactionVerboseModel(foundTransaction, this.network) }); }
/// <summary>Adds a new balance change entry to to the <see cref="addressIndexRepository"/>.</summary> /// <param name="height">The height of the block this being processed.</param> /// <param name="address">The address receiving the funds.</param> /// <param name="amount">The amount being received.</param> /// <param name="deposited"><c>false</c> if this is an output being spent, <c>true</c> otherwise.</param> /// <remarks>Should be protected by <see cref="lockObject"/>.</remarks> private void ProcessBalanceChangeLocked(int height, string address, Money amount, bool deposited) { AddressIndexerData indexData = this.addressIndexRepository.GetOrCreateAddress(address); // Record new balance change into the address index data. indexData.BalanceChanges.Add(new AddressBalanceChange() { BalanceChangedHeight = height, Satoshi = amount.Satoshi, Deposited = deposited }); // Anything less than that should be compacted. int heightThreshold = this.consensusManager.Tip.Height - this.compactionTriggerDistance; bool compact = (indexData.BalanceChanges.Count > this.compactionThreshold) && (indexData.BalanceChanges[1].BalanceChangedHeight < heightThreshold); if (!compact) { this.addressIndexRepository.AddOrUpdate(indexData.Address, indexData, indexData.BalanceChanges.Count + 1); this.logger.LogTrace("(-)[TOO_FEW_CHANGE_RECORDS]"); return; } var compacted = new List <AddressBalanceChange>(this.compactionThreshold / 2) { new AddressBalanceChange() { BalanceChangedHeight = 0, Satoshi = 0, Deposited = true } }; for (int i = 0; i < indexData.BalanceChanges.Count; i++) { AddressBalanceChange change = indexData.BalanceChanges[i]; if ((change.BalanceChangedHeight) < heightThreshold && i < this.compactionAmount) { this.logger.LogDebug("Balance change: {0} was selected for compaction. Compacted balance now: {1}.", change, compacted[0].Satoshi); if (change.Deposited) { compacted[0].Satoshi += change.Satoshi; } else { compacted[0].Satoshi -= change.Satoshi; } this.logger.LogDebug("New compacted balance: {0}.", compacted[0].Satoshi); } else { compacted.Add(change); } } indexData.BalanceChanges = compacted; this.addressIndexRepository.AddOrUpdate(indexData.Address, indexData, indexData.BalanceChanges.Count + 1); }