private static ImmutableList <Block <T> > GetRewindPath(
            BlockChain <T> chain,
            BlockHash targetHash)
        {
            if (!chain.ContainsBlock(targetHash))
            {
                throw new KeyNotFoundException(
                          $"Given chain {chain.Id} must contain target hash {targetHash}");
            }

            Block <T>         target = chain[targetHash];
            List <Block <T> > path   = new List <Block <T> >();

            for (
                Block <T> current = chain.Tip;
                current.Index > target.Index && current.PreviousHash is { } ph;
                current = chain[ph])
            {
                path.Add(current);
            }

            return(path.ToImmutableList());
        }
Example #2
0
        internal BlockChain <T> Fork(HashDigest <SHA256> point)
        {
            var forked = new BlockChain <T>(Policy, Store, Guid.NewGuid());

            try
            {
                _rwlock.EnterReadLock();
                foreach (var index in Store.IterateIndex(Id.ToString()))
                {
                    forked.Append(Blocks[index]);

                    if (index == point)
                    {
                        break;
                    }
                }

                return(forked);
            }
            finally
            {
                _rwlock.ExitReadLock();
            }
        }
Example #3
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
                            );
                    }
                }
            }
        }
Example #4
0
        internal BlockChain <T> Fork(HashDigest <SHA256> point)
        {
            var  forked   = new BlockChain <T>(Policy, Store, Guid.NewGuid());
            Guid forkedId = forked.Id;

            try
            {
                _rwlock.EnterReadLock();

                Store.ForkBlockIndexes(Id, forkedId, point);
                Block <T> pointBlock = Blocks[point];

                var signersToStrip = new Dictionary <Address, int>();

                for (
                    Block <T> block = Tip;
                    block.PreviousHash is HashDigest <SHA256> hash &&
                    !block.Hash.Equals(point);
                    block = Blocks[hash])
                {
                    IEnumerable <(Address, int)> signers = block
                                                           .Transactions
                                                           .GroupBy(tx => tx.Signer)
                                                           .Select(g => (g.Key, g.Count()));

                    foreach ((Address address, int txCount) in signers)
                    {
                        int existingValue = 0;
                        signersToStrip.TryGetValue(address, out existingValue);
                        signersToStrip[address] = existingValue + txCount;
                    }
                }

                Store.ForkStateReferences(Id, forked.Id, pointBlock);

                foreach (KeyValuePair <Address, long> pair in Store.ListTxNonces(Id))
                {
                    Address address       = pair.Key;
                    long    existingNonce = pair.Value;
                    long    txNonce       = existingNonce;
                    int     staleTxCount  = 0;
                    if (signersToStrip.TryGetValue(address, out staleTxCount))
                    {
                        txNonce -= staleTxCount;
                    }

                    if (txNonce < 0)
                    {
                        throw new InvalidOperationException(
                                  $"A tx nonce for {address} in the store seems broken.\n" +
                                  $"Existing tx nonce: {existingNonce}\n" +
                                  $"# of stale transactions: {staleTxCount}\n"
                                  );
                    }

                    // Note that at this point every address has tx nonce = 0
                    // it's merely "setting" rather than "increasing."
                    Store.IncreaseTxNonce(forkedId, address, txNonce);
                }
            }
            finally
            {
                _rwlock.ExitReadLock();
            }

            return(forked);
        }
Example #5
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)
        {
            // Finds the branch point.
            Block <T> topmostCommon = 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;
                }
            }

            // 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]
                )
            {
                var actions = b.EvaluateActionsPerTx(a =>
                                                     GetStates(new[] { a }, b.PreviousHash).GetValueOrDefault(a)
                                                     ).Reverse();
                foreach (var(_, evaluation) in actions)
                {
                    evaluation.Action.Unrender(
                        evaluation.InputContext,
                        evaluation.OutputStates
                        );
                }
            }

            try
            {
                _rwlock.EnterWriteLock();

                Id           = other.Id;
                Blocks       = new BlockSet <T>(Store);
                Transactions = new TransactionSet <T>(Store);
            }
            finally
            {
                _rwlock.ExitWriteLock();
            }

            // 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)
            {
                var actions = b.EvaluateActionsPerTx(a =>
                                                     GetStates(new[] { a }, b.PreviousHash).GetValueOrDefault(a)
                                                     );
                foreach (var(_, evaluation) in actions)
                {
                    evaluation.Action.Render(
                        evaluation.InputContext,
                        evaluation.OutputStates
                        );
                }
            }
        }
