使用go语言构建区块链 Part2.工作量证明

news2024/11/18 7:51:07

英文源地址

简介

在上一篇文章中, 我们构建了一个非常简单的数据结构, 这是区块链数据库的本质.并且我们可以通过它们之间的链式关系来添加区块: 每个区块都链接到前一个区块.哎, 我们的区块链实现有一个重大缺陷: 向链中添加区块既容易又便捷. 区块链和比特币的关键之一是增加新的区块是一项艰难的任务. 今天我们将修复这个缺陷.

工作量证明

区块链的一个关键思想是, 人们必须执行一些艰难的任务才能将数据放入其中.正是这项艰难的任务使区块链变得安全和一致.此外, 这种艰难的工作也会得到奖励(这就是人们miner获得比特币的方式).
这种机制于现实生活中的机制非常类似: 人们必须努力工作以获得奖励并维持他们的生活.在区块链中, 网络中的一些参与者(miner)通过工作来维持网络, 向其添加新的区块, 并获得他们的工作奖励. 由于他们的工作, 一个区块以一种安全的方式被合并到区块链中, 从而维护了整个区块链数据库的稳定性.值得注意的是,完成工作的人必须证明这一点.
这整个’努力工作并证明’的机制成为工作量证明. 这很难, 因为它需要大量的计算能力: 即使是高性能的计算机也无法快速完成. 此外,这项工作的难度会不断增加, 以保持每小时6个区块的新区块产量.在比特币中, 这种工作的目标是为一个区块找到一个哈希值, 满足一些要求. 这个哈希值可以作为证明. 因此, 寻找证明才是真正的工作.
最后要注意的是, 工作量证明算法必须满足一个要求: 做工作很难, 但是验证工作证明很容易. 证明通常交给其他人, 所以对他们来说, 不应该花太多时间来验证它.

哈希函数

在这一小节, 我们将讨论哈希(散列)函数.如果你熟悉这个概念, 可以跳过这一部分.
散列函数是为指定数据获取散列的过程. 哈希值是计算数据的唯一表示.哈希函数是一种接受任意大小的数据作为输入并生成固定大小的哈希值的函数.下面是哈希函数的一些关键特性:

  1. 不能从散列值恢复原始数据. 因此, 哈希操作不是加密操作
  2. 同样的数据只会有一个哈希值, 并且哈希值是唯一的
  3. 即使更改输入数据中的一个字节也会导致完全不同的哈希值
    在这里插入图片描述
    散列函数被广泛用于检查数据一致性.一些软件提供商除了发布软件包之外还会发布校验和.下载文件后, 您可以将其提供给散列函数, 并将生成的散列值于软件开发人员提供的散列值进行比较.
    在区块链中, 哈希操作被用来保证区块的一致性.哈希算法的输入数据包含前一个区块的哈希值, 因此不可能(或至少相当困难)修改链中的区块: 必须重新计算其哈希值和之后所有块的哈希值.

Hashcash

比特币使用的是Hashcash, 这是一种工作量证明算法, 最初是为了防止垃圾邮件而开发的. 它可以分为以下几个步骤:

  1. 以一些公开的数据(如果是电子邮件, 则是收件人的电子邮箱地址; 就比特币而言, 他就是区块头部信息)
  2. 给它添加一个计数器. 计数器从0开始计数.
  3. 获取数据+计数器组合的哈希值
  4. 检查哈希值是否满足一定的要求:
    1.如果满足, 就完成了
    2.如果不满足, 增加计数器并重复3,4步骤

因此, 这是一个蛮力算法:你改变计数器, 计算一个新的哈希值, 检查它, 增加计数器, 计算一个哈希值, 等等.这就是为什么它在计算机上代价很高的原因.
现在让我们仔细看看散列值必须满足的条件.在最初Hashcash实现中, 这个要求听起来像是’哈希的前20位必须为零’. 在比特币中, 这一要求是随着时间调整的, 因为根据设计, 尽管计算能力随着时间的推移而增加, 越来越多的miner加入网络, 但必须每10分钟生成一个区块.
为了演示这个算法, 我从签名的例子中(‘i like donuts’)中获取数据, 并找到一个以3个零字节开头的哈希值.在这里插入图片描述
ca07ca是计数器的十六进制值, 在十进制中对应的是13240266.

