Esempio n. 1
0
        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)}");
                    }
Esempio n. 2
0
        // 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);
        }