Exemple #1
0
        /// <summary>
        /// Returns the max of the feerates calculated with a 60%
        /// threshold required at target / 2, an 85% threshold required at target and a
        /// 95% threshold required at 2 * target.Each calculation is performed at the
        /// shortest time horizon which tracks the required target.Conservative
        /// estimates, however, required the 95% threshold at 2 * target be met for any
        /// longer time horizons also.
        /// </summary>
        public FeeRate EstimateSmartFee(int confTarget, FeeCalculation feeCalc, bool conservative)
        {
            lock (this.lockObject)
            {
                if (feeCalc != null)
                {
                    feeCalc.DesiredTarget  = confTarget;
                    feeCalc.ReturnedTarget = confTarget;
                }

                double           median     = -1;
                EstimationResult tempResult = new EstimationResult();

                // Return failure if trying to analyze a target we're not tracking
                if (confTarget <= 0 || confTarget > this.longStats.GetMaxConfirms())
                {
                    return(new FeeRate(0));  // error condition
                }

                // It's not possible to get reasonable estimates for confTarget of 1
                if (confTarget == 1)
                {
                    confTarget = 2;
                }

                int maxUsableEstimate = MaxUsableEstimate();
                if (confTarget > maxUsableEstimate && maxUsableEstimate > 1)
                {
                    confTarget = maxUsableEstimate;
                }
                if (feeCalc != null)
                {
                    feeCalc.ReturnedTarget = confTarget;
                }

                if (confTarget <= 1)
                {
                    return(new FeeRate(0)); // error condition
                }

                Guard.Assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints

                // true is passed to estimateCombined fee for target/2 and target so
                // that we check the max confirms for shorter time horizons as well.
                // This is necessary to preserve monotonically increasing estimates.
                // For non-conservative estimates we do the same thing for 2*target, but
                // for conservative estimates we want to skip these shorter horizons
                // checks for 2*target because we are taking the max over all time
                // horizons so we already have monotonically increasing estimates and
                // the purpose of conservative estimates is not to let short term
                // fluctuations lower our estimates by too much.
                double halfEst = EstimateCombinedFee(confTarget / 2, HalfSuccessPct, true, tempResult);
                if (feeCalc != null)
                {
                    feeCalc.Estimation = tempResult;
                    feeCalc.Reason     = FeeReason.HalfEstimate;
                }
                median = halfEst;
                double actualEst = EstimateCombinedFee(confTarget, SuccessPct, true, tempResult);
                if (actualEst > median)
                {
                    median = actualEst;
                    if (feeCalc != null)
                    {
                        feeCalc.Estimation = tempResult;
                        feeCalc.Reason     = FeeReason.FullEstimate;
                    }
                }
                double doubleEst = EstimateCombinedFee(2 * confTarget, DoubleSuccessPct, !conservative, tempResult);
                if (doubleEst > median)
                {
                    median = doubleEst;
                    if (feeCalc != null)
                    {
                        feeCalc.Estimation = tempResult;
                        feeCalc.Reason     = FeeReason.DoubleEstimate;
                    }
                }

                if (conservative || median == -1)
                {
                    double consEst = EstimateConservativeFee(2 * confTarget, tempResult);
                    if (consEst > median)
                    {
                        median = consEst;
                        if (feeCalc != null)
                        {
                            feeCalc.Estimation = tempResult;
                            feeCalc.Reason     = FeeReason.Coservative;
                        }
                    }
                }

                if (median < 0)
                {
                    return(new FeeRate(0));            // error condition
                }
                return(new FeeRate(Convert.ToInt64(median)));
            }
        }