Example #6
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.");
            }
        }
Example #7
0
        internal BlockChain <T> Fork(
            HashDigest <SHA256> point,
            DateTimeOffset currentTime)
        {
            var forked = new BlockChain <T>(Policy, Store, Guid.NewGuid());

            try
            {
                _rwlock.EnterReadLock();
                foreach (var index in Store.IterateIndex(Id.ToString()))
                {
                    Store.AppendIndex(forked.Id.ToString(), index);
                    if (index.Equals(point))
                    {
                        break;
                    }
                }

                Block <T> pointBlock = Blocks[point];

                var addressesToStrip = new HashSet <Address>();
                var signersToStrip   = new HashSet <Address>();

                for (
                    Block <T> block = Tip;
                    block.PreviousHash is HashDigest <SHA256> hash &&
                    !block.Hash.Equals(point);
                    block = Blocks[hash])
                {
                    ImmutableHashSet <Address> addresses =
                        Store.GetBlockStates(block.Hash)
                        .Select(kv => kv.Key)
                        .ToImmutableHashSet();

                    ImmutableHashSet <Address> signers = block
                                                         .Transactions
                                                         .Select(tx => tx.Signer)
                                                         .ToImmutableHashSet();

                    addressesToStrip.UnionWith(addresses);
                    signersToStrip.UnionWith(signers);
                }

                Store.ForkStateReferences(
                    Id.ToString(),
                    forked.Id.ToString(),
                    pointBlock,
                    addressesToStrip.ToImmutableHashSet());
                Store.ForkTxNonce(
                    Id.ToString(),
                    forked.Id.ToString(),
                    pointBlock,
                    signersToStrip.ToImmutableHashSet());
            }
            finally
            {
                _rwlock.ExitReadLock();
            }

            return(forked);
        }
        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);
        }
Example #9
0
        internal BlockChain <T> Fork(
            HashDigest <SHA256> point,
            DateTimeOffset currentTime)
        {
            var    forked   = new BlockChain <T>(Policy, Store, Guid.NewGuid());
            string id       = Id.ToString();
            string forkedId = forked.Id.ToString();

            try
            {
                _rwlock.EnterReadLock();
                foreach (var index in Store.IterateIndex(id))
                {
                    Store.AppendIndex(forkedId, index);
                    if (index.Equals(point))
                    {
                        break;
                    }
                }

                Block <T> pointBlock = Blocks[point];

                var addressesToStrip = new HashSet <Address>();
                var signersToStrip   = new Dictionary <Address, int>();

                for (
                    Block <T> block = Tip;
                    block.PreviousHash is HashDigest <SHA256> hash &&
                    !block.Hash.Equals(point);
                    block = Blocks[hash])
                {
                    ImmutableHashSet <Address> addresses =
                        Store.GetBlockStates(block.Hash)
                        .Select(kv => kv.Key)
                        .ToImmutableHashSet();

                    addressesToStrip.UnionWith(addresses);

                    IEnumerable <(Address, int)> signers = block
                                                           .Transactions
                                                           .GroupBy(tx => tx.Signer)
                                                           .Select(g => (g.Key, g.Count()));

                    foreach ((Address address, int txCount) in signers)
                    {
                        int existingValue = 0;
                        signersToStrip.TryGetValue(address, out existingValue);
                        signersToStrip[address] = existingValue + txCount;
                    }
                }

                Store.ForkStateReferences(
                    id,
                    forked.Id.ToString(),
                    pointBlock,
                    addressesToStrip.ToImmutableHashSet());

                foreach (KeyValuePair <Address, long> pair in Store.ListTxNonces(id))
                {
                    Address address       = pair.Key;
                    long    existingNonce = pair.Value;
                    long    txNonce       = existingNonce;
                    int     staleTxCount  = 0;
                    if (signersToStrip.TryGetValue(address, out staleTxCount))
                    {
                        txNonce -= staleTxCount;
                    }

                    if (txNonce < 0)
                    {
                        throw new InvalidOperationException(
                                  $"A tx nonce for {address} in the store seems broken.\n" +
                                  $"Existing tx nonce: {existingNonce}\n" +
                                  $"# of stale transactions: {staleTxCount}\n"
                                  );
                    }

                    // Note that at this point every address has tx nonce = 0
                    // it's merely "setting" rather than "increasing."
                    Store.IncreaseTxNonce(forkedId, address, txNonce);
                }
            }
            finally
            {
                _rwlock.ExitReadLock();
            }

            return(forked);
        }
Example #10
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);
        }