예제 #1
0
        // FIXME it's very dangerous because replacing Id means
        // ALL blocks (referenced by MineBlock(), etc.) will be changed.
        // we need to add a synchronization mechanism to handle this correctly.
        internal void Swap(BlockChain <T> other, bool render)
        {
            // Finds the branch point.
            Block <T> topmostCommon = null;

            if (render && !(Tip is null || other.Tip is null))
            {
                long shorterHeight =
                    Math.Min(this.LongCount(), other.LongCount()) - 1;
                for (
                    Block <T> t = this[shorterHeight], o = other[shorterHeight];
                    t.PreviousHash is HashDigest <SHA256> tp &&
                    o.PreviousHash is HashDigest <SHA256> op;
                    t = Blocks[tp], o = other.Blocks[op]
                    )
                {
                    if (t.Equals(o))
                    {
                        topmostCommon = t;
                        break;
                    }
                }
            }

            if (render)
            {
                // Unrender stale actions.
                for (
                    Block <T> b = Tip;
                    !(b is null) && b.Index > (topmostCommon?.Index ?? -1) &&
                    b.PreviousHash is HashDigest <SHA256> ph;
                    b = Blocks[ph]
                    )
                {
                    List <ActionEvaluation> evaluations = b.EvaluateActionsPerTx(a =>
                                                                                 GetStates(new[] { a }, b.PreviousHash).GetValueOrDefault(a))
                                                          .Select(te => te.Item2).ToList();

                    if (Policy.BlockAction is IAction)
                    {
                        evaluations.Add(EvaluateBlockAction(b, evaluations));
                    }

                    evaluations.Reverse();

                    foreach (var evaluation in evaluations)
                    {
                        evaluation.Action.Unrender(
                            evaluation.InputContext,
                            evaluation.OutputStates
                            );
                    }
                }
            }

            try
            {
                _rwlock.EnterWriteLock();

                var tipChangedEventArgs = new TipChangedEventArgs
                {
                    PreviousHash  = Tip?.Hash,
                    PreviousIndex = Tip?.Index,
                    Hash          = other.Tip.Hash,
                    Index         = other.Tip.Index,
                };
                Guid obsoleteId = Id;
                Id = other.Id;
                Store.SetCanonicalChainId(Id);
                Blocks = new BlockSet <T>(Store);
                TipChanged?.Invoke(this, tipChangedEventArgs);
                Transactions = new TransactionSet <T>(Store);
                Store.DeleteChainId(obsoleteId);
            }
            finally
            {
                _rwlock.ExitWriteLock();
            }

            if (render)
            {
                // Render actions that had been behind.
                IEnumerable <Block <T> > blocksToRender =
                    topmostCommon is Block <T> branchPoint
                        ? this.SkipWhile(b => b.Index <= branchPoint.Index)
                        : this;

                foreach (Block <T> b in blocksToRender)
                {
                    List <ActionEvaluation> evaluations = b.EvaluateActionsPerTx(a =>
                                                                                 GetStates(new[] { a }, b.PreviousHash).GetValueOrDefault(a))
                                                          .Select(te => te.Item2).ToList();

                    if (Policy.BlockAction is IAction)
                    {
                        evaluations.Add(EvaluateBlockAction(b, evaluations));
                    }

                    foreach (var evaluation in evaluations)
                    {
                        evaluation.Action.Render(
                            evaluation.InputContext,
                            evaluation.OutputStates
                            );
                    }
                }
            }
        }
예제 #2
0
        internal void Append(
            Block <T> block,
            DateTimeOffset currentTime,
            bool evaluateActions,
            bool renderActions
            )
        {
            if (!evaluateActions && renderActions)
            {
                throw new ArgumentException(
                          $"{nameof(renderActions)} option requires {nameof(evaluateActions)} " +
                          "to be turned on.",
                          nameof(renderActions)
                          );
            }

            _rwlock.EnterUpgradeableReadLock();
            try
            {
                InvalidBlockException e =
                    Policy.ValidateNextBlock(this, block);

                if (!(e is null))
                {
                    throw e;
                }

                HashDigest <SHA256>?tip = Store.IndexBlockHash(Id, -1);

                var nonceDeltas = new Dictionary <Address, long>();

                // block.Transactions have already been sorted by
                // the tx nounce order when the block was created
                foreach (Transaction <T> tx1 in block.Transactions)
                {
                    Address txSigner = tx1.Signer;
                    nonceDeltas.TryGetValue(txSigner, out var nonceDelta);

                    long expectedNonce = nonceDelta + Store.GetTxNonce(Id, txSigner);

                    if (!expectedNonce.Equals(tx1.Nonce))
                    {
                        throw new InvalidTxNonceException(
                                  tx1.Id,
                                  expectedNonce,
                                  tx1.Nonce,
                                  "Transaction nonce is invalid."
                                  );
                    }

                    nonceDeltas[txSigner] = nonceDelta + 1;
                }

                _rwlock.EnterWriteLock();
                try
                {
                    Block <T> prevTip = Tip;
                    Blocks[block.Hash] = block;
                    foreach (KeyValuePair <Address, long> pair in nonceDeltas)
                    {
                        Store.IncreaseTxNonce(Id, pair.Key, pair.Value);
                    }

                    Store.AppendIndex(Id, block.Hash);
                    var tipChangedEventArgs = new TipChangedEventArgs
                    {
                        PreviousIndex = prevTip?.Index,
                        PreviousHash  = prevTip?.Hash,
                        Index         = block.Index,
                        Hash          = block.Hash,
                    };
                    TipChanged?.Invoke(this, tipChangedEventArgs);
                    ISet <TxId> txIds = block.Transactions
                                        .Select(t => t.Id)
                                        .ToImmutableHashSet();

                    Store.UnstageTransactionIds(txIds);
                }
                finally
                {
                    _rwlock.ExitWriteLock();
                }
            }
            finally
            {
                _rwlock.ExitUpgradeableReadLock();
            }

            if (evaluateActions)
            {
                ExecuteActions(block, renderActions);
            }
        }