Exemple #2
0
        /// <summary>
        /// Calculate a feerate estimate.  Find the lowest value bucket (or range of buckets
        /// to make sure we have enough data points) whose transactions still have sufficient likelihood
        /// of being confirmed within the target number of confirmations.
        /// </summary>
        /// <param name="confTarget">Target number of confirmations.</param>
        /// <param name="sufficientTxVal">Required average number of transactions per block in a bucket range.</param>
        /// <param name="successBreakPoint">The success probability we require.</param>
        /// <param name="requireGreater">Return the lowest feerate such that all higher values pass minSuccess OR return the highest feerate such that all lower values fail minSuccess.</param>
        /// <param name="nBlockHeight">The current block height.</param>
        /// <returns></returns>
        public double EstimateMedianVal(int confTarget, double sufficientTxVal, double successBreakPoint,
                                        bool requireGreater, int nBlockHeight, EstimationResult result)
        {
            // Counters for a bucket (or range of buckets)
            double nConf        = 0; // Number of tx's confirmed within the confTarget
            double totalNum     = 0; // Total number of tx's that were ever confirmed
            int    extraNum     = 0; // Number of tx's still in mempool for confTarget or longer
            double failNum      = 0; // Number of tx's that were never confirmed but removed from the mempool after confTarget
            int    periodTarget = (confTarget + this.scale - 1) / this.scale;

            int maxbucketindex = this.buckets.Count - 1;

            // requireGreater means we are looking for the lowest feerate such that all higher
            // values pass, so we start at maxbucketindex (highest feerate) and look at successively
            // smaller buckets until we reach failure.  Otherwise, we are looking for the highest
            // feerate such that all lower values fail, and we go in the opposite direction.
            int startbucket = requireGreater ? maxbucketindex : 0;
            int step        = requireGreater ? -1 : 1;

            // We'll combine buckets until we have enough samples.
            // The near and far variables will define the range we've combined
            // The best variables are the last range we saw which still had a high
            // enough confirmation rate to count as success.
            // The cur variables are the current range we're counting.
            int curNearBucket  = startbucket;
            int bestNearBucket = startbucket;
            int curFarBucket   = startbucket;
            int bestFarBucket  = startbucket;

            bool            foundAnswer    = false;
            int             bins           = this.unconfTxs.Count;
            bool            newBucketRange = true;
            bool            passing        = true;
            EstimatorBucket passBucket     = new EstimatorBucket();;
            EstimatorBucket failBucket     = new EstimatorBucket();

            // Start counting from highest(default) or lowest feerate transactions
            for (int bucket = startbucket; bucket >= 0 && bucket <= maxbucketindex; bucket += step)
            {
                if (newBucketRange)
                {
                    curNearBucket  = bucket;
                    newBucketRange = false;
                }
                curFarBucket = bucket;
                nConf       += this.confAvg[periodTarget - 1][bucket];
                totalNum    += this.txCtAvg[bucket];
                failNum     += this.failAvg[periodTarget - 1][bucket];
                for (int confct = confTarget; confct < this.GetMaxConfirms(); confct++)
                {
                    extraNum += this.unconfTxs[Math.Abs(nBlockHeight - confct) % bins][bucket];
                }
                extraNum += this.oldUnconfTxs[bucket];
                // If we have enough transaction data points in this range of buckets,
                // we can test for success
                // (Only count the confirmed data points, so that each confirmation count
                // will be looking at the same amount of data and same bucket breaks)
                if (totalNum >= sufficientTxVal / (1 - this.decay))
                {
                    double curPct = nConf / (totalNum + failNum + extraNum);

                    // Check to see if we are no longer getting confirmed at the success rate
                    if ((requireGreater && curPct < successBreakPoint) ||
                        (!requireGreater && curPct > successBreakPoint))
                    {
                        if (passing == true)
                        {
                            // First time we hit a failure record the failed bucket
                            int failMinBucket = Math.Min(curNearBucket, curFarBucket);
                            int failMaxBucket = Math.Max(curNearBucket, curFarBucket);
                            failBucket.Start          = failMinBucket > 0 ? this.buckets[failMinBucket - 1] : 0;
                            failBucket.End            = this.buckets[failMaxBucket];
                            failBucket.WithinTarget   = nConf;
                            failBucket.TotalConfirmed = totalNum;
                            failBucket.InMempool      = extraNum;
                            failBucket.LeftMempool    = failNum;
                            passing = false;
                        }
                        continue;
                    }

                    // Otherwise update the cumulative stats, and the bucket variables
                    // and reset the counters
                    else
                    {
                        failBucket              = new EstimatorBucket(); // Reset any failed bucket, currently passing
                        foundAnswer             = true;
                        passing                 = true;
                        passBucket.WithinTarget = nConf;
                        nConf = 0;
                        passBucket.TotalConfirmed = totalNum;
                        totalNum               = 0;
                        passBucket.InMempool   = extraNum;
                        passBucket.LeftMempool = failNum;
                        failNum        = 0;
                        extraNum       = 0;
                        bestNearBucket = curNearBucket;
                        bestFarBucket  = curFarBucket;
                        newBucketRange = true;
                    }
                }
            }

            double median = -1;
            double txSum  = 0;

            // Calculate the "average" feerate of the best bucket range that met success conditions
            // Find the bucket with the median transaction and then report the average feerate from that bucket
            // This is a compromise between finding the median which we can't since we don't save all tx's
            // and reporting the average which is less accurate
            int minBucket = Math.Min(bestNearBucket, bestFarBucket);
            int maxBucket = Math.Max(bestNearBucket, bestFarBucket);

            for (int j = minBucket; j <= maxBucket; j++)
            {
                txSum += this.txCtAvg[j];
            }
            if (foundAnswer && txSum != 0)
            {
                txSum = txSum / 2;
                for (int j = minBucket; j <= maxBucket; j++)
                {
                    if (this.txCtAvg[j] < txSum)
                    {
                        txSum -= this.txCtAvg[j];
                    }
                    else
                    {
                        // we're in the right bucket
                        median = this.avg[j] / this.txCtAvg[j];
                        break;
                    }
                }
                passBucket.Start = minBucket > 0 ? this.buckets[minBucket - 1] : 0;
                passBucket.End   = this.buckets[maxBucket];
            }

            // If we were passing until we reached last few buckets with insufficient data, then report those as failed
            if (passing && !newBucketRange)
            {
                int failMinBucket = Math.Min(curNearBucket, curFarBucket);
                int failMaxBucket = Math.Max(curNearBucket, curFarBucket);
                failBucket.Start          = failMinBucket > 0 ? this.buckets[failMinBucket - 1] : 0;
                failBucket.End            = this.buckets[failMaxBucket];
                failBucket.WithinTarget   = nConf;
                failBucket.TotalConfirmed = totalNum;
                failBucket.InMempool      = extraNum;
                failBucket.LeftMempool    = failNum;
            }

            this.logger.LogInformation(
                $"FeeEst: {confTarget} {(requireGreater ? $">" : $"<")} " +
                $"{successBreakPoint} decay {this.decay} feerate: {median}" +
                $" from ({passBucket.Start} - {passBucket.End}" +
                $" {100 * passBucket.WithinTarget / (passBucket.TotalConfirmed + passBucket.InMempool + passBucket.LeftMempool)}" +
                $" {passBucket.WithinTarget}/({passBucket.TotalConfirmed}" +
                $" {passBucket.InMempool} mem {passBucket.LeftMempool} out) " +
                $"Fail: ({failBucket.Start} - {failBucket.End} " +
                $"{100 * failBucket.WithinTarget / (failBucket.TotalConfirmed + failBucket.InMempool + failBucket.LeftMempool)}" +
                $" {failBucket.WithinTarget}/({failBucket.TotalConfirmed}" +
                $" {failBucket.InMempool} mem {failBucket.LeftMempool} out)");

            if (result != null)
            {
                result.Pass  = passBucket;
                result.Fail  = failBucket;
                result.Decay = this.decay;
                result.Scale = this.scale;
            }
            return(median);
        }
