比特币之 难度调整算法推导及实现

在这里插入图片描述

比特币工作证明基本原理:对比特币 整个区块头进行hash 再进行 比较 是否在 一个特定单位内 一个hash 的取值范围是 2 256 2^{256} 次方大小。

难度值(difficulty)是矿工们在挖矿时候的重要参考指标,它决定了矿工大约需要经过多少次哈希运算才能产生一个合法的区块。比特币的区块大约每10分钟生成一个,如果要在不同的全网算力条件下,新区块的产生保持都基本这个速率,难度值必须根据全网算力的变化进行调整。简单地说,难度值被设定在无论挖矿能力如何,新区块产生速率都保持在10分钟一个。

难度的调整是在每个完整节点中独立自动发生的。每2016个区块,所有节点都会按统一的公式自动调整难度,这个公式是由最新2016个区块的花费时长与期望时长(期望时长为20160分钟即两周,是按每10分钟一个区块的产生速率计算出的总时长)比较得出的,根据实际时长与期望时长的比值,进行相应调整(或变难或变易)。也就是说,如果区块产生的速率比10分钟快则增加难度,比10分钟慢则降低难度。

比特币是怎么自动调整挖矿难度的呢?
比特币是通过将交易头 hash256 生成64 位的hash 值 满足是否在这个区间来判断当前 矿工是否挖到矿的。假设 有0 ~ 15 的10个数 你要猜到 小于 3 的概率 是 2 16 = 1 8 \frac{2}{16} =\frac{1}{8}
那么学过计算机的小伙伴 应该知道16进制表示法 16 =f 就相当于2/F的概率 那如果是 256= 1 6 2 16^2 =ff
在这里插入图片描述
如上图, 假定一个范围 为 0~30 概率为 30/256 = 11.54% 如果要随机猜测的话 的话 平均 要 9次左右 才能 得到 想要的hash值 小于等于 0-30这个区间内。
但对于计算机来说 1GHZ主频大约就是每秒10亿次运算 来说 几百以内的猜数字 当然是小意思了 那怎么办 其实很简单 把数字范围加大不就好了吗
sha 256 生成的是 1.157920892373162e+77 这么大的范围区间 当你要在 一个 2 2 56 2^256 位的数字中 找一个特定的值 的概率是 1 2 256 \frac{1}{2^{256}} 大概是8.636168555094445e-78 也几乎是不可能的。 假设一台计算机每秒能猜 100亿个数字 1e10 那么要猜完全部的数字 大概是 3.67e+59 年 与此相比宇宙存在150亿年(1.5e10) 。
而 比特币 就是通过生成 这个 2 256 2^{256} 位的hash 值去计算 的
在这里插入图片描述
小于 2 128 2^{128} 都是正确的值 此时 可以取到的正确值的总数为 样本空间点数: 2 128 2^{128} 概率为 按照古典概型计算:样本空间点/全样本空间点 = 2 128 2 256 \frac{2^{128}}{2^{256}} =2.938735877055719e-39 可以看出 这个范围 越小 难度就越大。
假定初始的难度0x00000000FFFF0000000000000000000000000000000000000000000000000000 = 2.695953529101131e+67
那 概率是多大呢 有上面的公式 可以计算 2.695953529101131 e + 67 2 256 2.3282709094019083 e 10 \frac{2.695953529101131e+67}{2^{256}} \approx2.3282709094019083e-10
打个比方 假如你要买彩票 你要中奖概率 是100万分之1(概率) 那么 你就要买 100万次(次数) 假设 假设你买 100万次 需要 100年 那么你的买彩票效率 是 100万次/100年 = 1万次/年

由上面可以得出公式: 计算次数 * 概率 = 1

计算次数 = 算力 * 时间

