第一章:最小可行区块链

  • 概览
  • 区块数据结构
  • 区块哈希
  • 创世块
  • 创建区块
  • 保存区块链
  • 验证区块完整性
  • 选择最长链
  • 节点间通信
  • 操作节点
  • 架构
  • 运行测试
  • 小结

概览

区块链的基础概念非常简单, 说白了就是一个维护着一个持续增长的有序数据记录列表的这么一个分布式数据库。在此章节中我们将实现一个简单的玩具版的区块链。此章节结束时,我们的区块链将实现以下功能:

  • 实现区块和区块链结构定义
  • 实现可以将包含任意数据的新区块写入到区块链的方法
  • 实现可以与其他节点进行点到点沟通和同步区块链数据的运行节点
  • 操作单个运行节点的简单HTTP(Restful) API

区块数据结构

我们首先会从区块数据结构的定义开始。在当前阶段,简单起见,我们只会给每个区块定义最关键的属性。

  • index: 区块在区块链中的高度(即序号),因为每加一个区块,该index就会加1,所以币圈将其称之为高度。
  • data: 任何需要包括在此区块中的数据。本章节中可以是任何数据,到后面章节我们会用来记账用。
  • timestamp: 时间戳。本章节中也是可以是任何数据,往后我们需要保证这个字段是正确的时间戳数据,用来防止攻击等用。
  • hash: 根据区块内容计算的哈希值(SHA256)。
  • previousHash: 前一个区块的哈希值。通过这个属性,我们能很方便回溯前面的区块。

image

相应代码大致如下:


class Block {public index: number;public hash: string;public previousHash: string;public timestamp: number;public data: string;constructor(index: number, hash: string, previousHash: string, timestamp: number, data: string) {this.index = index;this.previousHash = previousHash;this.timestamp = timestamp;this.data = data;this.hash = hash;}
}

区块哈希

区块哈希值是区块中最重要的属性之一。哈希值根据区块中的所有数据计算而得,这意味着如果区块中任何数据发生变化,原有的哈希值就不再有效。区块哈希值也能被看成区块的唯一性标识。比如说,两个人同时挖矿成功,那就有可能出现两个高度一致的区块,但是因为要通过其他属性值一起算哈希(往后我们会看到data属性会存放交易数据,交易数据,特别是id,肯定不能重复),所以绝对不会出现一样的哈希值。
根据以下的代码来计算哈希值:

const calculateHash = (index: number, previousHash: string, timestamp: number, data: string): string =>CryptoJS.SHA256(index + previousHash + timestamp + data).toString();

需要注意的是,在这个阶段,区块的哈希值与挖矿没有任何关系,因为还未有 POW(工作量证明) 问题需要解决。我们使用区块哈希值来保证区块的完整性,同时也使用它来回溯前一个区块。

由以上对 hash 和 previousHash 属性的处理机制,很容易得出区块链的一个重要特性:区块的内容不能被修改,除非同时修改它后续的所有区块内容。

以下的例子描述了这个特性。如果将第44区块的数据从“DESERT”修改成“STREET”,所有后续区块的哈希值也必须被修改。这是由于区块的哈希值是通过对区块的内容计算哈希得到的,而内容中包含了 previousHash 这个代表了前一个区块的哈希的值。

image

这个特性在我们后面章节中引入的工作量证明机制来说尤其重要。一个区块在区块链中的位置越深(即越靠前),要修改它的难度就越大,因为需要同时修改它本身以及它后续的所有区块。

创世块

创世块是区块链中的第一个区块。它是唯一一个没有 previousHash 的区块,因为这个区块比较特别,我们在代码里会将创世区块进行硬编码处理:


const genesisBlock: Block = new Block(0, '816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7', null, 1465154705, 'my genesis block!!'
);

创建区块

创建一个新的区块,需要获得上一个区块的哈希值,并创建其他必须的内容( index, hash, data 和 timestamp)。区块的数据(data字段)由用户提供,其他的参数使用以下代码生成:


