private void OnShare(object sender, EventArgs e)
        {
            var shareArgs = (ShareEventArgs)e;
            var miner     = shareArgs.Miner;

            if (miner == null)
            {
                return;
            }

            int now = TimeHelpers.NowInUnixTime();

            if (miner.VardiffBuffer == null)
            {
                miner.LastVardiffRetarget  = now - (Config.RetargetTime / 2);
                miner.LastVardiffTimestamp = now;
                miner.VardiffBuffer        = new RingBuffer(_bufferSize);
                return;
            }

            var sinceLast = now - miner.LastVardiffTimestamp; // how many seconds elapsed since last share?

            miner.VardiffBuffer.Append(sinceLast);            // append it to vardiff buffer.
            miner.LastVardiffTimestamp = now;

            if (now - miner.LastVardiffRetarget < Config.RetargetTime && miner.VardiffBuffer.Size > 0) // check if we need a re-target.
            {
                return;
            }

            miner.LastVardiffRetarget = now;
            var average   = miner.VardiffBuffer.Average;
            var deltaDiff = Config.TargetTime / average;

            if (average > _tMax && miner.Difficulty > Config.MinimumDifficulty)
            {
                if (deltaDiff * miner.Difficulty < Config.MinimumDifficulty)
                {
                    deltaDiff = Config.MinimumDifficulty / miner.Difficulty;
                }
            }
            else if (average < _tMin)
            {
                if (deltaDiff * miner.Difficulty > Config.MaximumDifficulty)
                {
                    deltaDiff = Config.MaximumDifficulty / miner.Difficulty;
                }
            }
            else
            {
                return;
            }

            var newDifficulty = miner.Difficulty * deltaDiff; // calculate the new difficulty.

            miner.SetDifficulty(newDifficulty);               // set the new difficulty and send it.
            _logger.Debug("Difficulty updated to {0} for miner: {1:l}", miner.Difficulty, miner.Username);

            miner.VardiffBuffer.Clear();
        }
        public void AddShare(IShare share)
        {
            try
            {
                if (!IsEnabled || !_redisProvider.IsConnected)
                {
                    return;
                }

                //_client.StartPipe(); // batch the commands.

                // add the share to round
                var currentKey = string.Format("{0}:shares:round:current", _coin);
                _redisProvider.Client.HIncrByFloat(currentKey, share.Miner.Username, share.Difficulty);

                // increment shares stats.
                var statsKey = string.Format("{0}:stats", _coin);
                _redisProvider.Client.HIncrBy(statsKey, share.IsValid ? "validShares" : "invalidShares", 1);

                // add to hashrate
                if (share.IsValid)
                {
                    var hashrateKey = string.Format("{0}:hashrate", _coin);
                    var entry       = string.Format("{0}:{1}", share.Difficulty, share.Miner.Username);
                    _redisProvider.Client.ZAdd(hashrateKey, Tuple.Create(TimeHelpers.NowInUnixTime(), entry));
                }

                //_client.EndPipe(); // execute the batch commands.
            }
            catch (Exception e)
            {
                _logger.Error("An exception occured while comitting share: {0:l}", e.Message);
            }
        }
Beispiel #3
0
        private void ReadHashrate()
        {
            // read hashrate stats.
            var windowTime = TimeHelpers.NowInUnixTime() - _statisticsConfig.HashrateWindow;

            _storage.DeleteExpiredHashrateData(windowTime);
            var hashrates = _storage.GetHashrateData(windowTime);

            double total = hashrates.Sum(pair => pair.Value);

            Hashrate = Convert.ToUInt64(_shareMultiplier * total / _statisticsConfig.HashrateWindow);
        }
