private async void TransactionProcessor_WalletRelevantTransactionProcessedAsync(object sender, ProcessedResult e) { try { foreach (var coin in e.NewlySpentCoins.Concat(e.ReplacedCoins).Concat(e.SuccessfullyDoubleSpentCoins).Distinct()) { ChaumianClient.ExposedLinks.TryRemove(coin.GetTxoRef(), out _); } } catch (Exception ex) { Logger.LogError(ex); } try { IEnumerable <SmartCoin> newCoins = e.NewlyReceivedCoins.Concat(e.RestoredCoins).Distinct(); if (newCoins.Any()) { if (ChaumianClient.State.Contains(e.Transaction.Transaction.Inputs.Select(x => x.PrevOut.ToTxoRef()))) { var coinsToQueue = new HashSet <SmartCoin>(); foreach (var newCoin in newCoins) { // If it's being mixed and anonset is not sufficient, then queue it. if (newCoin.Unspent && ChaumianClient.HasIngredients && newCoin.AnonymitySet < ServiceConfiguration.MixUntilAnonymitySet) { coinsToQueue.Add(newCoin); } } await ChaumianClient.QueueCoinsToMixAsync(coinsToQueue).ConfigureAwait(false); } } } catch (Exception ex) { Logger.LogError(ex); } }
private void ProcessTransaction(SmartTransaction tx, List <HdPubKey> keys = null) { if (tx.Height != Height.MemPool && tx.Height != Height.Unknown) { MemPool.TransactionHashes.TryRemove(tx.GetHash()); } //iterate tx // if already have the coin // if NOT mempool // update height //if double spend // if mempool // if all double spent coins are mempool and RBF // remove double spent coins(if other coin spends it, remove that too and so on) // will add later if they came to our keys // else // return // 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 //iterate tx // if came to our keys // add coin // If key list is not provided refresh the key list. if (keys == null) { keys = KeyManager.GetKeys().ToList(); } for (var i = 0; i < tx.Transaction.Outputs.Count; i++) { // If we already had it, just update the height. Maybe got from mempool to block or reorged. var foundCoin = Coins.FirstOrDefault(x => x.TransactionId == tx.GetHash() && x.Index == i); if (foundCoin != default) { // If tx height is mempool then don't, otherwise update the height. if (tx.Height != Height.MemPool) { foundCoin.Height = tx.Height; } } } // If double spend: List <SmartCoin> doubleSpends = Coins .Where(x => tx.Transaction.Inputs.Any(y => x.SpentOutputs.Select(z => z.ToOutPoint()).Contains(y.PrevOut))) .ToList(); if (doubleSpends.Any()) { if (tx.Height == Height.MemPool) { // if all double spent coins are mempool and RBF if (doubleSpends.All(x => x.Height == Height.MemPool && x.RBF)) { // remove double spent coins(if other coin spends it, remove that too and so on) // will add later if they came to our keys foreach (var doubleSpentCoin in doubleSpends) { RemoveCoinRecursively(doubleSpentCoin); } } else { return; } } 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 (var doubleSpentCoin in doubleSpends) { RemoveCoinRecursively(doubleSpentCoin); } } } 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 = keys.SingleOrDefault(x => x.GetP2wpkhScript() == output.ScriptPubKey); if (foundKey != default) { foundKey.SetKeyState(KeyState.Used, KeyManager); List <SmartCoin> spentOwnCoins = Coins.Where(x => tx.Transaction.Inputs.Any(y => y.PrevOut.Hash == x.TransactionId && y.PrevOut.N == x.Index)).ToList(); var mixin = tx.Transaction.GetMixin(i); if (spentOwnCoins.Count != 0) { mixin += spentOwnCoins.Min(x => x.Mixin); } var coin = new SmartCoin(tx.GetHash(), i, output.ScriptPubKey, output.Value, tx.Transaction.Inputs.ToTxoRefs().ToArray(), tx.Height, tx.Transaction.RBF, mixin, foundKey.Label, spenderTransactionId: null, locked: false); // Don't inherit locked status from key, that's different. Coins.TryAdd(coin); if (coin.Unspent && coin.Label == "ZeroLink Change" && ChaumianClient.OnePiece != null) { Task.Run(async() => { try { await ChaumianClient.QueueCoinsToMixAsync(ChaumianClient.OnePiece, coin); } catch (Exception ex) { Logger.LogError <WalletService>(ex); } }); } // Make sure there's always 21 clean keys generated and indexed. if (AssertCleanKeysIndexed(21, foundKey.IsInternal())) { // If it generated a new key refresh the keys: keys = KeyManager.GetKeys().ToList(); } } } // If spends any of our coin for (var i = 0; i < tx.Transaction.Inputs.Count; i++) { var input = tx.Transaction.Inputs[i]; var foundCoin = Coins.FirstOrDefault(x => x.TransactionId == input.PrevOut.Hash && x.Index == input.PrevOut.N); if (foundCoin != null) { foundCoin.SpenderTransactionId = tx.GetHash(); CoinSpentOrSpenderConfirmed?.Invoke(this, foundCoin); } } }