Exemple #3
0
        /// <summary>
        /// Return an estimate fee according to horizon
        /// </summary>
        /// <param name="confTarget">The desired number of confirmations to be included in a block</param>
        public FeeRate EstimateRawFee(int confTarget, double successThreshold, FeeEstimateHorizon horizon, EstimationResult result)
        {
            TxConfirmStats stats;
            double         sufficientTxs = SufficientFeeTxs;

            switch (horizon)
            {
            case FeeEstimateHorizon.ShortHalfLife:
            {
                stats         = this.shortStats;
                sufficientTxs = SufficientTxsShort;
                break;
            }

            case FeeEstimateHorizon.MedHalfLife:
            {
                stats = this.feeStats;
                break;
            }

            case FeeEstimateHorizon.LongHalfLife:
            {
                stats = this.longStats;
                break;
            }

            default:
            {
                throw new ArgumentException(nameof(horizon));
            }
            }
            lock (this.lockObject)
            {
                // Return failure if trying to analyze a target we're not tracking
                if (confTarget <= 0 || confTarget > stats.GetMaxConfirms())
                {
                    return(new FeeRate(0));
                }
                if (successThreshold > 1)
                {
                    return(new FeeRate(0));
                }

                double median = stats.EstimateMedianVal(confTarget, sufficientTxs, successThreshold,
                                                        true, this.nBestSeenHeight, result);

                if (median < 0)
                {
                    return(new FeeRate(0));
                }

                return(new FeeRate(Convert.ToInt64(median)));
            }
        }