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);
}
}
}
}