Exemple #1
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);
        }
        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);
        }
        public NineChroniclesNodeService(
            PrivateKey?minerPrivateKey,
            LibplanetNodeServiceProperties <NCAction> properties,
            IBlockPolicy <NCAction> blockPolicy,
            NetworkType networkType,
            Progress <PreloadState>?preloadProgress = null,
            bool ignoreBootstrapFailure             = false,
            bool ignorePreloadFailure = false,
            bool strictRendering      = false,
            bool authorizedMiner      = false,
            bool isDev           = false,
            int blockInterval    = 10000,
            int reorgInterval    = 0,
            TimeSpan txLifeTime  = default,
            int minerCount       = 1,
            int txQuotaPerSigner = 10
            )
        {
            MinerPrivateKey = minerPrivateKey;
            Properties      = properties;

            LogEventLevel logLevel          = LogEventLevel.Debug;
            var           blockPolicySource = new BlockPolicySource(Log.Logger, logLevel);
            // Policies for dev mode.
            IBlockPolicy <NCAction>?easyPolicy  = null;
            IBlockPolicy <NCAction>?hardPolicy  = null;
            IStagePolicy <NCAction> stagePolicy = new StagePolicy(txLifeTime, txQuotaPerSigner);

            if (isDev)
            {
                easyPolicy = new ReorgPolicy(new RewardGold(), 1);
                hardPolicy = new ReorgPolicy(new RewardGold(), 2);
            }

            BlockRenderer      = blockPolicySource.BlockRenderer;
            ActionRenderer     = blockPolicySource.ActionRenderer;
            ExceptionRenderer  = new ExceptionRenderer();
            NodeStatusRenderer = new NodeStatusRenderer();
            var renderers      = new List <IRenderer <NCAction> >();
            var strictRenderer = new StrictRenderer(onError: exc =>
                                                    ExceptionRenderer.RenderException(
                                                        RPCException.InvalidRenderException,
                                                        exc.Message.Split("\n")[0]
                                                        )
                                                    );

            if (Properties.Render)
            {
                renderers.Add(blockPolicySource.BlockRenderer);
                renderers.Add(blockPolicySource.LoggedActionRenderer);
            }
            else if (Properties.LogActionRenders)
            {
                renderers.Add(blockPolicySource.BlockRenderer);
                // The following "nullRenderer" does nothing.  It's just for filling
                // the LoggedActionRenderer<T>() constructor's parameter:
                IActionRenderer <NCAction> nullRenderer =
                    new AnonymousActionRenderer <NCAction>();
                renderers.Add(
                    new LoggedActionRenderer <NCAction>(
                        nullRenderer,
                        Log.Logger,
                        logLevel
                        )
                    );
            }
            else
            {
                renderers.Add(blockPolicySource.LoggedBlockRenderer);
            }

            if (strictRendering)
            {
                Log.Debug(
                    $"Strict rendering is on. Add {nameof(StrictRenderer)}.");
                renderers.Add(strictRenderer);
            }

            async Task minerLoopAction(
                BlockChain <NCAction> chain,
                Swarm <NCAction> swarm,
                PrivateKey privateKey,
                CancellationToken cancellationToken)
            {
                var miner = new Miner(chain, swarm, privateKey, authorizedMiner);

                Log.Debug("Miner called.");
                while (!cancellationToken.IsCancellationRequested)
                {
                    try
                    {
                        long nextBlockIndex = chain.Tip.Index + 1;

                        if (swarm.Running)
                        {
                            Log.Debug("Start mining.");

                            if (chain.Policy is BlockPolicy bp)
                            {
                                if (bp.IsAllowedToMine(privateKey.ToAddress(), chain.Count))
                                {
                                    IEnumerable <Task <Block <NCAction> > > miners = Enumerable
                                                                                     .Range(0, minerCount)
                                                                                     .Select(_ => miner.MineBlockAsync(cancellationToken));
                                    await Task.WhenAll(miners);
                                }
                                else
                                {
                                    Log.Debug(
                                        "Miner {MinerAddress} is not allowed to mine a block with index {Index} " +
                                        "under current policy.",
                                        privateKey.ToAddress(),
                                        chain.Count);
                                    await Task.Delay(1000, cancellationToken);
                                }
                            }
                            else
                            {
                                Log.Error(
                                    "No suitable policy was found for chain {ChainId}.",
                                    chain.Id);
                                await Task.Delay(1000, cancellationToken);
                            }
                        }
                        else
                        {
                            await Task.Delay(1000, cancellationToken);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex, "Exception occurred.");
                    }
                }
            }

            async Task devMinerLoopAction(
                Swarm <NCAction> mainSwarm,
                Swarm <NCAction> subSwarm,
                PrivateKey privateKey,
                CancellationToken cancellationToken)
            {
                var miner = new ReorgMiner(mainSwarm, subSwarm, privateKey, reorgInterval);

                Log.Debug("Miner called.");
                while (!cancellationToken.IsCancellationRequested)
                {
                    try
                    {
                        if (mainSwarm.Running)
                        {
                            Log.Debug("Start mining.");
                            await miner.MineBlockAsync(cancellationToken);

                            await Task.Delay(blockInterval, cancellationToken);
                        }
                        else
                        {
                            await Task.Delay(1000, cancellationToken);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex, "Exception occurred.");
                    }
                }
            }

            if (isDev)
            {
                NodeService = new DevLibplanetNodeService <NCAction>(
                    Properties,
                    easyPolicy,
                    hardPolicy,
                    stagePolicy,
                    renderers,
                    devMinerLoopAction,
                    preloadProgress,
                    (code, msg) =>
                {
                    ExceptionRenderer.RenderException(code, msg);
                    Log.Error(msg);
                },
                    isPreloadStarted => { NodeStatusRenderer.PreloadStatus(isPreloadStarted); },
                    ignoreBootstrapFailure
                    );
            }
            else
            {
                NodeService = new LibplanetNodeService <NCAction>(
                    Properties,
                    blockPolicy,
                    stagePolicy,
                    renderers,
                    minerLoopAction,
                    preloadProgress,
                    (code, msg) =>
                {
                    ExceptionRenderer.RenderException(code, msg);
                    Log.Error(msg);
                },
                    isPreloadStarted =>
                {
                    NodeStatusRenderer.PreloadStatus(isPreloadStarted);
                },
                    ignoreBootstrapFailure,
                    ignorePreloadFailure
                    );
            }

            strictRenderer.BlockChain = NodeService.BlockChain ?? throw new Exception("BlockChain is null.");
        }