/// <inheritdoc /> public override Task SaveChangesAsync(IEnumerable <UnspentOutputs> unspentOutputs, IEnumerable <TxOut[]> originalOutputs, uint256 oldBlockHash, uint256 nextBlockHash) { this.logger.LogTrace("({0}.Count():{1},{2}.Count():{3},{4}:'{5}',{6}:'{7}')", nameof(unspentOutputs), unspentOutputs?.Count(), nameof(originalOutputs), originalOutputs?.Count(), nameof(oldBlockHash), oldBlockHash, nameof(nextBlockHash), nextBlockHash); RewindData rewindData = originalOutputs != null ? new RewindData(oldBlockHash) : null; int insertedEntities = 0; List <UnspentOutputs> all = unspentOutputs.ToList(); Dictionary <uint256, TxOut[]> unspentToOriginal = new Dictionary <uint256, TxOut[]>(all.Count); using (new StopwatchDisposable(o => this.PerformanceCounter.AddInsertTime(o))) { if (originalOutputs != null) { IEnumerator <TxOut[]> originalEnumerator = originalOutputs.GetEnumerator(); foreach (UnspentOutputs output in all) { originalEnumerator.MoveNext(); unspentToOriginal.Add(output.TransactionId, originalEnumerator.Current); } } } Task task = Task.Run(() => { this.logger.LogTrace("()"); using (DBreeze.Transactions.Transaction transaction = this.dbreeze.GetTransaction()) { transaction.ValuesLazyLoadingIsOn = false; transaction.SynchronizeTables("BlockHash", "Coins", "Rewind"); transaction.Technical_SetTable_OverwriteIsNotAllowed("Coins"); using (new StopwatchDisposable(o => this.PerformanceCounter.AddInsertTime(o))) { uint256 current = this.GetCurrentHash(transaction); if (current != oldBlockHash) { this.logger.LogTrace("(-)[BLOCKHASH_MISMATCH]"); throw new InvalidOperationException("Invalid oldBlockHash"); } this.SetBlockHash(transaction, nextBlockHash); all.Sort(UnspentOutputsComparer.Instance); foreach (UnspentOutputs coin in all) { this.logger.LogTrace("Outputs of transaction ID '{0}' are {1} and will be {2} to the database.", coin.TransactionId, coin.IsPrunable ? "PRUNABLE" : "NOT PRUNABLE", coin.IsPrunable ? "removed" : "inserted"); if (coin.IsPrunable) { transaction.RemoveKey("Coins", coin.TransactionId.ToBytes(false)); } else { transaction.Insert("Coins", coin.TransactionId.ToBytes(false), coin.ToCoins()); } if (originalOutputs != null) { TxOut[] original = null; unspentToOriginal.TryGetValue(coin.TransactionId, out original); if (original == null) { // This one haven't existed before, if we rewind, delete it. rewindData.TransactionsToRemove.Add(coin.TransactionId); } else { // We'll need to restore the original outputs. UnspentOutputs clone = coin.Clone(); clone._Outputs = original.ToArray(); rewindData.OutputsToRestore.Add(clone); } } } if (rewindData != null) { int nextRewindIndex = this.GetRewindIndex(transaction) + 1; this.logger.LogTrace("Rewind state #{0} created.", nextRewindIndex); transaction.Insert("Rewind", nextRewindIndex, rewindData); } insertedEntities += all.Count; transaction.Commit(); } } this.PerformanceCounter.AddInsertedEntities(insertedEntities); this.logger.LogTrace("(-)"); }); this.logger.LogTrace("(-)"); return(task); }
/// <inheritdoc /> public Task SaveChangesAsync(IList <UnspentOutputs> unspentOutputs, IEnumerable <TxOut[]> originalOutputs, uint256 oldBlockHash, uint256 nextBlockHash, int height, List <RewindData> rewindDataList = null) { Task task = Task.Run(() => { int insertedEntities = 0; using (DBreeze.Transactions.Transaction transaction = this.CreateTransaction()) { transaction.ValuesLazyLoadingIsOn = false; transaction.SynchronizeTables("BlockHash", "Coins", "Rewind"); // Speed can degrade when keys are in random order and, especially, if these keys have high entropy. // This settings helps with speed, see dBreeze documentations about details. // We should double check if this settings help in our scenario, or sorting keys and operations is enough. // Refers to issue #2483. https://github.com/stratisproject/StratisBitcoinFullNode/issues/2483 transaction.Technical_SetTable_OverwriteIsNotAllowed("Coins"); using (new StopwatchDisposable(o => this.performanceCounter.AddInsertTime(o))) { uint256 current = this.GetTipHash(transaction); if (current != oldBlockHash) { this.logger.LogTrace("(-)[BLOCKHASH_MISMATCH]"); throw new InvalidOperationException("Invalid oldBlockHash"); } this.SetBlockHash(transaction, nextBlockHash); // Here we'll add items to be inserted in a second pass. List <UnspentOutputs> toInsert = new List <UnspentOutputs>(); foreach (var coin in unspentOutputs.OrderBy(utxo => utxo.TransactionId, new UInt256Comparer())) { if (coin.IsPrunable) { this.logger.LogTrace("Outputs of transaction ID '{0}' are prunable and will be removed from the database.", coin.TransactionId); transaction.RemoveKey("Coins", coin.TransactionId.ToBytes(false)); } else { // Add the item to another list that will be used in the second pass. // This is for performance reasons: dBreeze is optimized to run the same kind of operations, sorted. toInsert.Add(coin); } } for (int i = 0; i < toInsert.Count; i++) { var coin = toInsert[i]; this.logger.LogTrace("Outputs of transaction ID '{0}' are NOT PRUNABLE and will be inserted into the database. {1}/{2}.", coin.TransactionId, i, toInsert.Count); transaction.Insert("Coins", coin.TransactionId.ToBytes(false), coin.ToCoins()); } if (rewindDataList != null) { int nextRewindIndex = this.GetRewindIndex(transaction) + 1; foreach (RewindData rewindData in rewindDataList) { this.logger.LogTrace("Rewind state #{0} created.", nextRewindIndex); transaction.Insert("Rewind", nextRewindIndex, rewindData); nextRewindIndex++; } } insertedEntities += unspentOutputs.Count; transaction.Commit(); } } this.performanceCounter.AddInsertedEntities(insertedEntities); }); return(task); }