只用120行Java代码写一个自己的区块链-3挖矿算法
在本系列前两篇文章中,我们向大家展示了如何通过精炼的Java代码实现一个简单的区块链。包括生成块,验证块数据,广播通信等等,这一篇让我们聚焦在如何实现 PoW算法。
大家都无不惊呼比特币、以太坊及其他加密电子货币的持续狂热,特别是对于刚接触这个领域的新手,不断得听到张三李四通过 GPU “挖矿”而聚集价值数万乃至数百万加密电子货币。那么“挖矿”到底是什么? 它是如何工作的? 相信对于程序员来说,没有什么比自己动手实践一遍“挖矿”算法更好的学习办法了。
在这篇文章中,让我们一起逐个解读每一个问题,并最终编写出自己的“挖矿”算法。这个算法称为工作证明算法(Proof-of-Work),它是比特币和以太坊这两种最流行的加密货币的基础。
什么是“挖矿”?
加密电子货币因为稀缺才具有价值。以现在的比特币为例,如果任何人任何时候都可以随意“制造”比特币,那么作为电子货币它会变得毫无价值。比特币通过算法来控制产出的速率并在大约122年内达到最大量。这种随着时间推移缓慢、稳定并逐步产出的方式有效避免了通货膨胀。
比特币的产出是通过给予“获胜矿工”奖励来实现,为了获取比特币奖励矿工之间会进行竞争。这个过程之所以被称为“挖矿”,是因为它类似于“Gold Rush”时每个黄金矿工通过辛苦劳作并最终(希望)找到一点黄金。
“挖矿”是如何工作的?
如果 Google 一下这个问题,你会得到大量的结果。简单来说,“挖矿”就是“解决一个数学难题”的过程。我们先来了解一些密码学和哈希算法的知识。
密码学简要介绍
单向加密以人类可读的文本(明文)作为输入,比如“Hello world”这个字符串,再通过一个数学函数产生出难以辨认的输出(密文)。 这类函数或算法的性质和复杂性各不相同。 算法越复杂,逆向工程就越困难。
以流行的 SHA-256 算法为例。 通过这个网站可以让你计算任意给定输入的输出,也就是 SHA-256 哈希值。比如让我们输入“Hello world”,看看得到了什么:

通过不断尝试计算“Hello world”的哈希值。你会发现每次的结果都完全相同。 这个过程称为幂等性。
加密算法一个最基本的特性是,非常难以通过反向工程来求解输入,但是非常容易验证输出。比如上面的例子,你可以很容易验证给定输入“Hello world”的SHA-256哈希值是否正确,但很难通过给定的哈希值判断它的输入是什么。这就是为什么将这种类型的算法称为单向加密。
比特币使用 Double SHA-256,它将 SHA-256 求得的哈希值作为输入再次计算 SHA-256 哈希值。 为了简化,我们只使用一次SHA-256。
挖矿
回到加密电子货币中,比特币就是通过让参与者利用这样的加密算法求解出符合特定条件的哈希值来实现“挖矿”过程。具体来说,比特币要求参与者通过 double SHA-256 算法计算出“前导0”超过若干位的哈希值,第一个求解出来的参与者就是“获胜的矿工”。
比如,我们求“886”这个字符串的 SHA-256 哈希值:

