// It takes dotCover to run it quite long, increased timeouts
        public async Task Can_process_mined_blocks()
        {
            int timeMultiplier = 1; // for debugging

            TimeSpan miningDelay = TimeSpan.FromMilliseconds(200 * timeMultiplier);

            /* logging & instrumentation */
//            OneLoggerLogManager logger = new OneLoggerLogManager(new SimpleConsoleLogger(true));
            ILogManager logManager = LimboLogs.Instance;
            ILogger     logger     = logManager.GetClassLogger();

            /* spec */
            FakeSealer sealer = new FakeSealer(miningDelay);

            RopstenSpecProvider specProvider = RopstenSpecProvider.Instance;

            /* state & storage */
            StateDb         codeDb          = new StateDb();
            StateDb         stateDb         = new StateDb();
            StateProvider   stateProvider   = new StateProvider(stateDb, codeDb, logManager);
            StorageProvider storageProvider = new StorageProvider(stateDb, stateProvider, logManager);

            /* store & validation */

            EthereumEcdsa ecdsa      = new EthereumEcdsa(specProvider, logManager);
            MemDb         receiptsDb = new MemDb();

            TxPool.TxPool        txPool               = new TxPool.TxPool(NullTxStorage.Instance, Timestamper.Default, ecdsa, specProvider, new TxPoolConfig(), stateProvider, logManager);
            IReceiptStorage      receiptStorage       = new PersistentReceiptStorage(receiptsDb, specProvider, logManager);
            var                  blockInfoDb          = new MemDb();
            BlockTree            blockTree            = new BlockTree(new MemDb(), new MemDb(), blockInfoDb, new ChainLevelInfoRepository(blockInfoDb), specProvider, txPool, NullBloomStorage.Instance, logManager);
            Timestamper          timestamper          = new Timestamper();
            DifficultyCalculator difficultyCalculator = new DifficultyCalculator(specProvider);
            HeaderValidator      headerValidator      = new HeaderValidator(blockTree, sealer, specProvider, logManager);
            OmmersValidator      ommersValidator      = new OmmersValidator(blockTree, headerValidator, logManager);
            TxValidator          txValidator          = new TxValidator(ChainId.Ropsten);
            BlockValidator       blockValidator       = new BlockValidator(txValidator, headerValidator, ommersValidator, specProvider, logManager);

            TestTransactionsGenerator generator = new TestTransactionsGenerator(txPool, ecdsa, TimeSpan.FromMilliseconds(50 * timeMultiplier), LimboLogs.Instance);

            generator.Start();

            /* blockchain processing */
            BlockhashProvider    blockhashProvider = new BlockhashProvider(blockTree, LimboLogs.Instance);
            VirtualMachine       virtualMachine    = new VirtualMachine(stateProvider, storageProvider, blockhashProvider, specProvider, logManager);
            TransactionProcessor processor         = new TransactionProcessor(specProvider, stateProvider, storageProvider, virtualMachine, logManager);
            RewardCalculator     rewardCalculator  = new RewardCalculator(specProvider);
            BlockProcessor       blockProcessor    = new BlockProcessor(specProvider, blockValidator, rewardCalculator,
                                                                        processor, stateDb, codeDb, stateProvider, storageProvider, txPool, receiptStorage, logManager);
            BlockchainProcessor blockchainProcessor = new BlockchainProcessor(blockTree, blockProcessor, new TxSignaturesRecoveryStep(ecdsa, NullTxPool.Instance, LimboLogs.Instance), logManager, false);

            /* load ChainSpec and init */
            ChainSpecLoader loader = new ChainSpecLoader(new EthereumJsonSerializer());
            string          path   = "chainspec.json";

            logManager.GetClassLogger().Info($"Loading ChainSpec from {path}");
            ChainSpec chainSpec = loader.Load(File.ReadAllText(path));

            foreach (var allocation in chainSpec.Allocations)
            {
                stateProvider.CreateAccount(allocation.Key, allocation.Value.Balance);
                if (allocation.Value.Code != null)
                {
                    Keccak codeHash = stateProvider.UpdateCode(allocation.Value.Code);
                    stateProvider.UpdateCodeHash(allocation.Key, codeHash, specProvider.GenesisSpec);
                }
            }

            stateProvider.Commit(specProvider.GenesisSpec);
            chainSpec.Genesis.Header.StateRoot = stateProvider.StateRoot;
            chainSpec.Genesis.Header.Hash      = chainSpec.Genesis.Header.CalculateHash();
            if (chainSpec.Genesis.Hash != new Keccak("0xafbc3c327d2d18ff2b843e89226ef288fcee379542f854f982e4cfb85916d126"))
            {
                throw new Exception("Unexpected genesis hash");
            }

            /* start processing */
            blockTree.SuggestBlock(chainSpec.Genesis);
            blockchainProcessor.Start();

            var transactionSelector = new PendingTxSelector(txPool, stateProvider, logManager);
            MinedBlockProducer minedBlockProducer = new MinedBlockProducer(transactionSelector, blockchainProcessor, sealer, blockTree, blockchainProcessor, stateProvider, timestamper, LimboLogs.Instance, difficultyCalculator);

            minedBlockProducer.Start();

            ManualResetEventSlim manualResetEvent = new ManualResetEventSlim(false);

            blockTree.NewHeadBlock += (sender, args) =>
            {
                if (args.Block.Number == 6)
                {
                    manualResetEvent.Set();
                }
            };

            manualResetEvent.Wait(miningDelay * 100);
            await minedBlockProducer.StopAsync();

            int previousCount = 0;
            int totalTx       = 0;

            for (int i = 0; i < 6; i++)
            {
                Block block = blockTree.FindBlock(i, BlockTreeLookupOptions.None);
                Assert.That(block, Is.Not.Null, $"Block {i} not produced");
                logger.Info($"Block {i} with {block.Transactions.Length} txs");

                ManualResetEventSlim blockProcessedEvent = new ManualResetEventSlim(false);
                blockchainProcessor.ProcessingQueueEmpty += (sender, args) => blockProcessedEvent.Set();
                blockchainProcessor.Enqueue(block, ProcessingOptions.ForceProcessing | ProcessingOptions.StoreReceipts | ProcessingOptions.ReadOnlyChain);
                blockProcessedEvent.Wait(miningDelay);

                GethStyleTracer gethStyleTracer = new GethStyleTracer(blockchainProcessor, receiptStorage, blockTree);

                int currentCount = receiptsDb.Keys.Count;
                logger.Info($"Current count of receipts {currentCount}");
                logger.Info($"Previous count of receipts {previousCount}");

                if (block.Transactions.Length > 0)
                {
                    Assert.AreNotEqual(previousCount, currentCount, $"receipts at block {i}");
                    totalTx += block.Transactions.Length;
                }

                previousCount = currentCount;
            }

            Assert.AreNotEqual(0, totalTx, "no tx in blocks");
        }
        public async Task Can_process_mined_blocks()
        {
            int timeMultiplier = 1; // for debugging

            TimeSpan miningDelay = TimeSpan.FromMilliseconds(50 * timeMultiplier);

            /* logging & instrumentation */
//            OneLoggerLogManager logger = new OneLoggerLogManager(new SimpleConsoleLogger(true));
            ILogManager logManager = NullLogManager.Instance;
            ILogger     logger     = logManager.GetClassLogger();

            /* spec */
            FakeSealer sealer = new FakeSealer(miningDelay);

            RopstenSpecProvider specProvider = RopstenSpecProvider.Instance;

            /* store & validation */

            EthereumEcdsa        ecdsa                = new EthereumEcdsa(specProvider, logManager);
            MemDb                receiptsDb           = new MemDb();
            MemDb                traceDb              = new MemDb();
            TxPool               txPool               = new TxPool(NullTxStorage.Instance, Timestamp.Default, ecdsa, specProvider, new TxPoolConfig(), logManager);
            IReceiptStorage      receiptStorage       = new PersistentReceiptStorage(receiptsDb, NullDb.Instance, specProvider, logManager);
            BlockTree            blockTree            = new BlockTree(new MemDb(), new MemDb(), new MemDb(), specProvider, txPool, logManager);
            Timestamp            timestamp            = new Timestamp();
            DifficultyCalculator difficultyCalculator = new DifficultyCalculator(specProvider);
            HeaderValidator      headerValidator      = new HeaderValidator(blockTree, sealer, specProvider, logManager);
            OmmersValidator      ommersValidator      = new OmmersValidator(blockTree, headerValidator, logManager);
            TxValidator          txValidator          = new TxValidator(ChainId.Ropsten);
            BlockValidator       blockValidator       = new BlockValidator(txValidator, headerValidator, ommersValidator, specProvider, logManager);

            /* state & storage */
            StateDb         codeDb          = new StateDb();
            StateDb         stateDb         = new StateDb();
            StateProvider   stateProvider   = new StateProvider(stateDb, codeDb, logManager);
            StorageProvider storageProvider = new StorageProvider(stateDb, stateProvider, logManager);

            TestTransactionsGenerator generator = new TestTransactionsGenerator(txPool, ecdsa, TimeSpan.FromMilliseconds(5 * timeMultiplier), NullLogManager.Instance);

            generator.Start();

            /* blockchain processing */
            BlockhashProvider    blockhashProvider = new BlockhashProvider(blockTree, LimboLogs.Instance);
            VirtualMachine       virtualMachine    = new VirtualMachine(stateProvider, storageProvider, blockhashProvider, logManager);
            TransactionProcessor processor         = new TransactionProcessor(specProvider, stateProvider, storageProvider, virtualMachine, logManager);
            RewardCalculator     rewardCalculator  = new RewardCalculator(specProvider);
            BlockProcessor       blockProcessor    = new BlockProcessor(specProvider, blockValidator, rewardCalculator,
                                                                        processor, stateDb, codeDb, traceDb, stateProvider, storageProvider, txPool, receiptStorage, logManager);
            BlockchainProcessor blockchainProcessor = new BlockchainProcessor(blockTree, blockProcessor, new TxSignaturesRecoveryStep(ecdsa, NullTxPool.Instance, LimboLogs.Instance), logManager, false, false);

            /* load ChainSpec and init */
            ChainSpecLoader loader = new ChainSpecLoader(new EthereumJsonSerializer());
            string          path   = "chainspec.json";

            logManager.GetClassLogger().Info($"Loading ChainSpec from {path}");
            ChainSpec chainSpec = loader.Load(File.ReadAllBytes(path));

            foreach (var allocation in chainSpec.Allocations)
            {
                stateProvider.CreateAccount(allocation.Key, allocation.Value.Balance);
                if (allocation.Value.Code != null)
                {
                    Keccak codeHash = stateProvider.UpdateCode(allocation.Value.Code);
                    stateProvider.UpdateCodeHash(allocation.Key, codeHash, specProvider.GenesisSpec);
                }
            }

            stateProvider.Commit(specProvider.GenesisSpec);
            chainSpec.Genesis.Header.StateRoot = stateProvider.StateRoot; // TODO: shall it be HeaderSpec and not BlockHeader?
            chainSpec.Genesis.Header.Hash      = BlockHeader.CalculateHash(chainSpec.Genesis.Header);
            if (chainSpec.Genesis.Hash != new Keccak("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"))
            {
                throw new Exception("Unexpected genesis hash");
            }

            /* start processing */
            blockTree.SuggestBlock(chainSpec.Genesis);
            blockchainProcessor.Start();

            MinedBlockProducer minedBlockProducer = new MinedBlockProducer(difficultyCalculator, txPool, blockchainProcessor, sealer, blockTree, timestamp, NullLogManager.Instance);

            minedBlockProducer.Start();

            ManualResetEventSlim manualResetEvent = new ManualResetEventSlim(false);

            blockTree.NewHeadBlock += (sender, args) =>
            {
                if (args.Block.Number == 6)
                {
                    manualResetEvent.Set();
                }
            };

            manualResetEvent.Wait(miningDelay * 12 * timeMultiplier);
            await minedBlockProducer.StopAsync();

            int previousCount = 0;
            int totalTx       = 0;

            for (int i = 0; i < 6; i++)
            {
                Block block = blockTree.FindBlock(i);
                logger.Info($"Block {i} with {block.Transactions.Length} txs");

                ManualResetEventSlim blockProcessedEvent = new ManualResetEventSlim(false);
                blockchainProcessor.ProcessingQueueEmpty += (sender, args) => blockProcessedEvent.Set();
                blockchainProcessor.SuggestBlock(block, ProcessingOptions.ForceProcessing | ProcessingOptions.StoreReceipts | ProcessingOptions.ReadOnlyChain);
                blockProcessedEvent.Wait(1000);

                Tracer tracer = new Tracer(blockchainProcessor, receiptStorage, blockTree, new MemDb());

                int currentCount = receiptsDb.Keys.Count;
                logger.Info($"Current count of receipts {currentCount}");
                logger.Info($"Previous count of receipts {previousCount}");

                if (block.Transactions.Length > 0)
                {
                    GethLikeTxTrace trace = tracer.Trace(block.Transactions[0].Hash);
                    Assert.AreSame(GethLikeTxTrace.QuickFail, trace);
                    Assert.AreNotEqual(previousCount, currentCount, $"receipts at block {i}");
                    totalTx += block.Transactions.Length;
                }

                previousCount = currentCount;
            }

            Assert.AreNotEqual(0, totalTx, "no tx in blocks");
        }
        protected virtual async Task <TestBlockchain> Build(ISpecProvider specProvider = null)
        {
            Timestamper    = new ManualTimestamper(new DateTime(2020, 2, 15, 12, 50, 30, DateTimeKind.Utc));
            JsonSerializer = new EthereumJsonSerializer();
            SpecProvider   = specProvider ?? MainnetSpecProvider.Instance;
            EthereumEcdsa  = new EthereumEcdsa(ChainId.Mainnet, LimboLogs.Instance);
            ITxStorage txStorage = new InMemoryTxStorage();

            DbProvider = new MemDbProvider();
            State      = new StateProvider(StateDb, CodeDb, LimboLogs.Instance);
            State.CreateAccount(TestItem.AddressA, 1000.Ether());
            State.CreateAccount(TestItem.AddressB, 1000.Ether());
            State.CreateAccount(TestItem.AddressC, 1000.Ether());
            byte[] code     = Bytes.FromHexString("0xabcd");
            Keccak codeHash = Keccak.Compute(code);

            State.UpdateCode(code);
            State.UpdateCodeHash(TestItem.AddressA, codeHash, SpecProvider.GenesisSpec);

            Storage = new StorageProvider(StateDb, State, LimboLogs.Instance);
            Storage.Set(new StorageCell(TestItem.AddressA, UInt256.One), Bytes.FromHexString("0xabcdef"));
            Storage.Commit();

            State.Commit(SpecProvider.GenesisSpec);
            State.CommitTree();

            TxPool = new TxPool.TxPool(txStorage, Timestamper, EthereumEcdsa, SpecProvider, new TxPoolConfig(), State, LimboLogs.Instance);

            IDb blockDb     = new MemDb();
            IDb headerDb    = new MemDb();
            IDb blockInfoDb = new MemDb();

            BlockTree = new BlockTree(blockDb, headerDb, blockInfoDb, new ChainLevelInfoRepository(blockDb), SpecProvider, TxPool, NullBloomStorage.Instance, LimboLogs.Instance);

            ReceiptStorage = new InMemoryReceiptStorage();
            VirtualMachine virtualMachine = new VirtualMachine(State, Storage, new BlockhashProvider(BlockTree, LimboLogs.Instance), SpecProvider, LimboLogs.Instance);

            TxProcessor    = new TransactionProcessor(SpecProvider, State, Storage, virtualMachine, LimboLogs.Instance);
            BlockProcessor = CreateBlockProcessor();

            BlockchainProcessor chainProcessor = new BlockchainProcessor(BlockTree, BlockProcessor, new TxSignaturesRecoveryStep(EthereumEcdsa, TxPool, LimboLogs.Instance), LimboLogs.Instance, BlockchainProcessor.Options.Default);

            chainProcessor.Start();

            StateReader = new StateReader(StateDb, CodeDb, LimboLogs.Instance);
            TxPoolTxSource txPoolTxSource = new TxPoolTxSource(TxPool, StateReader, LimboLogs.Instance);
            ISealer        sealer         = new FakeSealer(TimeSpan.Zero);

            BlockProducer = new TestBlockProducer(txPoolTxSource, chainProcessor, State, sealer, BlockTree, chainProcessor, Timestamper, LimboLogs.Instance);
            BlockProducer.Start();

            _resetEvent             = new AutoResetEvent(false);
            BlockTree.NewHeadBlock += (s, e) =>
            {
                Console.WriteLine(e.Block.Header.Hash);
                _resetEvent.Set();
            };

            var genesis = GetGenesisBlock();

            BlockTree.SuggestBlock(genesis);
            await _resetEvent.WaitOneAsync(CancellationToken.None);

            await AddBlocksOnStart();

            return(this);
        }