实现

好了, 我们讲完了理论, 开始写代码吧!首先, 让我们来定义miner的难度:

const targetBits = 24

在比特币中. '目标位’是存储区块开采难度的区块头.我们现在不会实现目标调整算法, 所以我们可以将难度定义为一个全局常数.
24是一个任意的数字, 我们的目标是在内存中占用少于256位的target数值.我们希望差值足够明显, 但又不会太大, 因为差值越大, 就越难找到合适的哈希值.

type ProofOfWork struct {
	block *Block
	target *big.Int
}

func NewProofOfWork(b *Block) *ProofOfWork {
	target := big.NewInt(1)
	target.Lsh(target, uint(256-trgetBits))
	
	pow := &ProofOfWork{b, target}
	return pow
}

在这里创建一个ProofOfWork结构, 它保存一个指向区块的指针和一个指向目标的指针.'target’是前一段描述的需求的另一个名称. 我们使用一个大整数, 因为我们将哈希值与target目标值进行比较: 我们将哈希值转换为一个大整数, 并检查它是否小于目标值.
在NewProofOfWork函数中, 初始化一个big.Int, 赋值为1并左移(256-targetBits)位.256是SHA-256哈希值的长度, 以bit为单位, 我们要使用的是SHA-256散列算法.target的十六进制表示为

0x10000000000000000000000000000000000000000000000000000000000

它占用了内存29个字节.下面是它与前面例子中的哈希值的视觉对比

0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

第一个哈希值(根据’I like donuts’计算)比目标值大, 因此它不是有效的工作量证明. 第二个哈希值('根据’I like donutsca07ca’计算)比目标值小, 因此它是一个有效的证明.
你可以将目标值视为范围上的上边界: 如果一个数字(哈希值)低于该边界, 则它是有效的, 反之亦然.降低边界将导致更少的有效数字, 因此寻找有效数字所需的工作会更加困难.
现在,我们需要对数据进行哈希值求解, 让我们准备一下:

func (pow *ProofOfWork) prepareData(nonce int) []byte {
	data := bytes.Join(
		[][]byte{
			pow.block.PrevBlockHash,
			pow.block.Data,
			IntToHex(pow.block.Timestamp),
			IntToHex(int64(targetBits)),
			IntToHex(int64(nonce)),
		},
		[]byte{})
	
	return data
}

这部分很简单: 我们只需将区块字段与目标和nonce合并. nonce是上面Hashcash算法描述中的计数器, 这是一个密码学术语.
好了, 所有的准备工作都完成了, 让我们来实现PoW算法的核心:

func (pow *ProofOfWork) Run() (int, []byte) {
	var hashInt big.Int
	var hash [32]byte
	nonce := 0
	maxNonce := math.MaxInt64
	fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
	for nonce < maxNonce {
		data := pow.prepareData(nonce)
		hash = sha256.Sum256(data)
		fmt.Printf("\r%x", hash)
		hashInt.SetBytes(hash[:])
		
		if hashInt.Cmp(pow.target) == -1 {
			break
		} else {
			nonce++
		}
	}
	fmt.Print("\n\n")
	return nonce, hash[:]
}

首先, 初始化变量: hashInt是hash值的整数形式.nonce是计数器值.接下来, 我们运行一个’无限’的循环:它受maxNonce的限制, 它等于math.MaxInt64; 这样做是为了避免nonce可能的溢出.尽管我们的PoW实现的难度太低, 计数器不会溢出, 但最好还是有个检查, 以防万一.
在循环中,我们:

  1. 准备数据
  2. 用SHA-256算法计算hash值
  3. 将哈希值转换为大整数
  4. 将该整数值与目标值进行比较

和之前解释的一样简单. 现在我们可以删除Block的SetHash方法并修改NewBlock函数:

func NewBlock(data string, prevBlockHash []byte) *Block {
	block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0}
	pow := NewProofOfWork(block)
	nonce, hash := pow.Run()

	block.Hash = hash[:]
	block.Nonce = nonce
	
	return block
}

