private ProcessedResult ProcessNoLock(SmartTransaction tx) { var result = new ProcessedResult(tx); // We do not care about non-witness transactions for other than mempool cleanup. if (tx.Transaction.PossiblyP2WPKHInvolved()) { uint256 txId = tx.GetHash(); // Performance ToDo: txids could be cached in a hashset here by the AllCoinsView and then the contains would be fast. if (!tx.Transaction.IsCoinBase && !Coins.AsAllCoinsView().CreatedBy(txId).Any()) // Transactions we already have and processed would be "double spends" but they shouldn't. { var doubleSpends = new List <SmartCoin>(); foreach (var txin in tx.Transaction.Inputs) { if (Coins.TryGetSpenderSmartCoinsByOutPoint(txin.PrevOut, out var coins)) { doubleSpends.AddRange(coins); } } if (doubleSpends.Any()) { if (tx.Height == Height.Mempool) { // if the received transaction is spending at least one input already // spent by a previous unconfirmed transaction signaling RBF then it is not a double // spending transaction but a replacement transaction. var isReplacementTx = doubleSpends.Any(x => x.IsReplaceable && !x.Confirmed); if (isReplacementTx) { // Undo the replaced transaction by removing the coins it created (if other coin // spends it, remove that too and so on) and restoring those that it replaced. // After undoing the replaced transaction it will process the replacement transaction. var replacedTxId = doubleSpends.First().TransactionId; var(replaced, restored) = Coins.Undo(replacedTxId); result.ReplacedCoins.AddRange(replaced); result.RestoredCoins.AddRange(restored); foreach (var replacedTransactionId in replaced.Select(coin => coin.TransactionId)) { TransactionStore.MempoolStore.TryRemove(replacedTransactionId, out _); } tx.SetReplacement(); } else { return(result); } } else // new confirmation always enjoys priority { // remove double spent coins recursively (if other coin spends it, remove that too and so on), will add later if they came to our keys foreach (SmartCoin doubleSpentCoin in doubleSpends) { Coins.Remove(doubleSpentCoin); } result.SuccessfullyDoubleSpentCoins.AddRange(doubleSpends); var unconfirmedDoubleSpentTxId = doubleSpends.First().TransactionId; TransactionStore.MempoolStore.TryRemove(unconfirmedDoubleSpentTxId, out _); } } } List <SmartCoin> spentOwnCoins = null; for (var i = 0U; i < tx.Transaction.Outputs.Count; i++) { // If transaction received to any of the wallet keys: var output = tx.Transaction.Outputs[i]; HdPubKey foundKey = KeyManager.GetKeyForScriptPubKey(output.ScriptPubKey); if (foundKey is { })
private ProcessedResult ProcessNoLock(SmartTransaction tx) { var result = new ProcessedResult(tx); // We do not care about non-witness transactions for other than mempool cleanup. if (tx.Transaction.PossiblyP2WPKHInvolved()) { uint256 txId = tx.GetHash(); // Performance ToDo: txids could be cached in a hashset here by the AllCoinsView and then the contains would be fast. if (!tx.Transaction.IsCoinBase && !Coins.AsAllCoinsView().CreatedBy(txId).Any()) // Transactions we already have and processed would be "double spends" but they shouldn't. { var doubleSpends = new List <SmartCoin>(); foreach (var txin in tx.Transaction.Inputs) { if (Coins.TryGetSpenderSmartCoinsByOutPoint(txin.PrevOut, out var coins)) { doubleSpends.AddRange(coins); } } if (doubleSpends.Any()) { if (tx.Height == Height.Mempool) { // if the received transaction is spending at least one input already // spent by a previous unconfirmed transaction signaling RBF then it is not a double // spending transaction but a replacement transaction. var isReplacemenetTx = doubleSpends.Any(x => x.IsReplaceable && !x.Confirmed); if (isReplacemenetTx) { // Undo the replaced transaction by removing the coins it created (if other coin // spends it, remove that too and so on) and restoring those that it replaced. // After undoing the replaced transaction it will process the replacement transaction. var replacedTxId = doubleSpends.First().TransactionId; var(replaced, restored) = Coins.Undo(replacedTxId); result.ReplacedCoins.AddRange(replaced); result.RestoredCoins.AddRange(restored); foreach (var replacedTransactionId in replaced.Select(coin => coin.TransactionId)) { TransactionStore.MempoolStore.TryRemove(replacedTransactionId, out _); } tx.SetReplacement(); } else { return(result); } } else // new confirmation always enjoys priority { // remove double spent coins recursively (if other coin spends it, remove that too and so on), will add later if they came to our keys foreach (SmartCoin doubleSpentCoin in doubleSpends) { Coins.Remove(doubleSpentCoin); } result.SuccessfullyDoubleSpentCoins.AddRange(doubleSpends); var unconfirmedDoubleSpentTxId = doubleSpends.First().TransactionId; TransactionStore.MempoolStore.TryRemove(unconfirmedDoubleSpentTxId, out _); } } } List <SmartCoin> spentOwnCoins = null; for (var i = 0U; i < tx.Transaction.Outputs.Count; i++) { // If transaction received to any of the wallet keys: var output = tx.Transaction.Outputs[i]; HdPubKey foundKey = KeyManager.GetKeyForScriptPubKey(output.ScriptPubKey); if (foundKey != default) { if (output.Value <= DustThreshold) { result.ReceivedDusts.Add(output); continue; } foundKey.SetKeyState(KeyState.Used, KeyManager); spentOwnCoins ??= Coins.OutPoints(tx.Transaction.Inputs.ToTxoRefs()).ToList(); var anonset = tx.Transaction.GetAnonymitySet(i); if (spentOwnCoins.Count != 0) { anonset += spentOwnCoins.Min(x => x.AnonymitySet) - 1; // Minus 1, because do not count own. } SmartCoin newCoin = new SmartCoin(txId, i, output.ScriptPubKey, output.Value, tx.Transaction.Inputs.ToTxoRefs().ToArray(), tx.Height, tx.IsRBF, anonset, foundKey.Label, spenderTransactionId: null, false, pubKey: foundKey); // Do not inherit locked status from key, that's different. result.ReceivedCoins.Add(newCoin); // If we did not have it. if (Coins.TryAdd(newCoin)) { result.NewlyReceivedCoins.Add(newCoin); // Make sure there's always 21 clean keys generated and indexed. KeyManager.AssertCleanKeysIndexed(isInternal: foundKey.IsInternal); if (foundKey.IsInternal) { // Make sure there's always 14 internal locked keys generated and indexed. KeyManager.AssertLockedInternalKeysIndexed(14); } } else // If we had this coin already. { if (newCoin.Height != Height.Mempool) // Update the height of this old coin we already had. { SmartCoin oldCoin = Coins.AsAllCoinsView().GetByOutPoint(new OutPoint(txId, i)); if (oldCoin is { }) // Just to be sure, it is a concurrent collection.