本文主要是介绍从斗地主角度看区块链,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
从身边的事情开始,平时接触棋牌类游戏比较多,最近在学习区块链知识,想从斗地主的角度理解区块链的知识。
“If you can not explain it simply,you don’t understand it。”
“如果你不能把一个技术很简单的讲出来,实际上是你没有吃透它。”
对于想准确理解的技术性读者,会将用到的技术中英文属于对照列出,避免很多文章中翻译不一致造成的概念混淆。
对于有一定编码能力的读者,我们提供了示例性的代码,你可以通过下载运行,一步一步更加直观的理解区块链在计算机上的实现方式,完成你的第一个可以实际运行的区块链代码示例。
从斗地主游戏说起
话说小吴小刘小张三个好基友最喜欢的娱乐就是“欢乐斗地主”,今天周末他们又聚在一起,准备好好干个天昏地暗。
规则还是老样子:输赢基数一元钱,炸弹翻倍。
打牌是在小张的家里,每次小张都是让自己的上小学的弟弟张二宝过来记账,张二宝虽小,但是是比较细心,字写得好看,冰雪聪明。
第1局,小李是地主,结果输掉了,他就分别给小张小王各1元钱;开始第2局……
三局之后,张二宝在纸上的记账如下:
记账的问题来了
三局过后,小张看到账本一盘算,还是自己赢的多,上次也是自己赢了,禁不住手舞足蹈起来,甚至还在别人洗牌间隙表演起来了自己在抖音上新的“嘟嘟舞”,然而意想不到的事情发生了:她竟然把刚刚泡好的一杯咖啡给打翻了,更重要的是这杯咖啡竟然就倒在了记账的便签纸上,一下子糊掉了,什么都看不清了…
在一阵慌乱之后,大家都开始了思索:
小李在心里嘀咕了:为什么我每次打牌都是输呢?是不是记账的张二宝同学有舞弊嫌疑,给哥哥记录假账的可能?
小王是个没有记性的人,每次打几把,他都要问记账的把账单拿过来看看,心里琢磨自己赢多少或者输多少?次数多了,有点被记账的张二宝嫌弃,也有点不爽。
记账员张二宝也不开心了,每次都给他们服务,只能得到一个棒棒糖的奖赏,而且又一次自己去上个厕所漏记了一次,被他们发现教训,心里不爽,借着这个机会,就推掉算了。
小张是个机灵的人,他早也看到了两位的心思,突然想起网上有关区块链的介绍,“区块链的基础核心是分布式记账系统,具备不可篡改、不可抵赖,公开透明的特点”,为什么不用用区块链技术呢?想到这里他开始说给大家如此这般介绍一下,大家一听,都拍手称赞,新的记账模式开始了:
小结:他们开始各自记账
原来的模式下:所有的记账都是一个集中式的,由张二宝同学集中记账,所有的记录以中央记录的账本为准,每一个参与者(打牌的人)要想了解记账信息,就要向集中的账本查询。在异常的情况下会出现账本丢失损毁,所有信息就会丢失;还会出现集中记账系统出现错误时候(漏记,记错,或者有意篡改数据的可能)。
新的各自记账的模式:取消了集中记账的角色(张二宝),在每局打完之后,三个人就分头各自记账并各自保存账本。这样的两个最明显的益处是: 可以防范集中记账奔溃的问题,而且每个人的账本完全一样,公开透明,每个人可以随时查看他自己的信息。
Blockchain is a distributed, decentralized,public ledger.
区块链是一种分布式的,去中心化的,公开的记账系统。
读到这里,恭喜你,你已经了解大致区块链是怎么运作了。
解决问题
“A solution of the problem may also bring new problems.”
“一个问题的解决也可能随之带来新的问题。”
记录更多的信息
为了让信息更加准确和完备,每局打完牌后我们可以记录更多的信息,如下是第一局结束后的记录:
其中:
“No”来标记这是第几局,是第一局,所以写“No:1”;这个数据有利于准确的记录局数,也便于最终的核对;
接下来是具体的输赢数据,第一局,小张赢了1元,小李赢了1元,小李输了2元,依次记录;
“时间”用来记录这局结束后记账的时间,我们精确到秒,例如“2020-03-26 16:42:08”
“计数器”记录的是节点计算机需要计算多少次哈希函数,才能得到符合设定目标的哈希值(后面详解)。
“哈希”记录的是本局(本区块)记账信息的一个哈希计算结果(后面详解)。
“前哈希”记录的是上一局(上一个区块)记账信息的一个哈希计算结果(后面详解)。
每个区块通过记录前面一个区块中的哈希值,把整个区块链接起来,形成了“区块链”。
注意:后面将要提到的“区块”对应到这里就是每一局结束后的交易记录(记账信息)。
后面将要提到的“节点”对应到这里就是每一个单独的记账者(本例子中有3个人各自记账,就有3个节点,节点数也可以和交易参与者数量不一致)。
hash算法
哈希函数(Hash Function),也称为散列函数或杂凑函数。哈希函数是一个公开函数,可以将任意长度的消息M映射成为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值(HashValue)、杂凑值或者消息摘要(Message Digest
)。
它是一种单向密码体制,即一个从明文到密文的不可逆映射,只有加密过程,没有解密过程。
- 通俗的讲,即无论输入是什么数字格式、文件有多大,输出都是固定长度的比特串。例如SH256算法为例,无论输入是什么数据文件,输出就是256bit(64位16进制数)。
- 理论上讲,计算机上的任意一段信息,都可以利用哈希函数来生成一个64位的不重复的16进制字符串。只要稍微更改一下信息,生成的哈希字符串就不一样了。
我们再回到我们前面的区块记录中:
哈希值记录的是当前块中数据字符转拼接起来,通过哈希算法,得到当前区块的哈希值,这个哈希值也可以叫做当前区块的签名,具备唯一性。就是当区块中数据如果发生了任何变化(例如被篡改等)那么重新计算出来的哈希函数值就会和表中记录的哈希值不相等,从而引起当前区块处于“无效”。
如果有的节点想篡改数据,他先修改了区块中的数据,然后按照哈希算法再算出新的哈希值,再用新计算的哈希值更新当前块的哈希值,使得当前块看起来“有效”,但是由于后面每个区块都会依次记录前一个区块的哈希值,现在发现前面的区块哈希值发生了变化,就会引起从这个区块及以后的所有的区块标识为“无效”。
共识机制:找出能把当前区块链接到链上的人
刚打完一局,例如第一局中,小李自己作为地主,输了2元,需要给小张和小王各自1元。这一点在大家没有异议的情况下,可以分头记录在自己的账本上。
但是如果参与者众多(成千上万的情况),最好的方式是,先由其中一个人例如小张记账,然后把记账拿给大家看,大家照着这个记账再抄写一遍到自己的账本上。我们再这里权且称这个人(小张)为本区块(局)“有权记账者”。
那么问题来了?这个有权记账者是怎么产生的?
要知道在区块链中每个节点的位置是完全相同的,那么说有权记账者是怎么选出来?这个可以有各种办法,例如抽签,例如投票,例如按照年龄,也可以设计一个简单问答来选出“有权记账者”但是在计算机实现上,考虑到计算机最大的资源是“计算机的运算能力”,我们就设定一个规则如下:
每一局结束,谁的计算能力最强,谁来做“有权记账者”,这个是就是把这个块链接到已经有的链上,让当前这个块有效。
要考察谁的计算能力最强,我们出的题目是:“计算每个区块的哈希值,为了增加难度,哈希值需要前面N位为0”
如果假定N设定为2。则计算过程为:
谁先计算出来,谁就是把这个区块链接到主链上的人。链接后区块有效,系统通知其他人员复制到自己的账本,本次记账完成,开始下一局打牌。
如果该节点计算了101次(None值=100),他的工作量就是100。
这就是区块链中的共识机制中的POW (Proof of Work) 工作量证明。
PoW工作量证明及其它
在区块链系统当中,没有一个像银行一样的中心化记账机构,保证每一笔交易在所有记账节点上的一致性,即让全网达成共识至关重要。区块链共识机制解决的就是这个问题。
目前区块链主要的共识机制有工作量证明机制PoW (Proof of Work)和权益证明机制PoS (Proof ofstack)。
- PoW通过评估你的工作量来决定你获得记账权的机率,工作量越大,就越有可能获得此次记账机会。
- PoS通过评估你持有代币的数量和时长来决定你获得记账权的机率。这就类似于股票的分红制度,持有股权相对多的人能够获得更多的分红。
- 还有一种是DPOS,它与POS原理相似,只是选了一些“代表”。与PoS的主要区别在于节点选举若干代表人,由代表验证和记账。
随着技术的发展,未来可能还会诞生更先进的共识机制。
总结
共识机制(consensus):区块链中的节点由于在同一时间会有时间上的延迟和动作的不同,需要一套公平的规则来规范这些节点,使得整个区块链系统顺利地运行下去。
从本质上来讲,共识机制就是决定了谁在区块链系统中有权负责某一个新区块链接到主链上的作用。
实践
*“Genuine knowledge comes from practice”
“实践出真知”
用nodejs 实现上面的简单概念:
pokerBlock目录下:
//pokerBlock.js
"use strict"const tools = require('./tools');var blocklist = [];class pokerBlock {//构造方法constructor(blockInfo, preHash) {this.info = blockInfo; //账单信息this.preHash = preHash; //上一块的hashthis.time = Date.now(); //当前几张时间this.nonce = 0; //工作量this.createHash(); //生成当前hash};//根据当前区块的信息内容计算新的哈希值createHash() {let data = JSON.stringify(this.info) +this.preHash +this.time +this.nonce;this.hash = tools.createHash(data);return this.hash;}//获取当前block的hashgetHash() { return this.hash; }//获取当前block的上一个block的hashgetPreHash() { return this.preHash; }/** * “挖矿”过程* 在这里指需要经过多少次哈希运算才能得到满足条件的哈希值* 条件有参数 difficulty 设定* 例如:如果difficulty 值是2,则要求计算出来的哈希值前2位字节为“00”* 如果计算出来不是,则nonce 值加1,继续计算* 直到算出来的哈希值满足条件,则结束 */mineBlock(difficulty) {let dstr = tools.getdifficultyStr(difficulty);while (this.hash.substring(0, difficulty) != dstr) {this.nonce++;this.createHash();console.log('mineBlock str:', dstr, 'nonce:', this.nonce, 'hash:', this.hash);}console.log('mineBlock done!!! nonce:', this.nonce);}/*** 打印模块,为了便于观看*/print() {console.log('-------------------------------------------------------------------------------------------');let data = {};data.info = this.info;//时间格式化let date = new Date(this.time);let dt = date.getFullYear() + "-" + (date.getMonth() < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1)) + "-" + (date.getDate() < 10 ? '0' + date.getDate() : date.getDate());data.time = dt;data.preHash = this.preHash;data.nonce = this.nonce;data.hash = this.hash;console.log(data);}};/*** 添加区块*/
exports.addBlock = function (blockInfo) {console.log('添加新的区块:info:', blockInfo);//获取上一个区块的hashlet preHash;if (blocklist.length == 0) {preHash = 0;} else {preHash = blocklist[blocklist.length - 1].getHash();}//生成一个区块var block = new pokerBlock(blockInfo, preHash);//挖矿,let difficulty = 1;block.mineBlock(difficulty);//添加新区块blocklist.push(block);console.log('addBlock success!!!');printBlockList();
}var printBlockList = function () {blocklist.forEach(v => v.print());}
//工具类
//tools.js
"use strict"/*** 工具类*/const crypto = require("crypto");exports.getdifficultyStr = function (difficulty) {let str = '';for (let i = 0; i < difficulty; i++) {str = str + '0';}//console.log('get difficulty str:', str);return str;
};exports.createHash = function (data, mode) {mode = mode || 'sha256';let s = crypto.createHash(mode);//console.log('createHash:', data);let hash = s.update(data).digest('hex');//console.log('createHash:', hash);return hash;
}
//main.js
"use strict"//简单的区块链示例:扑克牌斗地主记账var pokerBlock = require('./pokerBlock');console.log('hello,world!');
console.log('-------------------------------------------------------------------------------------------');var data = {uanme: "创世块",xiaowu: 1,xiaoliu: 1,xiaozhang: -2,no: 1
};
pokerBlock.addBlock(data);var data1 = {uanme: "第一块",xiaowu: 1,xiaoliu: 1,xiaozhang: -2,no: 1
};
pokerBlock.addBlock(data1);var data2 = {uanme: "第二块",xiaowu: 4,xiaoliu: -2,xiaozhang: -2,no: 1
};
pokerBlock.addBlock(data2);console.log('-------------------------------------------------------------------------------------------');
console.log('happy ending!');
执行node main.js 运行结果如下:
代码路径: https://github.com/wuyingzhen/pokerBlock
这篇关于从斗地主角度看区块链的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!