예제 #3
0
        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 <long> LongRange(long start, long count)
                    {
                        for (long i = 0; i < count; i++)
                        {
                            yield return(start + i);
                        }
                    }

                    ImmutableHashSet <Transaction <T> >
                    GetTxsWithRange(BlockChain <T> chain, Block <T> start, Block <T> end) =>
                    LongRange(start.Index + 1, end.Index - start.Index)
                    .SelectMany(index => chain[index].Transactions)
                    .ToImmutableHashSet();

                    // It assumes reorg is small size. If it was big, this may be heavy task.
                    ImmutableHashSet <Transaction <T> > txsToStage =
                        GetTxsWithRange(this, branchpoint, Tip);
                    ImmutableHashSet <Transaction <T> > txsToUnstage =
                        GetTxsWithRange(other, branchpoint, other.Tip);
                    foreach (Transaction <T> tx in txsToStage)
                    {
                        StagePolicy.Stage(this, tx);
                    }

                    Guid obsoleteId = Id;
                    Id = other.Id;
                    Store.SetCanonicalChainId(Id);
                    _blocks = new BlockSet <T>(Policy.GetHashAlgorithm, Store);
                    foreach (Transaction <T> tx in txsToUnstage)
                    {
                        StagePolicy.Unstage(this, tx.Id);
                    }

                    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);
        }
예제 #4
0
        // FIXME it's very dangerous because replacing Id means
        // ALL blocks (referenced by MineBlock(), etc.) will be changed.
        // we need to add a synchronization mechanism to handle this correctly.
        internal void Swap(BlockChain <T> other, bool render)
        {
            if (other?.Tip is null)
            {
                throw new ArgumentException(
                          $"The chain to be swapped is invalid. Id: {other?.Id}, Tip: {other?.Tip}",
                          nameof(other));
            }

            _logger.Debug(
                "Swapping block chain. (from: {fromChainId}) (to: {toChainId})", Id, other.Id);

            // Finds the branch point.
            Block <T> topmostCommon = null;

            if (render && !(Tip is null))
            {
                long shorterHeight =
                    Math.Min(Count, other.Count) - 1;
                for (
                    Block <T> t = this[shorterHeight], o = other[shorterHeight];
                    t.PreviousHash is HashDigest <SHA256> tp &&
                    o.PreviousHash is HashDigest <SHA256> op;
                    t = this[tp], o = other[op]
                    )
                {
                    if (t.Equals(o))
                    {
                        topmostCommon = t;
                        break;
                    }
                }
            }

            _logger.Debug(
                "Branchpoint is {branchPoint} (at {index})", topmostCommon, topmostCommon?.Index);

            if (render)
            {
                _logger.Debug("Unrendering abandoned actions...");

                // Unrender stale actions.
                for (
                    Block <T> b = Tip;
                    !(b is null) && b.Index > (topmostCommon?.Index ?? -1) &&
                    b.PreviousHash is HashDigest <SHA256> ph;
                    b = this[ph]
                    )
                {
                    List <ActionEvaluation> evaluations = EvaluateActions(b).ToList();
                    evaluations.Reverse();

                    foreach (var evaluation in evaluations)
                    {
                        _logger.Debug("Unrender action {action}", evaluation.Action);
                        evaluation.Action.Unrender(
                            evaluation.InputContext,
                            evaluation.OutputStates
                            );
                    }
                }

                _logger.Debug($"Unrender for {nameof(Swap)}() is completed.");
            }

            try
            {
                _rwlock.EnterWriteLock();

                var tipChangedEventArgs = new TipChangedEventArgs
                {
                    PreviousHash  = Tip?.Hash,
                    PreviousIndex = Tip?.Index,
                    Hash          = other.Tip.Hash,
                    Index         = other.Tip.Index,
                };
                Guid obsoleteId = Id;
                Id = other.Id;
                Store.SetCanonicalChainId(Id);
                _blocks = new BlockSet <T>(Store);
                TipChanged?.Invoke(this, tipChangedEventArgs);
                _transactions = new TransactionSet <T>(Store);
                Store.DeleteChainId(obsoleteId);
            }
            finally
            {
                _rwlock.ExitWriteLock();
            }

            if (render)
            {
                _logger.Debug("Rendering actions in new chain");

                // Render actions that had been behind.
                long startToRenderIndex = topmostCommon is Block <T> branchPoint
                    ? branchPoint.Index + 1
                    : 0;

                RenderBlocks(startToRenderIndex);
                _logger.Debug($"Render for {nameof(Swap)}() is completed.");
            }
        }
예제 #5
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);
        }