基于Java语言构建区块链(二)—— 工作量证明

2023-10-27

最终内容请以原文为准:https://wangwei.one/posts/7890ab7e.html

引言

上一篇文章中,我们实现了区块链最基本的数据结构模型,添加区块以及和前一个区块连接在一起。但是,我们的实现方式非常简单,而真实的比特币区块链中,每一个区块的添加都是需要经过大量的计算才可以完成,这个过程就是我们熟知的挖矿

工作量证明机制

区块链最关键的一个思想就是,必须进行大量且困难的计算工作才能将交易数据存放到区块链上。这种工作机制才能保证整个区块链数据的安全性和一致性。同时,完成这个计算工作的矿工会得到相应的Token奖励。

这套机制和我们的现实生活非常相似:我们必须努力工作来赚取报酬用以维持我们的生活。在区块链中,网络中的矿工们努力工作来维持区块链网络,为其添加区块,并且获得一定的Token奖励。作为他们工作的成果,一个区块以安全的方式被组合进了区块链中,这样就保证了整个区块链数据库的稳定性。还有一个必须要注意的是,某个矿工完成了计算工作的结果,还必须得到其他所有矿工的认同(证明是正确的),这样才算完成。

这一整套的计算和证明机制,就称为Proof-of-Work(工作量证明)。计算工作是非常非常困难的,因为它需要消耗大量的计算机算力资源,即使是性能非常高的计算机都不能非常快地计算出正确的结果。此外,随着时间的推移,这项计算工作的难度也会随之增加,目的是为了保证每小时6个新区块的出块率。在比特币中,这种工作的目标是找到满足某个特定要求的区块Hash(哈希值)。这个区块哈希值就是工作结果的一个证明。因此,计算工作的目的就是为了寻找到这个证明值。

最后要注意的是,计算出这个特定的Hash(哈希值)是非常困难的,但是别人来验证这个Hash值是否正确的时候,是非常简单的,一下子就能完成。

Hashing

Hash:哈希 | 散列

我们来讨论一下Hashing(哈希),对这一块非常熟悉的朋友可以直接跳过这一段内容。

哈希是一种计算机算法,该算法能够计算出任意大小数据的哈希值,并且这个哈希值的长度是固定的,256bit。这个被计算出来的哈希值能够作为这个数据的唯一代表。哈希算法有几个关键的特性:

  • 不可逆性。不能根据一个哈希值推导出原始数据。所以,哈希不是加密。
  • 唯一性。每个数据有且仅有一个唯一的哈希值。
  • 迥异性。原始数据一丁点的变化都将得到完全不一样的哈希值。

例如:

SHA256("wangwei1") ——> 1e898b7c9adaad86c20139a302ccd5277f81040cab68dc2aecfc684773532652
SHA256("wangwei2") ——> c9cc7417c17318c8aab448cc8ace24c53b6dcf350f5c5fd8e91cbc3b011a179d
复制代码

哈希算法被广泛用于验证文件的一致性上。比如软件提供商通常会在安装包上附加一个检验码(checksums),当我们下载完一个软件安装包后,可以用哈希函数计算一下这个软件安装包的哈希值,然后再和软件安装包的检验码做个对比,就可以知道下载的安装包是否完整、是否有数据丢失。

在区块链中,哈希值用于保证区块的一致性。每一个区块被用于进行哈希计算的数据,都包含前一个区块链的哈希值,因此任何人想要修改区块的数据几乎是不可能的,他必须要把整个区块链中从创世区块到最新的区块的所有哈希值全部重新计算一遍。

你可以脑补一下这个工作量有多大,按照目前计算机的算力来看,几乎不可能

Hashcash

比特币的工作量证明是使用的是Hashcash算法,一种最初被用于反垃圾邮件的算法,它可以被拆解为以下几步:

  1. 获取某种公开可知的数据data(在邮件案例中,指的是收件人邮件地址;比特币案例中,指的是区块头)
  2. 添加一个计数器counter,初始值设置为0;
  3. 计算 data 与 counter拼接字符串的哈希值;
  4. 检查上一步的哈希值是否满足某个条件,满足则停止计算,不满足则 counter 加1,然后重复第3步和第4步,直到满足这个特定的条件为止。

这是一种粗暴的算法:你改变计数器,计算一个新的哈希值,检查它,增加计数器,计算一个新的哈希值,循环往复,这就是为什么它需要花费大量计算机算力资源的原因所在。

让我们来近距离看一下这个特定的条件指的是什么。在原始的Hashcash算法中,这个特殊的要求指的是计算出来的哈希值的前20bit必须全是零,

在比特币种,这种要求哈希值前面有多少个零打头的要求是随着时间的推移而不断调整的,这是出于设计的目的,尽管在计算机的算力会不断的提升和越来越多的矿工加入这个网络中的情况下,都要保证每10min生产一个区块。