Beispiel #4
0
        /// <summary>
        /// Creates a new instance of generation transaction.
        /// </summary>
        /// <param name="extraNonce">The extra nonce.</param>
        /// <param name="daemonClient">The daemon client.</param>
        /// <param name="blockTemplate">The block template.</param>
        /// <param name="poolConfig">The associated pool's configuration</param>
        /// <remarks>
        /// Reference implementations:
        /// https://github.com/zone117x/node-stratum-pool/blob/b24151729d77e0439e092fe3a1cdbba71ca5d12e/lib/transactions.js
        /// https://github.com/Crypto-Expert/stratum-mining/blob/master/lib/coinbasetx.py
        /// </remarks>
        public GenerationTransaction(IExtraNonce extraNonce, IDaemonClient daemonClient, IBlockTemplate blockTemplate, IPoolConfig poolConfig)
        {
            // TODO: we need a whole refactoring here.
            // we should use DI and it shouldn't really require daemonClient connection to function.

            BlockTemplate = blockTemplate;
            ExtraNonce    = extraNonce;
            PoolConfig    = poolConfig;

            Version   = (UInt32)(poolConfig.Coin.SupportsTxMessages ? 2 : 1);
            TxMessage = Serializers.SerializeString(poolConfig.Meta.TxMessage);
            LockTime  = 0;

            // transaction inputs
            Inputs = new List <TxIn>
            {
                new TxIn
                {
                    PreviousOutput = new OutPoint
                    {
                        Hash  = Hash.ZeroHash,
                        Index = (UInt32)Math.Pow(2, 32) - 1
                    },
                    Sequence        = 0x0,
                    SignatureScript =
                        new SignatureScript(
                            blockTemplate.Height,
                            blockTemplate.CoinBaseAux.Flags,
                            TimeHelpers.NowInUnixTime(),
                            (byte)extraNonce.ExtraNoncePlaceholder.Length,
                            "/CoiniumServ/")
                }
            };

            // transaction outputs
            Outputs = new Outputs(daemonClient);

            double blockReward = BlockTemplate.Coinbasevalue; // the amount rewarded by the block.

            // generate output transactions for recipients (set in config).
            foreach (var pair in poolConfig.Rewards)
            {
                var amount = blockReward * pair.Value / 100; // calculate the amount he recieves based on the percent of his shares.
                blockReward -= amount;

                Outputs.AddRecipient(pair.Key, amount);
            }

            // send the remaining coins to pool's central wallet.
            Outputs.AddPoolWallet(poolConfig.Wallet.Adress, blockReward);
        }
        private bool BanExpired(IPAddress ip)
        {
            var banTime = _bannedIps[ip];

            var elapsedTime = TimeHelpers.NowInUnixTime() - banTime; // elapsed time since his ban.
            var timeLeft    = Config.Duration - elapsedTime;         // time left for his ban

            if (timeLeft > 0)                                        // if he has still remaining time
            {
                return(false);
            }

            return(true);
        }
        private void Ban(IMiner miner)
        {
            // TODO: add vanilla miners to banlist too.
            if (miner is IVanillaMiner) // as vanilla miners doesn't use persistent connections, we don't need to disconect him
            {
                return;                 // but just blacklist his ip.
            }
            var client = (IClient)miner;
            var ip     = client.Connection.RemoteEndPoint.Address;

            if (!_bannedIps.ContainsKey(ip))
            {
                _bannedIps.Add(ip, TimeHelpers.NowInUnixTime());
            }

            client.Connection.Disconnect();
        }
Beispiel #7
0
        private void CleanUp(object state)
        {
            var startingCount = _jobs.Count;

            // calculate the cleanup delta time - jobs created before this will be cleaned up.
            var delta = TimeHelpers.NowInUnixTime() - _cleanupFrequency;

            // find expired jobs that were created before our calcualted delta time.
            _jobs = _jobs.Where(j => j.Value.CreationTime >= delta || j.Value == Current)
                    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

            var cleanedCount = startingCount - _jobs.Count;

            if (cleanedCount > 0)
            {
                _logger.Debug("Cleaned-up {0} expired jobs", cleanedCount);
            }

            _cleanupTimer.Change(_cleanupFrequency * 1000, Timeout.Infinite); // reset the cleanup timer.
        }
Beispiel #8
0
        /// <summary>
        /// Creates a new instance of JobNotification.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="algorithm"></param>
        /// <param name="blockTemplate"></param>
        /// <param name="generationTransaction"></param>
        public Job(UInt64 id, IHashAlgorithm algorithm, IBlockTemplate blockTemplate, IGenerationTransaction generationTransaction)
        {
            // init the values.
            Id                        = id;
            HashAlgorithm             = algorithm;
            BlockTemplate             = blockTemplate;
            Height                    = blockTemplate.Height;
            GenerationTransaction     = generationTransaction;
            PreviousBlockHash         = blockTemplate.PreviousBlockHash.HexToByteArray().ToHexString();
            PreviousBlockHashReversed = blockTemplate.PreviousBlockHash.HexToByteArray().ReverseByteOrder().ToHexString();
            CoinbaseInitial           = generationTransaction.Initial.ToHexString();
            CoinbaseFinal             = generationTransaction.Final.ToHexString();
            CreationTime              = TimeHelpers.NowInUnixTime();

            _shares = new List <UInt64>();

            // calculate the merkle tree
            MerkleTree = new MerkleTree(BlockTemplate.Transactions.GetHashList());

            // set version
            Version = BitConverter.GetBytes(blockTemplate.Version.BigEndian()).ToHexString();

            // set the encoded difficulty (bits)
            EncodedDifficulty = blockTemplate.Bits;

            // set the target
            Target = string.IsNullOrEmpty(blockTemplate.Target)
                ? EncodedDifficulty.BigIntFromBitsHex()
                : BigInteger.Parse(blockTemplate.Target, NumberStyles.HexNumber);

            // set the block diff
            Difficulty = ((double)new BigRational(AlgorithmManager.Diff1, Target));

            // set the ntime
            NTime = BitConverter.GetBytes(blockTemplate.CurTime.BigEndian()).ToHexString();
        }
