protected virtual byte[] SerializeHeader(uint nTime, byte[] extraNonce, uint nonce)
        {
            byte[] serialized;

            using (var stream = new MemoryStream())
            {
                var bs = new BitcoinStream(stream, true);
                BlockHeader.ReadWrite(bs);
                serialized = stream.ToArray();
            }


            var tmpBlockHeader = new CommerciumBlockHeader(serialized);

            tmpBlockHeader.Timestamp = nTime;
            tmpBlockHeader.Nonce     = nonce;
            Array.Resize(ref extraNonce, 32);
            tmpBlockHeader.ExtraData = extraNonce;

            byte[] serializedTmp;
            using (var stream = new MemoryStream())
            {
                var bs = new BitcoinStream(stream, true);
                tmpBlockHeader.ReadWriteWithoutSolution(bs);

                serializedTmp = stream.ToArray();
            }

            return(serializedTmp);
        }
        public virtual void Init(CommerciumGetWork work, string jobId,
                                 PoolConfig poolConfig, ClusterConfig clusterConfig, IMasterClock clock,
                                 IDestination poolAddressDestination, BitcoinNetworkType networkType,
                                 double shareMultiplier, decimal blockrewardMultiplier, IHashAlgorithm headerHasher)
        {
            Contract.RequiresNonNull(work, nameof(work));
            Contract.RequiresNonNull(poolConfig, nameof(poolConfig));
            Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig));
            Contract.RequiresNonNull(clock, nameof(clock));
            Contract.RequiresNonNull(poolAddressDestination, nameof(poolAddressDestination));
            Contract.RequiresNonNull(headerHasher, nameof(headerHasher));
            Contract.Requires <ArgumentException>(!string.IsNullOrEmpty(jobId), $"{nameof(jobId)} must not be empty");

            this.clock = clock;

            if (CommerciumConstants.Chains.TryGetValue(poolConfig.Coin.Type, out var chain))
            {
                chain.TryGetValue(networkType, out chainConfig);
            }

            Work        = work;
            BlockHeader = new CommerciumBlockHeader(work.Data);
            JobId       = jobId;

            this.shareMultiplier = shareMultiplier;
            this.headerHasher    = headerHasher;

            blockTarget = new Target(BlockHeader.Bits);

            Difficulty = chainConfig.Diff1.Divide(blockTarget.ToBigInteger()).LongValue;

            BuildCoinbase();

            jobParams = new object[]
            {
                JobId,
                BlockHeader.PrevBlock.ToHexString(),
                coinbaseInitialHex,
                coinbaseFinalHex,
                "",
                BlockHeader.Version.ToStringHex8().HexToByteArray().ReverseArray().ToHexString(),
                BlockHeader.Bits.ReverseByteOrder().ToStringHex8(),
                BlockHeader.Timestamp.ReverseByteOrder().ToStringHex8(),
                false
            };
        }
        protected virtual async Task <(bool IsNew, bool Force)> UpdateJob(bool forceUpdate, string via = null, string json = null)
        {
            logger.LogInvoke(LogCat);

            try
            {
                if (forceUpdate)
                {
                    _lastJobRebroadcast = _clock.Now;
                }

                var response = string.IsNullOrEmpty(json) ?
                               await GetWorkAsync() :
                               GetWorkFromJson(json);

                // may happen if daemon is currently not connected to peers
                if (response.Error != null)
                {
                    logger.Warn(() => $"[{LogCat}] Unable to update job. Daemon responded with: {response.Error.Message} Code {response.Error.Code}");
                    return(false, forceUpdate);
                }

                var work        = response.Response;
                var blockHeader = new CommerciumBlockHeader(work.Data);

                var job   = currentJob;
                var isNew = job == null ||
                            (job.BlockHeader?.PrevBlock != blockHeader.PrevBlock &&
                             blockHeader.Height > job.BlockHeader?.Height);

                if (isNew || forceUpdate)
                {
                    job = new CommerciumJob();

                    job.Init(work, NextJobId(),
                             poolConfig, clusterConfig, _clock, _poolAddressDestination, _networkType,
                             ShareMultiplier, _extraPoolPaymentProcessingConfig?.BlockrewardMultiplier ?? 1.0m,
                             _headerHasher);

                    lock (jobLock)
                    {
                        if (isNew)
                        {
                            if (via != null)
                            {
                                logger.Info(() => $"[{LogCat}] Detected new block {blockHeader.Height} via {via}");
                            }
                            else
                            {
                                logger.Info(() => $"[{LogCat}] Detected new block {blockHeader.Height}");
                            }

                            _validJobs.Clear();

                            // update stats
                            BlockchainStats.LastNetworkBlockTime = _clock.Now;
                            BlockchainStats.BlockHeight          = blockHeader.Height;
                            BlockchainStats.NetworkDifficulty    = job.Difficulty;
                        }

                        else
                        {
                            // trim active jobs
                            while (_validJobs.Count > _maxActiveJobs - 1)
                            {
                                _validJobs.RemoveAt(0);
                            }
                        }

                        _validJobs.Add(job);
                    }

                    currentJob = job;
                }

                return(isNew, forceUpdate);
            }

            catch (Exception ex)
            {
                logger.Error(ex, () => $"[{LogCat}] Error during {nameof(UpdateJob)}");
            }

            return(false, forceUpdate);
        }