在这里我们看到nonce被保存为Block的属性.这是必要的. 因为需要nonce来验证证明. Block结构现在是这样的:

type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
	Nonce         int
}

好了!让我们运行程序看看是否一切正常:

Prev. hash:
Data: Genesis Block
Hash: 0000005d42343f4f2aef73a27ed617c7b61768b87fdf9705b588db99477c4fb9

Prev. hash: 0000005d42343f4f2aef73a27ed617c7b61768b87fdf9705b588db99477c4fb9
Data: Send 1 BTC to Ivan
Hash: 000000fbcdefd2389096e73d2c3af7ad11d6c17d59756dd848bfaddfe3c3ac1b

Prev. hash: 000000fbcdefd2389096e73d2c3af7ad11d6c17d59756dd848bfaddfe3c3ac1b
Data: Send 2 more BTC to Ivan
Hash: 000000d01d9fa49f97f5abd759d1ae59cee4f36044117754a9347f7414ec4980

耶!你可以看到, 现在每个散列都以三个零字节开始, 并且需要一些时间来获得这些散列值.
还有一件事要做: 让我们使得验证工作量证明成为可能.

func (pow *ProofOfWork) Validate() bool {
	var hashInt big.Int
	
	data := pow.prepareData(pow.block.Nonce)
	hash := sha256.Sum256(data)
	hashInt.SetBytes(hash[:])
	
	isValid := hashInt.Cmp(pow.target) == -1
	
	return isValid
}

这就是我们需要保存nonce的原因.
让我们再检查一次, 确保一切正常.

func main() {
	bc := NewBlockchain()

	bc.AddBlock("Send 1 BTC to Ivan")
	bc.AddBlock("Send 2 more BTC to Ivan")

	for _, block := range bc.blocks {
		fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
		fmt.Printf("Data: %s\n", block.Data)
		fmt.Printf("Hash: %x\n", block.Hash)
		fmt.Println()

		pow := NewProofOfWork(block)
		fmt.Printf("Pow: %s\n", strconv.FormatBool(pow.Validate()))
		fmt.Println()
	}
}

输出结果

Prev. hash:
Data: Genesis Block
Hash: 00000074c46066dc882a455694f6457f18ce4552153c58d4c7c94c97b420c405

Pow: true

Prev. hash: 00000074c46066dc882a455694f6457f18ce4552153c58d4c7c94c97b420c405
Data: Send 1 BTC to Ivan
Hash: 000000bb4739f0563ef910e16afd8b6f1fe3d1837e355b22714c388b2b73506e

Pow: true

Prev. hash: 000000bb4739f0563ef910e16afd8b6f1fe3d1837e355b22714c388b2b73506e
Data: Send 2 more BTC to Ivan
Hash: 000000d7ae7d8736aa04542d09c58fc6bbc6437a21e4ad23bafe4d6ef140c8d2

Pow: true

总结

我们的区块链离实际框架又近了一步: 添加区块现在需要经过艰难的工作, 因此miner是可行的. 但它仍然缺乏一些关键性功能: 区块链数据库没有持久化存储, 没有钱包, 地址, 事务, 也没有共识机制.我们将在之后的文章中实现所有这些功能, 现在, 祝您miner愉快!

links:
https://github.com/Jeiwan/blockchain_go/tree/part_2
https://en.bitcoin.it/wiki/Block_hashing_algorithm
https://en.bitcoin.it/wiki/Proof_of_work
https://en.bitcoin.it/wiki/Hashcash

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

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