可以看到,是一个“前导0”为3位的哈希值(前三位是0)。
回忆我们前面说到的“单向加密”的特点:
任何人都可以很容易地验证“886”是否产生3位“前导0”的哈希值。但为了找到这样一个能产生3位“前导0”的输入(就是这里的“886”),我们做了大量繁琐的计算工作:从一个很大的数字和字母集合中逐个计算它们的哈希值并判断是否满足上述条件。如果我是第一个找到“886”的人,那其他人通过验证就能判断我做了这样大量繁琐的工作。在比特币、以太坊中这样的过程就称为工作证明算法。
“如果我运气非常好,第一次尝试就找到了一个符合条件的(输入)值呢?” —— 这是非常不可能的,你可以试试随意输入一些字母和数字。
比特币中实际的算法和约束要比上说要求复杂,当然也更难(要求更多位的“前导0”)。同时它也可以动态调整难度,目标是确保每隔10分钟产出一次比特币,不管参与“挖矿”的人多还是少。
差不多可以动手了
了解了足够的背景知识,接着我们就用 Java语言来编码实践下工作量证明(Proof-of-Work)算法。建议你阅读之前的系列文章,因为下面工作证明算法部分会涉及之前的代码。
Proof-of-work
创建新块并加入到链上之前需要完成“工作量证明”过程。我们先写一个简单的函数来检查给定的哈希值是否满足要求。
哈希值必须具有给定位的“前导0”
“前导0”的位数是由难度(
difficulty)决定的可以动态调整难度(
difficulty)来确保 Proof-of-Work 更难解
下面就是 isHashValid 这个函数:
/**
* 校验HASH的合法性
*
* @param hash
* @param difficulty
* @return
*/
public static boolean isHashValid(String hash, int difficulty) {
String prefix = repeat("0", difficulty);
return hash.startsWith(prefix);
} private static String repeat(String str, int repeat) {
final StringBuilder buf = new StringBuilder();
for (int i = 0; i < repeat; i++) {
buf.append(str);
}
return buf.toString();
}
我们定义prefix 变量,它代表“前导0”,接着检查哈希值是否具有满足条件的“前导0”,然后返回 True 或 False 。
我们修改之前生成块的generateBlock 函数:
public static Block generateBlock(Block oldBlock, int vac) {
        Block newBlock = new Block();
        newBlock.setIndex(oldBlock.getIndex() + 1);
        newBlock.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        newBlock.setVac(vac);
        newBlock.setPrevHash(oldBlock.getHash());
        newBlock.setDifficulty(difficulty);
        /*
         * 这里的 for 循环很重要: 获得 i 的十六进制表示 ,将 Nonce 设置为这个值,并传入 calculateHash 计算哈希值。
         * 之后通过上面的 isHashValid 函数判断是否满足难度要求,如果不满足就重复尝试。 这个计算过程会一直持续,直到求得了满足要求的
         * Nonce 值,之后通过 handleWriteBlock 函数将新块加入到链上。
         */
        for (int i = 0;; i++) {
            String hex = String.format("%x", i);
            newBlock.setNonce(hex);
            if (!isHashValid(calculateHash(newBlock), newBlock.getDifficulty())) {
                System.out.printf("%s do more work!\n", calculateHash(newBlock));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    LOGGER.error("error:", e);
                }
                continue;
            } else {
                System.out.printf("%s work done!\n", calculateHash(newBlock));
                newBlock.setHash(calculateHash(newBlock));
                break;
            }
        }
        return newBlock;
    }
篇幅有限,我已经将完整代码发布在 Github上,可以从这里获得。
跑起来看看
启动程序,在浏览器中访问 http://localhost:4567

接着通过 RestClient 来发送一个包含vac数据的POST 请求。我们观察命令行窗口,不断得计算哈希值,如果不满足难度要求就继续重试,直到找到满足要求的哈希值及 Nonce

可以看到最后一个哈希值满足我们设定的难度要求(1位“前导0”)。我们再来刷新下浏览器:

可以看到第二个块创建成功并加到链上了,其中Nonce 就是通过Proof-of-Work计算出来满足难度要求的值。
下一步
到这里要先祝贺你,上面的内容很有价值。尽管我们的示例中使用了非常低的难度,但本质上,工作证明算法就是比特币、以太坊等区块链的重要组成根本。
对于下一步应该深入区块链的哪个方向,我们推荐可以学习如何通过 IPFS存取大文件并与区块链打通。[IPFS]
此外相比 Proof-of-Work,Proof-of-Stake 算法正越来越受到关注和青睐,你也可以学习如何将本文的 PoW 算法改为实现 PoS 算法。
只用120行Java代码写一个自己的区块链-3挖矿算法的更多相关文章
- 只用120行Java代码写一个自己的区块链
		
区块链是目前最热门的话题,广大读者都听说过比特币,或许还有智能合约,相信大家都非常想了解这一切是如何工作的.这篇文章就是帮助你使用 Java 语言来实现一个简单的区块链,用不到 120 行代码来揭示区 ...
 - 只用120行Java代码写一个自己的区块链-2网络
		
已经看完第一章的内容了吗,欢迎回来. 上一章我们介绍了关于怎么去编写自己的区块链,完成哈希和新块的校验.但是它只是在一个终端(结点)上跑.我们怎么样来连接其他结点以及贡献新的块呢,怎么样广播到其他结点 ...
 - 只用120行Java代码写一个自己的区块链-4实现真正的p2p网络
		