我们演示一下这个算法,

# 计算字符串'I like donuts'的哈希值
SHA256("I like donuts") 
——> f80867f6efd4484c23b0e7184e53fe4af6ab49b97f5293fcd50d5b2bfa73a4d0

# 拼接一个计数器值(ca07ca),再次进行Hash计算
SHA256("I like donutsca07ca") 
——> 0000002f7c1fe31cb82acdc082cfec47620b7e4ab94f2bf9e096c436fc8cee06
复制代码

这里的ca07ca是计数器值的十六进制,他表示的十进制值为13240266

即,从0开始,总共计算了13240266次,才计算出I like donuts这个数据的Hash值,满足前6位(3字节)全是零。

代码实现

思路:

1)每次区块被添加到区块链之前,先要进行挖矿(Pow)

2)挖矿过程中,产生的 Hash 值,如果小于难度目标值则添加进区块,否则继续挖矿,直到找到正确的Hash为止

3)最后,验证区块Hash是否有效

定义Pow类
/**
 * 工作量证明
 *
 * @author wangwei
 * @date 2018/02/04
 */
@Data
public class ProofOfWork {

    /**
     * 难度目标位
     */
    public static final int TARGET_BITS = 20;

    /**
     * 区块
     */
    private Block block;
    /**
     * 难度目标值
     */
    private BigInteger target;

    private ProofOfWork(Block block, BigInteger target) {
        this.block = block;
        this.target = target;
    }
  
    /**
     * 创建新的工作量证明,设定难度目标值
     * <p>
     * 对1进行移位运算,将1向左移动 (256 - TARGET_BITS) 位,得到我们的难度目标值
     * 
     * @param block
     * @return 
     */
    public static ProofOfWork newProofOfWork(Block block) {
        BigInteger targetValue = BigInteger.valueOf(1).shiftLeft((256 - TARGET_BITS));
        return new ProofOfWork(block, targetValue);
    }
}
复制代码
  • 设定一个难度目标位TARGET_BITS,表示最终挖矿挖出来Hash值,转化为二进制后,与256相比,长度少了多少bit,也即二进制前面有多少bit是零.

    • TARGET_BITS 越大,最终targetValue就越小,要求计算出来的Hash越来越小,也就是挖矿的难度越来越大。
    • 我们这里的TARGET_BITS是固定的,但是在真实的比特币中,难度目标是随着时间的推推,会动态调整的。详见:《精通比特币 (第二版)》第10章
  • 由于数值比较大,这里要使用BitInteger类型。

准备数据
/**
 * 准备数据
 * <p>
 * 注意:在准备区块数据时,一定要从原始数据类型转化为byte[],不能直接从字符串进行转换
 * @param nonce
 * @return
 */
private String prepareData(long nonce) {
   byte[] prevBlockHashBytes = {};
   if (StringUtils.isNoneBlank(this.getBlock().getPrevBlockHash())) {
       prevBlockHashBytes = new BigInteger(this.getBlock().getPrevBlockHash(), 16).toByteArray();
   }

   return ByteUtils.merge(
           prevBlockHashBytes,
           this.getBlock().getData().getBytes(),
           ByteUtils.toBytes(this.getBlock().getTimeStamp()),
           ByteUtils.toBytes(TARGET_BITS),
           ByteUtils.toBytes(nonce)
    );
}
复制代码
  • 参与Hash运算的如下几个信息:
    • 前一个区块(父区块)的Hash值;
    • 区块中的交易数据;
    • 区块生成的时间;
    • 难度目标;
    • 用于工作量证明算法的计数器

详见:《精通比特币 (第二版)》第09章

Pow算法
/**
 * 运行工作量证明,开始挖矿,找到小于难度目标值的Hash
 *
 * @return
 */
public PowResult run() {
    long nonce = 0;
    String shaHex = "";
    System.out.printf("Mining the block containing:%s \n", this.getBlock().getData());

    long startTime = System.currentTimeMillis();
    while (nonce < Long.MAX_VALUE) {
        String data = this.prepareData(nonce);
        shaHex = DigestUtils.sha256Hex(data);
        if (new BigInteger(shaHex, 16).compareTo(this.target) == -1) {
            System.out.printf("Elapsed Time: %s seconds \n", (float) (System.currentTimeMillis() - startTime) / 1000);
            System.out.printf("correct hash Hex: %s \n\n", shaHex);
            break;
         } else {
            nonce++;
         }
     }
     return new PowResult(nonce, shaHex);
}
复制代码
  • 循环体里面主要以下四步:
    • 准备数据
    • 进行sha256运算
    • 转化为BigInter类型
    • 与target进行比较
  • 最后,返回正确的Hash值以及运算计数器nonce
