public async Task <FileContentResult> Sync( [ModelBinder(BinderType = typeof(DestinationModelBinder))] DerivationStrategyBase extPubKey, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 confHash = null, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 unconfHash = null, bool noWait = false) { if (extPubKey == null) { throw new ArgumentNullException(nameof(extPubKey)); } var waitingTransaction = noWait ? Task.FromResult(false) : WaitingTransaction(extPubKey); UTXOChanges changes = null; var getKeyPaths = GetKeyPaths(extPubKey); var matchScript = MatchKeyPaths(getKeyPaths); while (true) { changes = new UTXOChanges(); changes.CurrentHeight = Chain.Height; var transactions = GetAnnotatedTransactions(extPubKey); var unconf = transactions.Where(tx => tx.Height == MempoolHeight); var conf = transactions.Where(tx => tx.Height != MempoolHeight); conf = conf.TopologicalSort(DependsOn(conf.ToList())).ToList(); unconf = unconf.OrderByDescending(t => t.Record.Inserted) .TopologicalSort(DependsOn(unconf.ToList())).ToList(); var states = UTXOStateResult.CreateStates(matchScript, unconfHash, unconf.Select(c => c.Record.Transaction), confHash, conf.Select(c => c.Record.Transaction)); var conflicted = states.Unconfirmed.Actual.Conflicts .SelectMany(c => c.Value) .SelectMany(txid => transactions.GetByTxId(txid)) .Where(a => a.Height == MempoolHeight) .Select(a => a.Record) .Distinct() .ToList(); if (conflicted.Count != 0) { Logs.Explorer.LogInformation($"Clean {conflicted.Count} conflicted transactions"); if (Logs.Explorer.IsEnabled(LogLevel.Debug)) { foreach (var conflict in conflicted) { Logs.Explorer.LogDebug($"Transaction {conflict.Transaction.GetHash()} is conflicted"); } } Repository.CleanTransactions(extPubKey, conflicted); } changes.Confirmed = SetUTXOChange(states.Confirmed); changes.Unconfirmed = SetUTXOChange(states.Unconfirmed, states.Confirmed.Actual); FillUTXOsInformation(changes.Confirmed.UTXOs, getKeyPaths, transactions, changes.CurrentHeight); FillUTXOsInformation(changes.Unconfirmed.UTXOs, getKeyPaths, transactions, changes.CurrentHeight); if (changes.HasChanges || !(await waitingTransaction)) { break; } waitingTransaction = Task.FromResult(false); //next time, will not wait } return(new FileContentResult(changes.ToBytes(), "application/octet-stream")); }
public async Task <FileContentResult> Sync( [ModelBinder(BinderType = typeof(DestinationModelBinder))] BitcoinExtPubKey extPubKey, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 lastBlockHash = null, [ModelBinder(BinderType = typeof(UInt256ModelBinding))] uint256 unconfirmedHash = null, bool noWait = false) { lastBlockHash = lastBlockHash ?? uint256.Zero; var actualLastBlockHash = uint256.Zero; var waitingTransaction = noWait ? Task.FromResult(false) : WaitingTransaction(extPubKey); Runtime.Repository.MarkAsUsed(new KeyInformation(extPubKey)); UTXOChanges changes = null; UTXOChanges previousChanges = null; List <TrackedTransaction> cleanList = null; var getKeyPath = GetKeyPaths(extPubKey); while (true) { cleanList = new List <TrackedTransaction>(); HashSet <uint256> conflictedUnconf = new HashSet <uint256>(); changes = new UTXOChanges(); List <AnnotatedTransaction> transactions = GetAnnotatedTransactions(extPubKey); var unconf = transactions.Where(tx => tx.Height == MempoolHeight); var conf = transactions.Where(tx => tx.Height != MempoolHeight); conf = conf.TopologicalSort(DependsOn(conf.ToList())).ToList(); unconf = unconf.TopologicalSort(DependsOn(unconf.ToList())).ToList(); foreach (var item in conf.Concat(unconf)) { var record = item.Record; if (record.BlockHash == null) { if ( //A parent conflicted with the current utxo record.Transaction.Inputs.Any(i => conflictedUnconf.Contains(i.PrevOut.Hash)) || //Conflict with the confirmed utxo changes.Confirmed.HasConflict(record.Transaction)) { cleanList.Add(record); conflictedUnconf.Add(record.Transaction.GetHash()); continue; } if (changes.Unconfirmed.HasConflict(record.Transaction)) { Logs.Explorer.LogInformation($"Conflicts in the mempool. {record.Transaction.GetHash()} ignored"); continue; } changes.Unconfirmed.LoadChanges(record.Transaction, getKeyPath); } else { if (changes.Confirmed.HasConflict(record.Transaction)) { Logs.Explorer.LogError("A conflict among confirmed transaction happened, this should be impossible"); throw new InvalidOperationException("The impossible happened"); } changes.Unconfirmed.LoadChanges(record.Transaction, getKeyPath); changes.Confirmed.LoadChanges(record.Transaction, getKeyPath); changes.Confirmed.Hash = record.BlockHash; actualLastBlockHash = record.BlockHash; if (record.BlockHash == lastBlockHash) { previousChanges = changes.Clone(); } } } changes.Unconfirmed = changes.Unconfirmed.Diff(changes.Confirmed); changes.Unconfirmed.Hash = changes.Unconfirmed.GetHash(); if (changes.Unconfirmed.Hash == unconfirmedHash) { changes.Unconfirmed.Clear(); } else { changes.Unconfirmed.Reset = true; } if (actualLastBlockHash == lastBlockHash) { changes.Confirmed.Clear(); } else if (previousChanges != null) { changes.Confirmed.Reset = false; changes.Confirmed = changes.Confirmed.Diff(previousChanges.Confirmed); } else { changes.Confirmed.Reset = true; changes.Confirmed.SpentOutpoints.Clear(); } if (changes.HasChanges || !(await waitingTransaction)) { break; } waitingTransaction = Task.FromResult(false); //next time, will not wait } Runtime.Repository.CleanTransactions(extPubKey.ExtPubKey, cleanList); return(new FileContentResult(changes.ToBytes(), "application/octet-stream")); }