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 { })
示例#2
0
        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.