const generateNextBlock = (blockData: string) => {const previousBlock: Block = getLatestBlock();const nextIndex: number = previousBlock.index + 1;const nextTimestamp: number = new Date().getTime() / 1000;const nextHash: string = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData);const newBlock: Block = new Block(nextIndex, nextHash, previousBlock.hash, nextTimestamp, blockData);return newBlock;
};

保存区块链

目前我们使用 JavaScript 的数组,将区块链保存在程序的运行内存中。这意味着当一个运行节点停止时,该节点上的区块链数据不会被持久化。

const blockchain: Block[] = [genesisBlock];

验证区块完整性

为确保数据完整性,我们应想办法做到可随时对一个区块,或者一条区块链上的区块进行有效性验证。特别是当我们的节点从其他运行节点中接收到广播过来的新区块时,我们就需要验证区块的有效性,以便决定是否接受这些区块。

验证区块的有效性,需要满足以下所有条件:

  • 区块的 index 需要比上一个区块大1;
  • 区块的 previousHash 属性需要与上一个区块的 hash 属性一致;
  • 区块自身的 hash 值需要有效。

以下代码描述了整个验证过程:


const isValidNewBlock = (newBlock: Block, previousBlock: Block) => {if (previousBlock.index + 1 !== newBlock.index) {console.log('invalid index');return false;} else if (previousBlock.hash !== newBlock.previousHash) {console.log('invalid previoushash');return false;} else if (calculateHashForBlock(newBlock) !== newBlock.hash) {console.log(typeof (newBlock.hash) + ' ' + typeof calculateHashForBlock(newBlock));console.log('invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash);return false;}return true;
};

同时我们还必须验证该区块的结构是否正确,以避免其他节点广播过来的带有不正确格式的数据导致程序崩溃。

const isValidBlockStructure = (block: Block): boolean => {return typeof block.index === 'number'&& typeof block.hash === 'string'&& typeof block.previousHash === 'string'&& typeof block.timestamp === 'number'&& typeof block.data === 'string';
};

既然我们现在能够验证单个区块的有效性,我们就可以进一步的对整个区块链进行有效性验证了。首先验证链中的第一个区块为创世区块。然后,我们使用以上的方式来依次校验链中的下一个区块,以下为实现代码:

const isValidChain = (blockchainToValidate: Block[]): boolean => {const isValidGenesis = (block: Block): boolean => {return JSON.stringify(block) === JSON.stringify(genesisBlock);};if (!isValidGenesis(blockchainToValidate[0])) {return false;}for (let i = 1; i < blockchainToValidate.length; i++) {if (!isValidNewBlock(blockchainToValidate[i], blockchainToValidate[i - 1])) {return false;}}return true;
};

选择最长链

在任何时候,在区块链系统中都应该只存在一条正确的链,但冲突还是在所难免的,我们需要有一个大家都认同的共识机制来确保冲突得以解决。在冲突发生的情况下(比如:主链在71这个块的时候发生分叉,然后我紧邻的节点在某一条链的基础上挖出了第73个块),则从中选择包含更长区块的链(比如我的节点启动时会和其他节点请求区块链状态,发现有最后块为72和73的两条链,那么我们的节点将会在73这个链的基础上继续贡献资源进行挖矿)。在以下的例子中,由于被更长的区块链复写,第72区块: a350235b00 中的数据将不会被包括在区块链中。

image

代码实现如下:

const replaceChain = (newBlocks: Block[]) => {if (isValidChain(newBlocks)&& newBlocks.length > getBlockchain().length) {console.log('Received blockchain is valid. Replacing current blockchain with received blockchain');blockchain = newBlocks;broadcastLatest();} else {console.log('Received blockchain invalid');}
};

节点间通信

每个运行节点都必须能和其他节点广播和同步区块链数据。我们通过以下规则保证节点间能正确有效的同步:

  • 当一个节点生成新区块时,该节点会将此区块广播至区块链网络中
  • 当一个节点和另外一个节点建立点对点连接时,该节点将会向另一个节点请求最新的区块链信息
  • 当一个节点发现从其他节点过来的一个区块的 index 比该节点中保留的区块链的最后一个区块的 index 大,根据两个index之间相差的大小,该节点会有两个选择:如果只相差1,则将此区块加到自身的区块链中; 如果超过1,则需要向其他节点请求整条区块链。

image

我们将会使用 WebSocket 技术来实现各个节点的点对点通信。各个节点的 socket 列表将保存在 const sockets: WebSocket[] 变量中。我们并没有实现节点发现机制,所以新增加一个节点后,需要手动添加需要建立点对点连接的目标节点的地址。

操作节点

用户需能够以某种方式来操作节点。我们将通过实现相应的http服务端接口来提供相应功能。

const initHttpServer = ( myHttpPort: number ) => {const app = express();app.use(bodyParser.json());app.get('/blocks', (req, res) => {res.send(getBlockchain());});app.post('/mineBlock', (req, res) => {const newBlock: Block = generateNextBlock(req.body.data);res.send(newBlock);});app.get('/peers', (req, res) => {res.send(getSockets().map(( s: any ) => s._socket.remoteAddress + ':' + s._socket.remotePort));});app.post('/addPeer', (req, res) => {connectToPeers(req.body.peer);res.send();});app.listen(myHttpPort, () => {console.log('Listening http on port: ' + myHttpPort);});
};

根据以上代码暴露出来的HTTP接口,用户可以发送请求到节点进行以下操作:

  • 列出所有区块
  • 由用户指定相应内容来创建一个新区块
  • 列出连接过来的节点的地址
  • 通过websocket url连接到指定节点

您可以通过Curl工具来对节点进行操作,当然您也可以通过postman等工具来操作:


#get all blocks from the node
> curl http://localhost:3001/blocks

架构

每个节点都对外暴露两个web 服务: 一个是用户来给用户对节点进行操作(HTTP Server),一个是用来实现节点间的点对点通信(Websocket HTTP server)。

image

运行测试

安装

npm install

运行

打开一个终端运行节点1. 节点1的http服务端端口为3001, p2p端口为6001。

npm run node1

建议打开另外一个终端运行节点2,以便能通过输出查看两个区块链节点是怎么通信的。 节点1的http服务端端口为3002, p2p端口为6002。

npm run node2

ps: 节点2运行后,即可以通过addPeer这个api和节点1进行websocket连接。

生成一个区块

curl -H "Content-type:application/json" --data '{"data" : "Some data to the first block"}' http://localhost:3001/mineBlock

返回结果示例:

{"index": 1,"previousHash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7","timestamp": 1561025398.834,"data": "Some data to the first block","hash": "979335f8383fa058c0abf5d342a232d345de51ea644756d3522eca5637e97a17"
}

获取区块链

curl http://localhost:3001/blocks

返回示例:

[
{
"index": 0,
"previousHash": "",
"timestamp": 1465154705,
"data": "my genesis block!!",
"hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7"
},
{
"index": 1,
"previousHash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7",
"timestamp": 1561025398.834,
"data": "Some data to the first block",
"hash": "979335f8383fa058c0abf5d342a232d345de51ea644756d3522eca5637e97a17"
}
]

连接到一个节点

curl -H "Content-type:application/json" --data '{"peer" : "ws://localhost:6001"}' http://localhost:3002/addPeer

查询连接的节点列表

curl http://localhost:3001/peers

返回示例:

["::ffff:127.0.0.1:54261"]

小结

到现在为止,我们实现了一个简单的玩具版的区块链。此外,本章节还为我们展示了如何用简单扼要的方法来实现区块链的一些基本原理。下一章节中我们将为naivecoin 加入工作量证明机制。

本章节的代码请查看这里

第二章

本文由天地会珠海分舵编译,转载需授权,喜欢点个赞,吐槽请评论,如能给Github上的项目给个星,将不胜感激。

转载于:https://www.cnblogs.com/techgogogo/p/11072536.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/393097.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 学习日记第二篇 -- 列表,元组

一、列表 列表是一个可以包含所以数据类型的对象的位置有序集合&#xff0c;它是可以改变的。 1、列表的序列操作&#xff08;Python3&#xff09; 123456789101112131415161718192021222324>>> one_list [1,2,3,4]>>> two_list ["jonny","…

【Gamma】PhyLab 测试报告

PhyLab Gamma测试报告 测试中发现的bug Gamma阶段新Bug Bug可能原因部分错误码设置与原先抛异常的逻辑冲突原先代码中使用了一些特殊的办法处理异常Beta未发现Bug Bug可能原因控制台新建实验编号不能以0开头后端处理编号会将其前导0去除&#xff0c;以数字形式存储&#xff0c;…

轻松学习分布式|系列3|分布式数据库。

我们继续来讲分布式&#xff0c;回到我们的创业游戏。 我们的业务规模上来了&#xff0c;客户也越来越忠诚了。很多客户都通过我们的订票服务&#xff0c;来方便自己的行程。 那对这些老客户&#xff0c;我们的宗旨是&#xff1a;要不断超越客户的期待。 所以&#xff0c;我们要…

linux增加端口失败,端口没被占用,怎么会bind失败呢?

今天在一个服务器上部署一个webserver的时候&#xff0c;提示我bind端口失败&#xff0c;我习惯性的用netstat看了下&#xff0c;没有被占用啊&#xff01;把问题分享出来后&#xff0c;给力的同事们搜索到了ip_local_port_range这个东西这个东西对应的是/proc/sys/net/ipv4/ip…

安装输入发

直接在系统 ——系统管理 ——语言支持 选——中文从新启动 sudo apt-get install scim-pinyin安装JAVA环境支持 sudo apt-get install sun-java-jre()要是 apt -get 命令不能用 可能是你 的 源有问题 可以 更新一下 在系统 &#xff0d;系统管理 源设置 选这台湾的 就可以 …

(第2篇)一篇文章教你轻松安装hadoop

摘要: 这篇文章将会手把手教你安装hadoop&#xff0c;只要你细心按照文章中的步骤操作&#xff0c;hadoop肯定能正确安装&#xff0c;绝对不会让你崩溃 博主福利 给大家赠送一套hadoop视频课程 授课老师是百度 hadoop 核心架构师 内容包括hadoop入门、hadoop生态架构以及大型ha…

python接口自动化2-发送post请求

前言 发送post的请求参考例子很简单&#xff0c;实际遇到的情况却是很复杂的&#xff0c;首先第一个post请求肯定是登录了&#xff0c;但登录是最难处理的。登录问题解决了&#xff0c;后面都简单了。 一、查看官方文档 1.学习一个新的模块&#xff0c;其实不用去百度什么的&am…

Linux查看tar实用程序,linux tar指令常用选项

linux的tar指令经常被用到&#xff0c;因为压缩文件的时候通常需要打包文档&#xff0c;而tar指令就是打包指令&#xff0c;同时gzip压缩程序和bzip2压缩程序都是支持tar指令的&#xff0c;所以tar指令在打包的同时还可以用gzip和bzip进行压缩&#xff0c;这样多文件可以打包的…

DaVinci各版本安装指南

链接: https://pan.baidu.com/s/1g1kaXZxcw-etsJENiW2IUQ?pwd0531 ​ #2024版 1.鼠标右击【DaVinci_Resolve_Studio_18.5(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 DaVinci_Resolve_Studio_18.5(64bit)】。 2.打开解压后的文…

使用 Servlet 读取表单数据

Technorati 标签: servlet&#xff1b;java 一、概述 Servlet 有一个比较好的功能就是可以自动处理表单提交的数据。我们只需要调用HttpServletRequest#getParameter(String name),就可以获得指定参数的值&#xff08;String&#xff09;&#xff0c;注意此方法是大小写敏感的。…

linux文档权限

1、登录 root 用户&#xff1a;su - mac一开始进入创建的用户是具有管理员权限的用户&#xff0c;但是密码却不是进入 root 用户的密码&#xff0c;可以使用 sudo su - 免密进入 root 用户。 2、退出 root 用户&#xff1a;exit 3、列出档案&#xff08;包括隐藏的档案&#xf…

linux开启ping服务,Linux 云服务器禁止和开启Ping

原标题&#xff1a;Linux 云服务器禁止和开启Ping在使用Linux服务器的时候&#xff0c;一般系统默认是开启ping的&#xff0c;比如我们可以ping测试网络的延迟质量。当然也有部分服务商是可以通过安全组设置禁止ping的&#xff0c;我们可以设置安全组对应项目开启或禁止ping&am…

红外感应模块+蜂鸣器实现简易报警(转)

拿到了一个红外感应模块HC-SR501&#xff0c;于是就用它和蜂鸣器简单试验了下。主要是试验一下这个红外感应模块的功能&#xff0c;所以代码也写的很随便啦&#xff0c;逻辑上也欠考虑。实现基本功能&#xff1a;运行脚本后&#xff0c;感应模块每隔一定时间检测&#xff0c;如…

linux运行apktool签名,解决Linux中使用ApkTool遇到问题

8种机械键盘轴体对比本人程序员&#xff0c;要买一个写代码的键盘&#xff0c;请问红轴和茶轴怎么选&#xff1f;遇到问题在Linux中使用IntelliDroid工具时&#xff0c;按要求配置好环境之后&#xff0c;始终无法成功运行该工具内部的ApkTool&#xff0c;导致后续的安卓静态分析…

python 脚本学习(二)

task1&#xff1a; 在一个文件中&#xff0c;单词之间使用空格、分号、逗号或者句号分隔&#xff0c;请提取全部单词。 代码实例&#xff1a; 1234567891011#!/usr/local/python27/bin/python2.7import sys import re words [] with open(sys.argv[1]) as f: for line in f: #…

2.2 Consumer API官网剖析(博主推荐)

不多说&#xff0c;直接上干货&#xff01; 一切来源于官网 http://kafka.apache.org/documentation/ 2.2 Consumer API 2.2、消费者API 随着0.9.0版本&#xff0c;我们已经增加了一个新的Java消费者替换我们现有的基于zookeeper的高级和低级消费者。这个客户端还是测试版的质量…

mybatis-generator-gui如何打包成exe

快速阅读&#xff1a; ​ 用wix和inno setup把mybatis-generator-gui 打包成exe和安装文件。 以后使用的时候方便&#xff0c;不用每次打开eclipse运行。 使用inno setup 5 和wix 3.11 基于mybatis generator开发一款界面工具, 非常容易及快速生成Mybatis的Java POJO文件及数据…

NeHe OpenGL教程 第三十课:碰撞检测

转自【翻译】NeHe OpenGL 教程 前言 声明&#xff0c;此 NeHe OpenGL教程系列文章由51博客yarin翻译&#xff08;2010-08-19&#xff09;&#xff0c;本博客为转载并稍加整理与修改。对NeHe的OpenGL管线教程的编写&#xff0c;以及yarn的翻译整理表示感谢。 NeHe OpenGL第三十课…

andorid手机电脑操作

之前一直使用androidscreencast在pc上对手机进行操作,好久都没用了,前些天再次用的时候,提演示样例如以下: 决定还是自己写一个吧,由于7月份要做一个小分享,打算讲一些android的东西,须要在电脑上显示手机这边的画面,提供一定的操作. 花了一点时间做好了,给大家截一个图,代码放…

在组策略中使用脚本为域用户添加网络打印机

使用脚本为用户添加网络打印机 如果你想让培训部门的用户登录后就能添加网络打印机&#xff0c;就可以使用登录脚本来实现。其中DCServer是域控制&#xff0c;MarketPC1是市场部门的计算机&#xff0c;韩立辉用户是培训部门的用户。下面就验证使用组策略为培训部门的用户添加网…