private void ValidateReorgEnd( Block <T> oldTip, Block <T> newTip, Block <T> branchpoint) { if (!(BlockChain is BlockChain <T> chain)) { return; } IBlockPolicy <T> policy = chain.Policy; IStore store = chain.Store; InvalidRenderException <T> heterogeneousGenesisError = Error(Records, "Reorg occurred from the chain with different genesis."); List <IAction> expectedUnrenderedActions = new List <IAction>(); BlockHeader header = oldTip.Header; IEnumerable <TxId> txIds = oldTip.Transactions.Select(tx => tx.Id); while (!header.Hash.Equals(branchpoint.Hash)) { if (policy.BlockAction is IAction blockAction) { expectedUnrenderedActions.Add(blockAction); } IEnumerable <Transaction <T> > transactions = txIds.Select(store.GetTransaction <T>); transactions = ActionEvaluator <T> .OrderTxsForEvaluation( header.ProtocolVersion, transactions, header.PreEvaluationHash ); expectedUnrenderedActions.AddRange( transactions.SelectMany(t => t.Actions).Cast <IAction>().Reverse()); BlockDigest prevDigest = store.GetBlockDigest( header.PreviousHash ?? throw heterogeneousGenesisError ) ?? throw Error(Records, $"Failed to load block {header.PreviousHash}."); header = prevDigest.GetHeader(policy.GetHashAlgorithm); txIds = prevDigest.TxIds.Select(b => new TxId(b.ToBuilder().ToArray())); } IEnumerable <IAction> expectedRenderedActionsBuffer = new List <IAction>(); header = newTip.Header; txIds = newTip.Transactions.Select(tx => tx.Id); while (!header.Hash.Equals(branchpoint.Hash)) { IEnumerable <Transaction <T> > transactions = txIds.Select(store.GetTransaction <T>); transactions = ActionEvaluator <T> .OrderTxsForEvaluation( header.ProtocolVersion, transactions, header.PreEvaluationHash ); IEnumerable <IAction> actions = transactions.SelectMany(t => t.Actions).Cast <IAction>(); if (policy.BlockAction is IAction blockAction) { #if NET472 || NET471 || NET47 || NET462 || NET461 // Even though .NET Framework 4.6.1 or higher supports .NET Standard 2.0, // versions lower than 4.8 lacks Enumerable.Append(IEnumerable<T>, T) method. actions = actions.Concat(new IAction[] { blockAction }); #else #pragma warning disable PC002 actions = actions.Append(blockAction); #pragma warning restore PC002 #endif } expectedRenderedActionsBuffer = actions.Concat(expectedRenderedActionsBuffer); BlockDigest prevDigest = store.GetBlockDigest( header.PreviousHash ?? throw heterogeneousGenesisError ) ?? throw Error(Records, $"Failed to load block {header.PreviousHash}."); header = prevDigest.GetHeader(policy.GetHashAlgorithm); txIds = prevDigest.TxIds.Select(b => new TxId(b.ToBuilder().ToArray())); } IAction[] expectedRenderedActions = expectedRenderedActionsBuffer.ToArray(); List <IAction> actualRenderedActions = new List <IAction>(); List <IAction> actualUnrenderedActions = new List <IAction>(); foreach (var record in Records.Reverse()) { if (record is RenderRecord <T> .Reorg b && b.Begin) { break; } if (record is RenderRecord <T> .ActionBase a) { if (a.Render) { actualRenderedActions.Add(a.Action); } else { actualUnrenderedActions.Add(a.Action); } } } actualRenderedActions.Reverse(); actualUnrenderedActions.Reverse(); string ReprAction(IAction?action) { if (action is null) { return("[N/A]"); } return(action.PlainValue.Inspect(loadAll: true) .Replace(" \n ", " ") .Replace(" \n", " ") .Replace("\n ", " ") .Replace("\n", " ")); } string MakeErrorMessage(string prefix, IList <IAction> expected, IList <IAction> actual) { int expectN = expected.Count; int actualN = actual.Count; if (expectN != actualN) { prefix += $" (expected: {expectN} actions, actual: {actualN} actions):"; } var buffer = new StringBuilder(); for (int i = 0, count = Math.Max(expectN, actualN); i < count; i++) { IAction?e = i < expectN ? expected[i] : null; IAction?a = i < actualN ? actual[i] : null; if (!(e is null || a is null) && e.PlainValue.Equals(a.PlainValue)) { buffer.Append($"\n\t {ReprAction(e)}"); }
// FIXME it's very dangerous because replacing Id means // ALL blocks (referenced by MineBlock(), etc.) will be changed. internal System.Action Swap( BlockChain <T> other, bool render, StateCompleterSet <T>?stateCompleters = null) { if (other is null) { throw new ArgumentNullException(nameof(other)); } // As render/unrender processing requires every step's states from the branchpoint // to the new/stale tip, incomplete states need to be complemented anyway... StateCompleterSet <T> completers = stateCompleters ?? StateCompleterSet <T> .Recalculate; if (Tip.Equals(other.Tip)) { // If it's swapped for a chain with the same tip, it means there is no state change. // Hence render is unnecessary. render = false; } else { _logger.Debug( "The blockchain was reorged from " + "{OldChainId} (#{OldTipIndex} {OldTipHash}) " + "to {NewChainId} (#{NewTipIndex} {NewTipHash}).", Id, Tip.Index, Tip.Hash, other.Id, other.Tip.Index, other.Tip.Hash); } System.Action renderSwap = () => { }; _rwlock.EnterUpgradeableReadLock(); try { // Finds the branch point. Block <T> branchpoint = FindTopCommon(this, other); if (branchpoint is null) { const string msg = "A chain cannot be reorged into a heterogeneous chain with a " + "different genesis."; throw new InvalidGenesisBlockException(Genesis.Hash, other.Genesis.Hash, msg); } _logger.Debug( "The branchpoint is #{BranchpointIndex} {BranchpointHash}.", branchpoint.Index, branchpoint ); Block <T> oldTip = Tip, newTip = other.Tip; ImmutableList <Block <T> > rewindPath = GetRewindPath(this, branchpoint.Hash); ImmutableList <Block <T> > fastForwardPath = GetFastForwardPath(other, branchpoint.Hash); // If there is no rewind, it is not considered as a reorg. bool reorg = rewindPath.Count > 0; _rwlock.EnterWriteLock(); try { IEnumerable <Transaction <T> > GetTxsWithRange(BlockChain <T> chain, Block <T> start, Block <T> end) => Enumerable .Range((int)start.Index + 1, (int)(end.Index - start.Index)) .SelectMany(x => { // FIXME: Change the type of IBlockContent<T>.Transactions to // IImmutableSet<Transaction<T>>, and define a distinct property // to Block<T> for this ordering. Block <T> block = chain[x]; return(ActionEvaluator <T> .OrderTxsForEvaluation( block.ProtocolVersion, block.Transactions, block.PreEvaluationHash )); }); // It assumes reorg is small size. If it was big, this may be heavy task. ImmutableHashSet <Transaction <T> > unstagedTxs = GetTxsWithRange(this, branchpoint, Tip).ToImmutableHashSet(); ImmutableHashSet <Transaction <T> > stageTxs = GetTxsWithRange(other, branchpoint, other.Tip).ToImmutableHashSet(); ImmutableHashSet <Transaction <T> > restageTxs = unstagedTxs.Except(stageTxs); foreach (Transaction <T> restageTx in restageTxs) { StagePolicy.Stage(this, restageTx); } Guid obsoleteId = Id; Id = other.Id; Store.SetCanonicalChainId(Id); _blocks = new BlockSet <T>(Policy.GetHashAlgorithm, Store); TipChanged?.Invoke(this, (oldTip, newTip)); Store.DeleteChainId(obsoleteId); } finally { _rwlock.ExitWriteLock(); } renderSwap = () => RenderSwap( render: render, oldTip: oldTip, newTip: newTip, branchpoint: branchpoint, rewindPath: rewindPath, fastForwardPath: fastForwardPath, stateCompleters: completers); } finally { _rwlock.ExitUpgradeableReadLock(); } return(renderSwap); }