比特币一般是10分钟1块 这个值是固定的,这样的话 每四年衰减一般,到2140 就全部挖完了。 算力 = 计算次数 / 时间
已知了概率 2.3282709094019083 e 10 2.3282709094019083e-10
那么计算次数 = 1 2.3282709094019083 e 10 \frac{1}{2.3282709094019083e-10} \approx =4295032833.000015
算力为 : 4295032833.000015 / 60/ 60 / 10 (化为10分钟) =119306.46758333375 次/分钟
但是随着矿机的添加 算力就会提升 算力提升了 那么 在时间不变的情况下 根据公式 计算次数 就会提升 计算次数 和 概率 又是成 反比的 那么 计算次数增加 那么概率 就会 变小 那么假设 全网 的算力 由 119306.46758333375到达了 219306.46758333375 那么 概率要怎么调整呢。
由公式得: 概率 = 1 / 计算次数
计算次数 = 219306.46758333375 * 10 * 60 * 60 = 7895032833.000015
那么可以反向推出 概率 = 1/ 计算次数 = 1.266619178352438e-10
那么剩下的问题就是 从 2 256 2^{256} 空间中选则的哪个样本空间是这个概率呢。

样本空间/全空间= 概率

样本空间 = 概率 * 全空间 = 1.266619178352438e-10 * 2 256 2^{256} = 1.4666448092948163e+67 转成16进制:

0X000000000ded374e381d4b800000000000000000000000000000000000000000 算力提升后的难度
0x00000000FFFF0000000000000000000000000000000000000000000000000000 初始难度

0X000000000ded374e381d4b8 = 1.4666448092948163e+67
0x00000000FFFF =2.695953529101131e+67

为了 更直观 我们舍去后面的 0 可以看到 调整后 的值明显变小了 而如果画在数轴上
在这里插入图片描述
就相当于范围变小了 范围变小了 就相当于 原来 你能买一百次 的 现在 只能买50次 那中奖概率肯定就会降低。假设 有个很能买彩票的人 人家 每年2万/次每年 那么这种方法 就相当于 达到修改概率 还是 100 年中奖。

基于以上推理我们得到以下公式 :
概率 = 1 / 计算次数
计算次数 = 算力 * 时间
目标难度 = 样本空间 = 概率 * 全空间

目标难度 = 样本空间 = 1 / 算力 * 时间 * 全空间
全空间为: 2 256 2^{256}
时间为:10 * 60 * 60 = 10分钟 (固定)
初始 的难度值 tt= 0x00000000FFFF0000000000000000000000000000000000000000000000000000 实际计算是获取前 n(比特币里是第前2016个block的bints 存储了 当时的难度值)个区块中 。

算力 = 计算次数/时间
计算次数 = 1/概率
得:算力 = 1/概率 /时间 = 1/概率*时间

= 1 / 1 / \frac{改变后目标难度}{改变前目标难度} = \frac{1 / 改变后算力 * 时间 * 全空间}{1 / 改变前算力 * 时间 * 全空间} = \frac{改变后算力}{改变前算力} = 1 / 1 / \frac{1/调整前概率*算力改变后时间 }{1/调整前概率*算力改变前时间} = \frac{算力改变后时间}{算力改变前时间}
所以得出: \frac{改变后目标难度}{改变前目标难度} = \frac{算力改变后时间}{算力改变前时间}
改变后目标难度 = 改变前目标难度 *\frac{算力改变后时间}{算力改变前时间}
算力改变后时间 就是前n个 比特币里设定2周 每 10分钟一个 就是2016个块实际运用时间
算力改变前时间控制在10分钟这个时间

所以总结就是 只要知道 前特定次 的实际花费时间 和 期望需要花费的时间 做个比值 再乘以 第那一次的 困难度 就可以了。

