BitSharper.Wallet.Reorganize C# (CSharp) Méthode

Reorganize() private méthode

Called by the BlockChain when the best chain (representing total work done) has changed. In this case, we need to go through our transactions and find out if any have become invalid. It's possible for our balance to go down in this case: money we thought we had can suddenly vanish if the rest of the network agrees it should be so.
The oldBlocks/newBlocks lists are ordered height-wise from top first to bottom last.
private Reorganize ( IList oldBlocks, IList newBlocks ) : void
oldBlocks IList
newBlocks IList
Résultat void
        internal void Reorganize(IList<StoredBlock> oldBlocks, IList<StoredBlock> newBlocks)
        {
            lock (this)
            {
                // This runs on any peer thread with the block chain synchronized.
                //
                // The reorganize functionality of the wallet is tested in ChainSplitTests.
                //
                // For each transaction we track which blocks they appeared in. Once a re-org takes place we have to find all
                // transactions in the old branch, all transactions in the new branch and find the difference of those sets.
                //
                // receive() has been called on the block that is triggering the re-org before this is called.

                _log.Info("  Old part of chain (top to bottom):");
                foreach (var b in oldBlocks) _log.InfoFormat("    {0}", b.Header.HashAsString);
                _log.InfoFormat("  New part of chain (top to bottom):");
                foreach (var b in newBlocks) _log.InfoFormat("    {0}", b.Header.HashAsString);

                // Transactions that appear in the old chain segment.
                IDictionary<Sha256Hash, Transaction> oldChainTransactions = new Dictionary<Sha256Hash, Transaction>();
                // Transactions that appear in the old chain segment and NOT the new chain segment.
                IDictionary<Sha256Hash, Transaction> onlyOldChainTransactions = new Dictionary<Sha256Hash, Transaction>();
                // Transactions that appear in the new chain segment.
                IDictionary<Sha256Hash, Transaction> newChainTransactions = new Dictionary<Sha256Hash, Transaction>();
                // Transactions that don't appear in either the new or the old section, ie, the shared trunk.
                IDictionary<Sha256Hash, Transaction> commonChainTransactions = new Dictionary<Sha256Hash, Transaction>();

                IDictionary<Sha256Hash, Transaction> all = new Dictionary<Sha256Hash, Transaction>();
                foreach (var pair in Unspent.Concat(Spent).Concat(_inactive))
                {
                    all[pair.Key] = pair.Value;
                }
                foreach (var tx in all.Values)
                {
                    var appearsIn = tx.AppearsIn;
                    Debug.Assert(appearsIn != null);
                    // If the set of blocks this transaction appears in is disjoint with one of the chain segments it means
                    // the transaction was never incorporated by a miner into that side of the chain.
                    var inOldSection = appearsIn.Any(oldBlocks.Contains) || oldBlocks.Any(appearsIn.Contains);
                    var inNewSection = appearsIn.Any(newBlocks.Contains) || newBlocks.Any(appearsIn.Contains);
                    var inCommonSection = !inNewSection && !inOldSection;

                    if (inCommonSection)
                    {
                        Debug.Assert(!commonChainTransactions.ContainsKey(tx.Hash), "Transaction appears twice in common chain segment");
                        commonChainTransactions[tx.Hash] = tx;
                    }
                    else
                    {
                        if (inOldSection)
                        {
                            Debug.Assert(!oldChainTransactions.ContainsKey(tx.Hash), "Transaction appears twice in old chain segment");
                            oldChainTransactions[tx.Hash] = tx;
                            if (!inNewSection)
                            {
                                Debug.Assert(!onlyOldChainTransactions.ContainsKey(tx.Hash), "Transaction appears twice in only-old map");
                                onlyOldChainTransactions[tx.Hash] = tx;
                            }
                        }
                        if (inNewSection)
                        {
                            Debug.Assert(!newChainTransactions.ContainsKey(tx.Hash), "Transaction appears twice in new chain segment");
                            newChainTransactions[tx.Hash] = tx;
                        }
                    }
                }

                // If there is no difference it means we have nothing we need to do and the user does not care.
                var affectedUs = oldChainTransactions.Count != newChainTransactions.Count ||
                                 !oldChainTransactions.All(
                                     item =>
                                     {
                                         Transaction rightValue;
                                         return newChainTransactions.TryGetValue(item.Key, out rightValue) && Equals(item.Value, rightValue);
                                     });
                _log.Info(affectedUs ? "Re-org affected our transactions" : "Re-org had no effect on our transactions");
                if (!affectedUs) return;

                // For simplicity we will reprocess every transaction to ensure it's in the right bucket and has the right
                // connections. Attempting to update each one with minimal work is possible but complex and was leading to
                // edge cases that were hard to fix. As re-orgs are rare the amount of work this implies should be manageable
                // unless the user has an enormous wallet. As an optimization fully spent transactions buried deeper than
                // 1000 blocks could be put into yet another bucket which we never touch and assume re-orgs cannot affect.

                foreach (var tx in onlyOldChainTransactions.Values) _log.InfoFormat("  Only Old: {0}", tx.HashAsString);
                foreach (var tx in oldChainTransactions.Values) _log.InfoFormat("  Old: {0}", tx.HashAsString);
                foreach (var tx in newChainTransactions.Values) _log.InfoFormat("  New: {0}", tx.HashAsString);

                // Break all the existing connections.
                foreach (var tx in all.Values)
                    tx.DisconnectInputs();
                foreach (var tx in Pending.Values)
                    tx.DisconnectInputs();
                // Reconnect the transactions in the common part of the chain.
                foreach (var tx in commonChainTransactions.Values)
                {
                    var badInput = tx.ConnectForReorganize(all);
                    Debug.Assert(badInput == null, "Failed to connect " + tx.HashAsString + ", " + badInput);
                }
                // Recalculate the unspent/spent buckets for the transactions the re-org did not affect.
                Unspent.Clear();
                Spent.Clear();
                _inactive.Clear();
                foreach (var tx in commonChainTransactions.Values)
                {
                    var unspentOutputs = 0;
                    foreach (var output in tx.Outputs)
                    {
                        if (output.IsAvailableForSpending) unspentOutputs++;
                    }
                    if (unspentOutputs > 0)
                    {
                        _log.InfoFormat("  TX {0}: ->unspent", tx.HashAsString);
                        Unspent[tx.Hash] = tx;
                    }
                    else
                    {
                        _log.InfoFormat("  TX {0}: ->spent", tx.HashAsString);
                        Spent[tx.Hash] = tx;
                    }
                }
                // Now replay the act of receiving the blocks that were previously in a side chain. This will:
                //   - Move any transactions that were pending and are now accepted into the right bucket.
                //   - Connect the newly active transactions.
                foreach (var b in newBlocks.Reverse()) // Need bottom-to-top but we get top-to-bottom.
                {
                    _log.InfoFormat("Replaying block {0}", b.Header.HashAsString);
                    ICollection<Transaction> txns = new HashSet<Transaction>();
                    foreach (var tx in newChainTransactions.Values)
                    {
                        if (tx.AppearsIn.Contains(b))
                        {
                            txns.Add(tx);
                            _log.InfoFormat("  containing tx {0}", tx.HashAsString);
                        }
                    }
                    foreach (var t in txns)
                    {
                        Receive(t, b, BlockChain.NewBlockType.BestChain, true);
                    }
                }

                // Find the transactions that didn't make it into the new chain yet. For each input, try to connect it to the
                // transactions that are in {spent,unspent,pending}. Check the status of each input. For inactive
                // transactions that only send us money, we put them into the inactive pool where they sit around waiting for
                // another re-org or re-inclusion into the main chain. For inactive transactions where we spent money we must
                // put them back into the pending pool if we can reconnect them, so we don't create a double spend whilst the
                // network heals itself.
                IDictionary<Sha256Hash, Transaction> pool = new Dictionary<Sha256Hash, Transaction>();
                foreach (var pair in Unspent.Concat(Spent).Concat(Pending))
                {
                    pool[pair.Key] = pair.Value;
                }
                IDictionary<Sha256Hash, Transaction> toReprocess = new Dictionary<Sha256Hash, Transaction>();
                foreach (var pair in onlyOldChainTransactions.Concat(Pending))
                {
                    toReprocess[pair.Key] = pair.Value;
                }
                _log.Info("Reprocessing:");
                // Note, we must reprocess dead transactions first. The reason is that if there is a double spend across
                // chains from our own coins we get a complicated situation:
                //
                // 1) We switch to a new chain (B) that contains a double spend overriding a pending transaction. It goes dead.
                // 2) We switch BACK to the first chain (A). The dead transaction must go pending again.
                // 3) We resurrect the transactions that were in chain (B) and assume the miners will start work on putting them
                //    in to the chain, but it's not possible because it's a double spend. So now that transaction must become
                //    dead instead of pending.
                //
                // This only occurs when we are double spending our own coins.
                foreach (var tx in _dead.Values.ToList())
                {
                    ReprocessTxAfterReorg(pool, tx);
                }
                foreach (var tx in toReprocess.Values)
                {
                    ReprocessTxAfterReorg(pool, tx);
                }

                _log.InfoFormat("post-reorg balance is {0}", Utils.BitcoinValueToFriendlyString(GetBalance()));

                // Inform event listeners that a re-org took place.
                if (Reorganized != null)
                {
                    // Synchronize on the event listener as well. This allows a single listener to handle events from
                    // multiple wallets without needing to worry about being thread safe.
                    lock (Reorganized)
                    {
                        Reorganized(this, EventArgs.Empty);
                    }
                }
            }
        }