相关文章

pix2pixHD代码---readme

1&#xff1a;基础配置 要求大于等于11G的显卡&#xff0c;安装pytorch&#xff0c;下载代码。 2&#xff1a;测试 dataset文件中放的是一些例子&#xff0c;下载cityscape的预训练权重&#xff0c;放入到checkpoints文件夹下&#xff0c;测试模型。测试结果放在results文件夹…

【正点原子STM32连载】 第十八章 独立看门狗(IWDG)实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十八…

【Netty】Netty 架构设计(二)

文章目录 前言一、Selector 模型1.1 SelectableChannel1.2 Channel 注册到 Selector1.3 SelectionKey1.4 遍历 SelectionKey 二、事件驱动2.1 Channel2.2 回调2.3 Future2.4 事件及处理器 三、责任链模式3.1 责任链模式的优缺点3.2 ChannelPipeline3.3 将事件传递给下一个处理器…

从事软件测试2年跳槽4次,不给你涨薪真不怪老板……

前几天和朋友们聚餐&#xff0c;发现了一个有意思的现象。 所谓的聚餐其实就是大家对于工作生活的一个大型倒苦水现场。 最近工作太TM烦心了&#xff0c;越来越觉得没有意思了&#xff0c;感觉没啥前途&#xff0c;也不给人涨薪。 我也是&#xff0c;不仅工作压力大&#xff0…

华为云网站备案操作流程

目录 一、官方指引二、操作步骤1.操作场景2.前提条件3.操作步骤&#xff08;1&#xff09;下载华为云 APP&#xff08;2&#xff09;登录华为云 APP&#xff0c;在 “控制台” 中单击 “网站备案”&#xff0c;进入 APP 备案操作入口&#xff08;3&#xff09;验证备案类型&…

HarmonyOS低代码开发-在已有工程中添加Visual

使用低代码开发应用或服务有以下两种开发方式&#xff1a;创建一个支持低代码开发的新工程&#xff0c;开发应用或服务的UI界面。在已有工程中&#xff0c;创建Visual文件来开发应用或服务的UI界面。ArkTS工程和JS工程使用低代码的步骤相同&#xff0c;接下来以JS工程为例分别讲…

【LCD应用编程】练习一 —— 绘制点、线、矩形框

之前获取LCD屏幕参数信息时了解到&#xff0c;LCD屏是 FrameBuffer 设备&#xff0c;操作 FrameBuffer 设备 其实就是在读写 /dev/fb0 文件。除此之外&#xff0c;LCD屏上包含多个像素点&#xff0c;绘制点、线、矩形框本质是在修改这些像素点的颜色。 目录 1、定义 lcd_color…

Vulkan Tutorial 4

11 framebuffer 我们已经将渲染传递设置为期望一个与交换链图像格式相同的单一帧缓冲&#xff0c;但我们还没有实际创建任何图像。 在渲染过程创建期间指定的附件通过将它们包装到一个VkFramebuffer对象中来绑定。帧缓冲区对象引用 VkImageView代表附件的所有对象。 std::ve…

SSH和SFTP是否相同

SSH和SFTP是否相同&#xff1f;SSH和SFTP是经典的对。在确保通信安全方面&#xff0c;它们交织在一起&#xff0c;尽管它们具有类似的功能&#xff0c;但它们并不是一回事。那么&#xff0c;它们之间有什么区别&#xff1f;请仔细阅读&#xff0c;找出答案。 什么是SSH&#x…

Java使用xlsx-streamer和EasyExcel解决读取超大excel数据时OutOfMemoryError的问题

解决读取超大excel数据时OutOfMemoryError的问题 前言关于Excel相关技术场景复现与问题定位问题代码读取50MB40万行数据读取84MB100万行数据 解决方案一&#xff1a;xlsx-streamer引入依赖&#xff1a;示例代码&#xff1a;加载数据效果耗费资源对比 解决方案二&#xff1a;Eas…