验证区块Hash有效性
/**
 * 验证区块是否有效
 *
 * @return
 */
public boolean validate() {
    String data = this.prepareData(this.getBlock().getNonce());
    return new BigInteger(DigestUtils.sha256Hex(data), 16).compareTo(this.target) == -1;
}
复制代码
修改区块添加逻辑
/**
 * <p> 创建新区块 </p>
 *
 * @param previousHash
 * @param data
 * @return
 */
public static Block newBlock(String previousHash, String data) {
    Block block = new Block("", previousHash, data, Instant.now().getEpochSecond(), 0);
    ProofOfWork pow = ProofOfWork.newProofOfWork(block);
    PowResult powResult = pow.run();
    block.setHash(powResult.getHash());
    block.setNonce(powResult.getNonce());
    return block;
}
复制代码
  • 创建区块
  • 创建Pow算法对象
  • 执行Pow算法
  • 保存返回的Hash以及运算计数器
测试运行
/**
 * 测试
 *
 * @author wangwei
 * @date 2018/02/05
 */
public class BlockchainTest {

    public static void main(String[] args) {

        Blockchain blockchain = Blockchain.newBlockchain();

        blockchain.addBlock("Send 1 BTC to Ivan");
        blockchain.addBlock("Send 2 more BTC to Ivan");

        for (Block block : blockchain.getBlockList()) {
            System.out.println("Prev.hash: " + block.getPrevBlockHash());
            System.out.println("Data: " + block.getData());
            System.out.println("Hash: " + block.getHash());
            System.out.println("Nonce: " + block.getNonce());

            ProofOfWork pow = ProofOfWork.newProofOfWork(block);
            System.out.println("Pow valid: " +  pow.validate() + "\n");
        }
    }
}

/**
 * 设定TARGET_BITS = 20,得到如下结果:
 */
Mining the block containing:Genesis Block 
Elapsed Time: 2.118 seconds 
correct hash Hex: 00000828ee8289ef6381f297585ef8c952fde93fc2b673ff7cc655f699bb2442 

Mining the block containing:Send 1 BTC to Ivan 
Elapsed Time: 1.069 seconds 
correct hash Hex: 00000a38c0d7f2ebbd20773e93770298aa8bc0cc6d85fca8756fe0646ae7fea5 

Mining the block containing:Send 2 more BTC to Ivan 
Elapsed Time: 4.258 seconds 
correct hash Hex: 00000777f93efe91d9aabcba14ab3d8ab8e0255b89818cdb9b93cfa844ad0c7f 

Prev.hash: 
Data: Genesis Block
Hash: 00000828ee8289ef6381f297585ef8c952fde93fc2b673ff7cc655f699bb2442
Nonce: 522163
Pow valid: true

Prev.hash: 00000828ee8289ef6381f297585ef8c952fde93fc2b673ff7cc655f699bb2442
Data: Send 1 BTC to Ivan
Hash: 00000a38c0d7f2ebbd20773e93770298aa8bc0cc6d85fca8756fe0646ae7fea5
Nonce: 474758
Pow valid: true

Prev.hash: 00000a38c0d7f2ebbd20773e93770298aa8bc0cc6d85fca8756fe0646ae7fea5
Data: Send 2 more BTC to Ivan
Hash: 00000777f93efe91d9aabcba14ab3d8ab8e0255b89818cdb9b93cfa844ad0c7f
Nonce: 1853839
Pow valid: true
复制代码

总结

我们正在一步一步接近真实的区块链架构,本篇我们实现了挖矿机制,但是我们还有很多关键性的功能没有实现:区块链数据库的持久性、钱包、地址、交易、共识机制,这些我们后面一步一步来实现

资料

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于Java语言构建区块链(二)—— 工作量证明 的相关文章