Beispiel #9
0
        public Share(IStratumMiner miner, UInt64 jobId, IJob job, string extraNonce2, string nTimeString, string nonceString)
        {
            Miner = miner;
            JobId = jobId;
            Job   = job;
            Error = ShareError.None;

            var submitTime = TimeHelpers.NowInUnixTime(); // time we recieved the share from miner.

            if (Job == null)
            {
                Error = ShareError.JobNotFound;
                return;
            }

            // check size of miner supplied extraNonce2
            if (extraNonce2.Length / 2 != ExtraNonce.ExpectedExtraNonce2Size)
            {
                Error = ShareError.IncorrectExtraNonce2Size;
                return;
            }
            ExtraNonce2 = Convert.ToUInt32(extraNonce2, 16); // set extraNonce2 for the share.

            // check size of miner supplied nTime.
            if (nTimeString.Length != 8)
            {
                Error = ShareError.IncorrectNTimeSize;
                return;
            }
            NTime = Convert.ToUInt32(nTimeString, 16); // read ntime for the share

            // make sure NTime is within range.
            if (NTime < job.BlockTemplate.CurTime || NTime > submitTime + 7200)
            {
                Error = ShareError.NTimeOutOfRange;
                return;
            }

            // check size of miner supplied nonce.
            if (nonceString.Length != 8)
            {
                Error = ShareError.IncorrectNonceSize;
                return;
            }
            Nonce = Convert.ToUInt32(nonceString, 16); // nonce supplied by the miner for the share.

            // set job supplied parameters.
            Height      = job.BlockTemplate.Height; // associated job's block height.
            ExtraNonce1 = miner.ExtraNonce;         // extra nonce1 assigned to miner.

            // check for duplicate shares.
            if (!Job.RegisterShare(this)) // try to register share with the job and see if it's duplicated or not.
            {
                Error = ShareError.DuplicateShare;
                return;
            }

            // construct the coinbase.
            CoinbaseBuffer = Serializers.SerializeCoinbase(Job, ExtraNonce1, ExtraNonce2);
            CoinbaseHash   = Coin.Coinbase.Utils.HashCoinbase(CoinbaseBuffer);

            // create the merkle root.
            MerkleRoot = Job.MerkleTree.WithFirst(CoinbaseHash).ReverseBuffer();

            // create the block headers
            HeaderBuffer = Serializers.SerializeHeader(Job, MerkleRoot, NTime, Nonce);
            HeaderHash   = Job.HashAlgorithm.Hash(HeaderBuffer, miner.Pool.Config.Coin.Options);
            HeaderValue  = new BigInteger(HeaderHash);

            // calculate the share difficulty
            Difficulty = ((double)new BigRational(AlgorithmManager.Diff1, HeaderValue)) * Job.HashAlgorithm.Multiplier;

            // calculate the block difficulty
            BlockDiffAdjusted = Job.Difficulty * Job.HashAlgorithm.Multiplier;

            // check if block candicate
            if (Job.Target >= HeaderValue)
            {
                IsBlockCandidate = true;
                BlockHex         = Serializers.SerializeBlock(Job, HeaderBuffer, CoinbaseBuffer, miner.Pool.Config.Coin.IsPOS);
                BlockHash        = HeaderBuffer.DoubleDigest().ReverseBuffer();
            }
            else
            {
                IsBlockCandidate = false;
                BlockHash        = HeaderBuffer.DoubleDigest().ReverseBuffer();

                // Check if share difficulty reaches miner difficulty.
                var lowDifficulty = Difficulty / miner.Difficulty < 0.99; // share difficulty should be equal or more then miner's target difficulty.

                if (!lowDifficulty)                                       // if share difficulty is high enough to match miner's current difficulty.
                {
                    return;                                               // just accept the share.
                }
                if (Difficulty >= miner.PreviousDifficulty)               // if the difficulty matches miner's previous difficulty before the last vardiff triggered difficulty change
                {
                    return;                                               // still accept the share.
                }
                // if the share difficulty can't match miner's current difficulty or previous difficulty
                Error = ShareError.LowDifficultyShare; // then just reject the share with low difficult share error.
            }
        }