静态时序分析-时序检查

时序检查 一旦在触发器的时钟引脚上定义了时钟,便会自动推断出该触发器的建立时间和保持时间检查。时序检查通常会在多个条件下执行,通常,最差情况的慢速条件对于建立时间检查很关键,而最佳情况的快速条件对于保持时间检查很关键。 1.建立时间检查 在时钟的有效沿到达触…

9:02面试,9:08就出来了,这问的我毫无还手之力····

就离谱了&#xff0c;现在面试都这么难的了嘛 从外包出来&#xff0c;没想到算法死在另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到8月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#x…

C语言代码封装MQTT协议报文,了解MQTT协议通信过程

【1】MQTT协议介绍 MQTT是一种轻量级的通信协议&#xff0c;适用于物联网&#xff08;IoT&#xff09;和低带宽网络环境。它基于一种“发布/订阅”模式&#xff0c;其中设备发送数据&#xff08;也称为 “发布”&#xff09;到经纪人&#xff08;称为MQTT代理&#xff09;&…

实现一个域名对应多个IP地址和DNS优缺点

DNS定义 DNS&#xff08;Domain Name System&#xff09;是因特网的一项服务&#xff0c;它作为域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使人更方便的访问互联网。 DNS作用 解析域名 人们在通过浏览器访问网站时只需要记住网站的域名即可&#xff0c;而不需…

清晰易懂IoC

1.IoC的目的在于让服务端的代码不需要改动 这段代码的问题在于&#xff0c;如果想要调用不同的dao层&#xff0c;就需要在服务端的代码Service层中进行改动 比如要调用dao1&#xff0c;Service层代码就是Dao dao1new Dao1() 比如要调用dao2&#xff0c;Service层代码就是Dao …

【JavaScript 递归】判断两个对象的键值是否完全一致,支持深层次查询,教你玩转JavaScript脚本语言

博主&#xff1a;東方幻想郷 Or _LJaXi 专栏分类&#xff1a;JavaScript | 脚本语言 JavaScript 递归 - 判断两个对象的键值 &#x1f315; 起因&#x1f313; 代码流程⭐ 第一步 判断两个对象的长度是否一致⭐ 第二步 循环 obj 进行判断两个对象⭐ 第三步 递归条件判断两个对象…

ChatGPT:你真的了解网络安全吗?浅谈攻击防御进行时之网络攻击新威胁

ChatGPT&#xff1a;你真的了解网络安全吗&#xff1f;浅谈网络安全攻击防御进行时 网络攻击新威胁1) 人工智能的应用2) 5G和物联网的崛起3) 云安全4) 社交工程的威胁 总结 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0…

大龄、零基础,想转行做网络安全。怎样比较可行?这届粉丝可真难带

昨晚上真的给我气孕了。 对于一直以来对网络安全兴趣很大&#xff0c;想以此作为以后的职业方向的人群。 不用担心&#xff0c;你可以选择兼顾工作和学习&#xff0c;以步步为营的方式尝试转行到网络安全领域。 那么&#xff0c;网络安全到底要学些什么呢&#xff1f; &…

getline()与cin.getline()

文章目录 1.getline2.cin.getline3.区别 1.getline 读取一行内容。定义为&#xff1a; istream& getline (istream& is, string& str, char delim);参数一&#xff1a;istream &is 表示一个输入流&#xff0c;譬如cin&#xff1b; 参数二&#xff1a;string…

Tensorflow2基础代码实战系列之双层RNN文本分类任务

深度学习框架Tensorflow2系列 注&#xff1a;大家觉得博客好的话&#xff0c;别忘了点赞收藏呀&#xff0c;本人每周都会更新关于人工智能和大数据相关的内容&#xff0c;内容多为原创&#xff0c;Python Java Scala SQL 代码&#xff0c;CV NLP 推荐系统等&#xff0c;Spark …