随机推荐

  • java 方法中的形参传值

    今天看到一个String传值问题 才发现以前的认知都是错的 为防止以后忘记 写下来 先来看一个问题 public static void main String args String a abc String b bcd change a
  • mysql存储过程事务处理

    今天分享的内容是mysql内存储过程进行事务处理 多研究下mysql的存储过程会发现 存储过程的业务流程可以看作我们java里的service里的业务方法 在存储过程添加了事务 就能保证存储过程内的dml操作保持一致性 要么成功要么失败 是
  • 检查 QProcess 对象的状态的所有接口

    QProcess isOpen QProcess isOpen 是 QProcess 类中的一个成员函数 用于检查 QProcess 对象是否已打开 在 QProcess 对象打开和启动外部进程后 可以使用该函数来判断它的状态 函数签名如下
  • OD(1)之git更换远程仓库的url地址

    OD 1 之git更换远程仓库的url地址 Author OnceDay Date 2023年4月17日 1 更换远程仓库的url地址 使用下面命令即可 ubuntu gt tdata git remote help error Unkno
  • 区块链-02-BTC-密码学原理

    目录 区块链与密码学 一 哈希 散列 函数 二 密码散列函数 Cryptographic hash function Collision resistance Hiding digital commitment puzzle friendl
  • 常见分布式系统生成唯一ID的方案

    1 数据库自增长序列或字段 2 UUID 3 UUID的变种 4 Redis生成ID 5 Twitter的snowflake算法 mybatis plus自带策略 6 利用zookeeper生成唯一ID 链接地址 https www cnb
  • 面经:静态多态和动态多态的区别?

    静态多态 Static Polymorphism 和动态多态 Dynamic Polymorphism 是C 中两种不同的多态性形式 1 静态多态 编译时多态 也称为函数重载或模板多态 静态多态是通过函数重载或模板特化来实现的 在编译时确定
  • css动画每日积累

  • c# 获取machineguid_C#正则表达式获取guid(亲测完美解决代码)

    前言 代码亲自测试过 放心使用 完美解决 网上很多文章都没有写清楚 到底是从一段字符串中截取其中的guid 还是判断一段字符串到底是不是guid GUID格式 由三十二位数字和字母组成 8位 4位 4位 4位 12位 c 使用正则表达式从一
  • DL(五)利用softmax线性分类器对线性不可分数据进行分类

    下面为代码 Train a Linear Classifier import numpy as np import matplotlib pyplot as plt np random seed 0 N 100 number of poin
  • Go基础(复杂类型):指针

    Go语言指针 Go 具有指针 指针保存了变量的内存地址 类型 T 是指向类型 T 的值的指针 其零值是 nil var p int 符号会生成一个指向其作用对象的指针 i 42 p i 符号表示指针指向的底层的值 fmt Println p
  • 算法和数据结构的学习之路

    推荐网站 LeetCode 牛客网 Visualgo net 推荐入门书籍 小灰算法 1 入门基础算法知识 2 面试常见算法题
  • python 生成巨大的excel表格xlsxwriter

    原来我是用xlwt来生成excel的 生成的后缀名为xls 但是由于数据太多于是报了个 ValueError row index 65536 not an int in range 65536 错误 原因是 在xlwt中生成的xls文件最多
  • Maven 项目之pom.xml 提示Unknow Error

    今天学习如何搭建SpringCloud 基础项目 pom xml 文件提示Unknow Error 异常 尝试解决办法 我更想maven 项目依赖 检查maven 项目所依赖的jar 包是否正常下载到本地仓库 但都没有解决该问题 经过goo
  • Oracle Data Pump 使用expbp 和 impdp 导出和导入

    预备 创建dmp文件存放文件夹 不创建后面会发生错误 mkdir p opt oracle dmp 1 创建directory数据库对象并授权 sqlplus as sysdba SQL gt create or replace direc
  • Basic Level 1046 划拳 (15分)

    题目 划拳是古老中国酒文化的一个有趣的组成部分 酒桌上两人划拳的方法为 每人口中喊出一个数字 同时用手比划出一个数字 如果谁比划出的数字正好等于两人喊出的数字之和 谁就赢了 输家罚一杯酒 两人同赢或两人同输则继续下一轮 直到唯一的赢家出现
  • 算法推荐技术合规要点梳理与备案指引

    2022年3月1日 国家互联网信息办公室 工业和信息化部 公安部以及国家市场监督管理总局联合发布的 互联网信息服务算法推荐管理规定 以下简称 规定 正式生效 同日 互联网信息服务算法备案系统正式上线运行 下文将简述算法推荐技术合规要点以及备
  • vue3中使用el-table-column sortable对数据进行排序-如何将用户的选择回显到table上显示状态

    element plus 当通过其他设置改变了排序条件后 显示表格需要对应改变筛选状态 在模板中 使用sortable属性将表格列设置为可排序 并绑定一个变量来保存排序的状态
  • 精通python100天——第一天:初识python及环境安装

    课程的初衷 为了小伙伴们 能系统性的从入门到精通python的主要技术点 深入浅出 结合实例 结合实际公司级的项目 让学完这套课程的小伙伴能直接用到工作中去 或达到求职的水平 Python简介 Python是由荷兰人吉多 范罗苏姆 Guid
  • 基于Java语言构建区块链(二)—— 工作量证明

    最终内容请以原文为准 https wangwei one posts 7890ab7e html 引言 上一篇文章中 我们实现了区块链最基本的数据结构模型 添加区块以及和前一个区块连接在一起 但是 我们的实现方式非常简单 而真实的比特币区块