以下是代码:

		 public static void main(String[] args) {
		 public final static Integer nTargetTimespan = 14 * 24 * 60 * 60; //过去两周时间
		    public final static  Integer nTargetSpacing = 10 * 60; //期望挖矿时间
		    public final static Integer nInterval = nTargetTimespan / nTargetSpacing; //过去2周期望达到的区块数 2016个
		    public final static String num2 =new BigInteger("0FF0000000000000000000000000000000000000000000000000000000000000", 16).toString();//初始难度
  
        BigDecimal numb2 = new BigDecimal(num2);
        //实际时间
        float timeInterval = 1;
        float scale = timeInterval/nTargetTimespan;
        System.out.println("缩放比例:"+scale+"如果当前 的算力越大 花费时间越小 和 预期时间不变并成反比 缩放比例也就越小 ");
        //假设
        int precision = 0; // 0位小数
        BigDecimal NEWb = new BigDecimal(numb2.multiply(new BigDecimal(scale)).toString()).setScale(precision, RoundingMode.DOWN);
        String toHex = String.format("%#x",NEWb.toBigInteger());
        StringBuilder zeroHeader = new StringBuilder();
        /**
         * 下面代码自动 补足长度为64位 如 0x00028ccccbd80000000000000000000000000000000000000000000000000000
         */
        for(int i =0;i<64 - toHex.replace("0x","").length() +1;i++){
            zeroHeader.append("0");
        }
        for(int m =2;m< toHex.length() -1;m++){
            zeroHeader.append(toHex.charAt(m));
        }
        toHex = "0x" + zeroHeader.toString();

        System.out.println(toHex + "\n"+toHex.length() +"0x".length());
    }

比特币里 nbits字段 用来存放区块难度
Bits:0x18013ce9(以十六进制显示)
上面这个其实 是 0x0000000000000000013ce9000000000000000000000000000000000000000000 压缩格式。
主要就是将一长串00省去了。
比特币 难度字符串反压缩算法:target = coefficient * 0x02^(0x08 * (exponent – 0x03))

    public static String decodeBits(String nbits){
        if(nbits.startsWith("0x"))
            nbits = nbits.substring(2,nbits.length());
        int end= 0;
        int begin = 0;
        for(int i=0;i<nbits.length()-1;i++){
            char ch =nbits.charAt(i);
            if(ch !='0' && begin == 0){
                begin= i;//开始标记
            }
            //如果 当前字符是 0且 前几次都是零
            if(ch =='0' && begin ==0)
                continue;
            if(ch !='0')
                //记录最后一个非0 char的index
                end = i;
        }
        String co = "0"+nbits.substring(begin,end+1);
        BigDecimal traget =new BigDecimal(new BigInteger(nbits,16));
        BigDecimal coefficient =new BigDecimal(new BigInteger(co,16));
        Double xx = traget.divide(coefficient).doubleValue();
        System.out.println(traget.divide(coefficient));
        Double mm =Math.log(xx)/Math.log(2) / 0x08 + 0x03;
        return  "0x" + Integer.toHexString(mm.intValue()) + co;
    }

比特币 难度字符串压缩算法 :log(traget/coefficient ,0x02) /0x08 + 0x03

   public static String unDecodeBits(String nbits){
        String a = "0x1903a30c";
        String ex = a.substring(2,4);
        String co = a.substring(4,a.length());
        BigInteger exponent = new BigInteger(ex,16);
        BigInteger coefficient = new BigInteger(co,16);
        Double xx =exponent.subtract(new BigInteger("3")).multiply(new BigInteger("8")).doubleValue();
        System.out.println(xx);
        Double ax = Math.pow(2,xx);
        BigDecimal res = new BigDecimal(coefficient).multiply(new BigDecimal(ax));
        System.out.println(res.toBigInteger());
        String toHex = String.format("%#x.",res.toBigInteger());
        System.out.println(autoformat(toHex));
        return autoformat(toHex);
    }

    public static String autoformat(String toHex){
        StringBuilder zeroHeader = new StringBuilder();
        /**
         * 下面代码自动 补足长度为64位 如 0x00028ccccbd80000000000000000000000000000000000000000000000000000
         */
        for(int i =0;i<64 - toHex.replace("0x","").length() +1;i++){
            zeroHeader.append("0");
        }
        for(int m =2;m< toHex.length() -1;m++){
            zeroHeader.append(toHex.charAt(m));
        }
        toHex = "0x" + zeroHeader.toString();
        return toHex.toUpperCase();

    }
发布了67 篇原创文章 · 获赞 5 · 访问量 3178

猜你喜欢

转载自blog.csdn.net/weixin_41315492/article/details/103191225
今日推荐