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

英文源地址

简介

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

工作量证明

区块链的一个关键思想是, 人们必须执行一些艰难的任务才能将数据放入其中.正是这项艰难的任务使区块链变得安全和一致.此外, 这种艰难的工作也会得到奖励(这就是人们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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

面对当下各种不确定性,如何面对,每天很忙碌,不慌

&#xff08;点击即可收听&#xff09; 疫情时期,都难,疫情之后,发现还更难 随着互联网的热度的下降,各大小公司纷纷勒紧裤腰带,受打击最大的无疑是底层打工人 每天一打开手机,会发现,一些大厂裁员信息霸榜头条,年龄也是一道坎 刚刚看到一个大v发的&#xff1a; 一个原先是跨国…

如何在 OpenSUSE 上安装 VirtualBox 7?

VirtualBox 是一款开源的虚拟化软件&#xff0c;允许用户在单个计算机上运行多个操作系统。本文将详细介绍如何在 OpenSUSE 上安装 VirtualBox 7。以下是安装过程的步骤&#xff1a; 步骤一&#xff1a;下载 VirtualBox 7 首先&#xff0c;我们需要下载 VirtualBox 7 的安装包…

真题详解(语法分析输入记号流)-软件设计(八十)

真题详解&#xff08;求叶子结点数&#xff09;-软件设计&#xff08;七十九)https://blog.csdn.net/ke1ying/article/details/130787349?spm1001.2014.3001.5501 极限编程XP最佳实践&#xff1a; 测试先行、 按日甚至按小时为客户提供可运行的版本。 组件图的 插座 和插头…

Pytorch的CNN,RNNLSTM

CNN 拿二维卷积举例&#xff0c;我们先来看参数 卷积的基本原理&#xff0c;默认你已经知道了&#xff0c;然后我们来解释pytorch的各个参数&#xff0c;以及其背后的计算过程。 首先我们先来看卷积过后图片的形状的计算&#xff1a; 参数&#xff1a; kernel_size &#xff…

使用Linkage Mapper工进行物种分布建模的步骤详解(含实际案例分析)

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Linkage Mapper解密数字世界链接 文章目录 引言:一、介绍二、数据准备2.1 物种分布数据获取2.2 环境变量数据获取2.3 数据预处理

车道线检测

前言 目前&#xff0c;车道线检测技术已经相当成熟&#xff0c;主要应用在自动驾驶、智能交通等领域。下面列举一些当下最流行的车道线检测方法&#xff1a; 基于图像处理的车道线检测方法。该方法是通过图像处理技术从摄像头传回的图像中提取车道线信息的一种方法&#xff0c…

FreeRTOS(6)----软件定时器

一&#xff0c;软件定时器概述 软件定时器允许设置一段时间&#xff0c;当设定的时间到达之后就会执行指定的功能函数&#xff0c;被定时器调用的这个函数叫做定时器的回调函数。回调函数的两次执行间隔叫做定时器的定时周期。 二&#xff0c;回调函数的注意事项 回调函数是…

Django框架之模板过滤器

过滤器可以用来修改变量的显示样式。 使用方式 格式&#xff1a;{{变量|过滤器方法}}。可以连续使用&#xff0c;形式如&#xff1a;{{变量|过滤器方法1|过滤器方法2}}。 过滤器如下 Lower 转化为小写字母 格式&#xff1a;变量|lower Upper 转化为大写字母 格式&#xf…

chatgpt赋能python:Python的BeautifulSoup库和find_all()方法

Python的Beautiful Soup库和find_all()方法 在Web爬虫中&#xff0c;我们需要从网页中找到特定的HTML标记或属性&#xff0c;以便提取我们需要的数据。对于Python开发人员而言&#xff0c;Beautiful Soup是最流行的解析HTML和XML的库之一。该库可以让我们轻松地从HTML解析器中…

【Tcp通信服务器流程】

TCP通信流程 1、服务器端&#xff08;被动接收连接的角色&#xff09; &#xff08;1&#xff09;创建一个用于监听的套接字 - 监听&#xff1a;监听有客户端的连接 - 套接字&#xff1a;这个套接字其实就是一个文件描述符 &#xff08;2&#xff09;将这个监听文件描述符和…

26 KVM热迁移虚拟机

文章目录 26 KVM热迁移虚拟机26.1 总体介绍26.1.1 概述26.1.2 应用场景26.1.3 注意事项和约束限制 26.2 热迁移操作26.2.1 前提条件26.2.2 热迁移脏页率预测&#xff08;可选&#xff09;26.2.3 设置热迁移参数&#xff08;可选&#xff09;26.2.4 热迁移操作&#xff08;共享存…

微服务之事务处理

Informal Essay By English Hi guys、happy labor day. Everyone should have a good time to relax during the Labor Day holiday. But don’t forget to improve yourself during the holiday period 参考书籍&#xff1a; “凤凰架构” “微服务架构设计模式” 引言 …

golang 服务中 context 超时处理的思考

文章目录 前言起因&#xff1a;日志告警引发的思考什么是contextcontext的作用context超时之后继续执行 or 中断 最后 前言 公司运行的服务代码中&#xff0c;随处可见各种各样的日志信息&#xff0c;其中大多数是用来记录各种异常的日志&#xff0c;一方面&#xff0c;当出现…

Linux终端环境下的浏览器Lynx和Carbonyl 的基本使用方法

一、Carbonyl 是基于Chromium开发的运行于终端下的现代版浏览器&#xff0c;比Lynx的功能更好&#xff0c;目前尚在滚动开发过程中&#xff0c;但也基本可以用了。 1. 2安装非常简单&#xff0c;下载Binaries&#xff0c;Docker&#xff0c;nmp install, 都可以。 注意&#…

FPGA远程更新/远程调试的一种简单方法

之前介绍过一种远程&#xff08;无线&#xff09;更新的方式&#xff0c;详见《起飞&#xff01;通过无线WIFI下载调试FPGA》&#xff0c;这种方式缺点有两个&#xff1a;一是速度较慢&#xff1b;二是我们的设备中需要增加一个无线设备&#xff0c;增加成本的同时增加了暴露的…

SpringCloud(23):Sentinel对Spring Cloud Gateway的支持

代码地址&#xff1a;https://download.csdn.net/download/u013938578/87767363 从 1.6.0 版本开始&#xff0c;Sentinel 提供了 Spring Cloud Gateway 的适配模块&#xff0c;可以提供两种资源维度的限流&#xff1a; route 维度&#xff1a;即在 Spring 配置文件中配置的路…

setContentHuggingPriority和setContentCompressionResistancePriority的使用

需求&#xff1a; 两个label并排显示&#xff0c;文字内容由服务器返回&#xff0c;label宽度以文字内容自适应&#xff0c;label之间间距大于等于10. 需要考虑以下情况&#xff1a; 当两个label的宽度和 < 屏幕宽度时&#xff0c;各自设置约束&#xff0c;无需处理&#…

GPT-4版Windows炸场,整个系统就是一个对话机器人,微软开建AI全宇宙

原创 智东西编辑部 智东西 Windows的GPT时刻到来&#xff0c;变革PC行业。 作者 | 智东西编辑部 今日凌晨&#xff0c;Windows迎来了GPT-4时刻&#xff01; 在2023微软Build大会上&#xff0c;微软总裁萨蒂亚纳德拉&#xff08;Satya Nadella&#xff09;宣布推出Windows Co…

【模型预测】A-4D战斗机姿态控制的模型预测控制方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

力扣 695. 岛屿的最大面积

一、题目描述 给你一个大小为 m x n 的二进制矩阵 grid。 岛屿是由一些相邻的 1&#xff08;代表土地&#xff09;构成的组合&#xff0c;这里的相邻要求两个 1 必须在水平或者竖直的四个方向上相邻。你可以假设 grid 的四个边缘都被 0&#xff08;代表水&#xff09;包围着。…