在之前的文章中,我们模拟了节点网络通讯,很多朋友反馈说,他们想看真正的节点网络通讯而不是单节点的模拟.本章将满足你们.
 - 只用200行Go代码写一个自己的区块链!
		
Coral Health · 大约23小时之前 · 220 次点击 · 预计阅读时间 7 分钟 · 不到1分钟之前 开始浏览 区块链是目前最热门的话题,广大读者都听说过比特币,或许还有智能合约,相信大 ...
 - 只用200行Go代码写一个自己的区块链!(转)
		
区块链是目前最热门的话题,广大读者都听说过比特币,或许还有智能合约,相信大家都非常想了解这一切是如何工作的.这篇文章就是帮助你使用 Go 语言来实现一个简单的区块链,用不到 200 行代码来揭示区块链 ...
 - 用java代码写一个简单的网上购物车程序
		
需求:1.写一个商品类,有商品编号.商品名称.商品分类.商品单价属性.2.写一个商品条目信息类,有商品和数量两个属性,有商品总价格方法. 3.写一个购物车类,有添加商品方法.查看订单信息,删除商品,修 ...
 - 如何用java代码写一个堆栈
		
public class Stack { int[] data; int maxSize; int top; public Stack(int maxSize) { this.maxSize = ma ...
 - 用 Python 构建一个极小的区块链
		
虽然有些人认为区块链是一个早晚会出现问题的解决方案,但是毫无疑问,这个创新技术是一个计算机技术上的奇迹.那么,究竟什么是区块链呢? 区块链 以比特币(Bitcoin)或其它加密货币按时间顺序公开地记录 ...
 - 如何用70行Java代码实现深度神经网络算法
		
http://www.tuicool.com/articles/MfYjQfV 如何用70行Java代码实现深度神经网络算法 时间 2016-02-18 10:46:17 ITeye 原文 htt ...
 
随机推荐
- kibana和ElasticSearch的信息查询检索
			
使用kibana来进行ElasticSearch的信息查询检索 大家经常会听到使用ELK搭建日志管理平台.完成日志聚合检索的功能,那么这个平台到底是个什么概念,怎么搭建,怎么使用呢? ELK包括Ela ...
 - [转载] Win7下MATLAB 7.0下载地址和详细安装
			
移步http://blog.csdn.net/feecooling/article/details/7525140 MATLAB中文手册命令汇总http://wenku.baidu.com/view/ ...
 - IIS注册asp.net4.0
			
1. 运行->cmd 2. cd C:\Windows\Microsoft.NET\Framework64\v4.0.30319 3. aspnet_regiis.exe -i
 - 算法学习——st表
			
st表是一种基于倍增思想的DP. 用于求一个数列中的某个区间的最大/最小值. 用st[i][j]表示从第i个开始往后2^j个点,最大的是多少. 我们令k[i]表示2^i等于多少 那么有转移方程 st[ ...
 - C++——继承时的this指针
			
1.this指针只在类的成员函数中使用,当类的成员函数需要用到自己的指针时就要用到this指针.但静态函数不能使用this关键字,其解释是:因为this是个引用,哪个对象调用方法就引用哪个对象. 而静 ...
 - Vue根据URL传参来控制全局 console.log 的开关
			
如果你的项目中console.log了很多信息,但是发到生产环境上又不想打印这些信息,这时候就需要设置一个全局变量,如:debug, 用正则匹配一下参数: const getQueryStr = (n ...
 - Spring validation 后端校验【转】
			
本文来自 下一秒升华 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/u013815546/article/details/77248003?utm_source=co ...
 - 论文笔记《Spatial Memory for Context Reasoning in Object Detection》
			
好久不写论文笔记了,不是没看,而是很少看到好的或者说值得记的了,今天被xinlei这篇paper炸了出来,这篇被据老大说xinlei自称idea of the year,所以看的时候还是很认真的,然后 ...
 - Codeforces 931.D Peculiar apple-tree
			
D. Peculiar apple-tree time limit per test 1 second memory limit per test 256 megabytes input standa ...
 - ZOJ3261:Connections in Galaxy War(逆向并查集)
			
Connections in Galaxy War Time Limit: 3 Seconds Memory Limit: 32768 KB 题目链接:http://acm.zju.edu. ...