内容简介:在上一篇文章中,我们构建了一个非常简单的数据结构,这是区块链数据库的本质。我们可以通过它们之间的链状关系为它添加区块:每个区块都链接到前一个块。我们的区块链实现有一个重大缺陷:向链中添加区块很容易。区块链和比特币的核心之一是:添加新区块是一项艰苦的工作。今天我们要解决这个缺陷。区块链的一个关键思想是,必须进行一些艰苦的工作才能将数据放入其中。正是这项艰苦的工作使区块链变得安全和一致。此外,这项艰苦的工作也得到了回报(这也就是通过挖矿获得币)。这种机制与现实生活中的机制非常相似:人们必须努力工作,才能获得奖励
Introduction
在上一篇文章中,我们构建了一个非常简单的数据结构,这是区块链数据库的本质。我们可以通过它们之间的链状关系为它添加区块:每个区块都链接到前一个块。我们的区块链实现有一个重大缺陷:向链中添加区块很容易。区块链和比特币的核心之一是:添加新区块是一项艰苦的工作。今天我们要解决这个缺陷。
Proof-of-Work(工作量证明)
区块链的一个关键思想是,必须进行一些艰苦的工作才能将数据放入其中。正是这项艰苦的工作使区块链变得安全和一致。此外,这项艰苦的工作也得到了回报(这也就是通过挖矿获得币)。
这种机制与现实生活中的机制非常相似:人们必须努力工作,才能获得奖励并维持生命。在区块链中,网络的一些参与者(矿工)努力维持网络,向其添加新区块,并获得对其工作的奖励。由于他们的工作,区块以安全的方式被整合到区块链中,这保持了整个区块链数据库的稳定性。值得注意的是,完成工作的人必须证明这一点。
这整个“努力工作和证明”的机制被称为工作量证明。这很难,因为它需要大量的计算能力:即使是高性能的计算机也无法快速完成。另外,这个工作的困难度会随着时间不断增长,以保持每 10 分钟出 1 个新块的速度。在比特币中,这种工作的目标是找到一个块的哈希,满足一些要求。这个哈希,也就充当了证明的角色。因此,寻求证明(寻找有效哈希),就是矿工实际要做的事情。
最后要注意的一点是。工作量证明算法必须满足要求:完成工作很难,但验证证明很容易。证明通常会交给其他人,因此对他们而言,验证它不应该花费太多时间。
Hashing
在本段中,我们将讨论哈希。如果您熟悉这个概念,可以跳过这一部分。
哈希是获取指定数据的哈希的过程。哈希是计算数据的唯一表示。哈希函数是一种获取任意大小数据并生成固定大小哈希的函数。以下是哈希的一些主要功能:
1、无法从哈希中恢复原始数据。因此,哈希不是加密。
2、某些数据只能有一个哈希值,哈希值是唯一的。
3、改变输入数据中的一个字节将导致完全不同的哈希。
哈希函数广泛用于检查数据的一致性。除软件包外,某些软件提供商还会发布校验和。下载文件后,您可以将其提供给哈希函数,并将生成的哈希与软件开发人员提供的哈希进行比较。
在区块链中,哈希用于保证区块的一致性。哈希算法的输入数据包含前一个区块的哈希,因此无法(或者至少非常困难)修改链中的区块:必须重新计算其哈希和其后所有区块的哈希值。
Hashcash
比特币使用 Hashcash ,一种最初开发用于防止电子邮件垃圾邮件的工作量证明算法。它可以分为以下几个步骤:
1、拿一些公开的数据(如果是电子邮件,它是接收者的电子邮件地址;对于比特币,它是块头)。
2、添加一个计数器。
3、计数器从0开始。将 data(数据) 和 counter(计数器) 组合到一起,获得一个哈希
4、检查哈希符合某些要求。如果确实如此,那就完成了。如果没有,请增加计数器并重复步骤3和4。
因此,这是一个暴力算法:你改变计数器,计算一个新的哈希,检查它,增加计数器,计算一个哈希等。这就是为什么它的计算成本很高。
现在让我们仔细看看哈希必须满足的要求。在最初的Hashcash实现中,它的要求是 “一个哈希的前 20 位必须是 0”。在比特币中,需求会不时调整,因为,尽管计算能力随着时间的推移而增加,并且越来越多的矿工加入网络,但必须保证每 10 分钟生成一个块。
为了演示这个算法,我从前面的例子中获取了数据(“我喜欢甜甜圈”)并找到了一个以3个零字节开头的哈希:
ca07ca是计数器的十六进制值,十进制系统中为13240266。
Implementation
好的,我们已经完成了理论,让我们编写代码!首先,让我们来定义挖掘的难度:
const targetBits = 24
在比特币中,当一个块被挖出来以后,“target bits” 代表了区块头里存储的难度,也就是开头有多少个 0。目前我们不会实现目标调整算法,因此我们可以将难度定义为全局常量。
24是一个任意数字,我们的目标是让一个目标在内存中占用少于256位。我们希望差异足够大,但不要太大,因为差异越大,找到合适的散列越困难。
type ProofOfWork struct { block *Block target *big.Int } func NewProofOfWork(b *Block) *ProofOfWork { target := big.NewInt(1) target.Lsh(target, uint(256-targetBits)) pow := &ProofOfWork{b, target} return pow }
这里,我们构造了 ProofOfWork 结构,里面存储了指向一个块( block
)和一个目标( target
)的指针。这里的 “目标” ,也就是前一节中所描述的必要条件。这里使用了一个 大整数 ,我们会将哈希与目标进行比较:先把哈希转换成一个大整数,然后检测它是否小于目标。
在 NewProofOfWork 函数中,我们将 big.Int 初始化为 1,然后左移 256 - targetBits
位。 256 是一个 SHA-256 哈希的位数,我们将要使用的是 SHA-256 哈希算法。 target(目标) 的 16 进制形式为:
0x10000000000000000000000000000000000000000000000000000000000复制代码
它在内存中占用29个字节。这里是与前面例子中的哈希的视觉比较:
0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3 0000010000000000000000000000000000000000000000000000000000000000 0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca
第一个哈希值(根据“我喜欢甜甜圈”计算)比目标大,因此它不是有效的工作证明。第二个哈希(计算在“我喜欢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 }
这个部分很简单:我们只是将 target ,nonce 与 Block 进行合并。nonce这里是上面Hashcash描述的计数器,这是一个加密术语。
好的,所有准备工作都已完成,让我们实现PoW算法的核心:
func (pow *ProofOfWork) Run() (int, []byte) { var hashInt big.Int var hash [32]byte nonce := 0 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实现的难度太低而不能使计数器溢出,但为了以防万一,进行此检查仍然更好。
在这个循环中,我们做的事情有:
- 准备数据
- 用 SHA-256 对数据进行哈希
- 将哈希转换成一个大整数
- 将这个大整数与目标进行比较
跟之前所讲的一样简单。现在我们可以移除 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 }
好的!让我们运行程序,看看一切是否正常:
Mining the block containing "Genesis Block" 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Mining the block containing "Send 1 BTC to Ivan" 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Mining the block containing "Send 2 more BTC to Ivan" 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe Prev. hash: Data: Genesis Block Hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Data: Send 1 BTC to Ivan Hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Data: Send 2 more BTC to Ivan Hash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe复制代码
好极了!您可以看到每个哈希现在以三个零字节开始,并且需要一些时间来获取这些哈希值。还有一件事要做:对工作量证明进行验证。
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 } 复制代码
这就是我们需要保存的随机数的地方。让我们再检查一切都没问题:
func main() { ... for _, block := range bc.blocks { ... pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) fmt.Println() } } 复制代码
Output:
... Prev. hash: Data: Genesis Block Hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038 PoW: true Prev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038 Data: Send 1 BTC to Ivan Hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b PoW: true Prev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b Data: Send 2 more BTC to Ivan Hash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57a PoW: true
Conclusion
我们的区块链更接近其实际架构:现在添加区块需要努力工作,因此可以进行挖掘。但它仍然缺乏一些关键特征:区块链数据库不是持久的,没有钱包,地址,交易,也没有共识机制。所有这些我们将在未来的文章中实现,现在,快乐的采矿吧!
英文原文:https://jeiwan.cc/posts/building-blockchain-in-go-part-2/
以上所述就是小编给大家介绍的《使用Go构建区块链 第2部分:工作量证明》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 何在不使用工作量证明的情况下实现公平且高效的提议
- 音乐和编程的协奏曲:新的工作量证明算法的诞生
- Oracle AWR(自动工作量资料档案库)的管理与维护详解
- 也许是国内第一篇把以太坊工作量证明从算法层将清楚的
- 能用机器完成的,千万别堆工作量:持续集成中的性能自动化测试
- 清华大作业指导:一人单刷雨课堂需要多少工作量?快手工程师详解如何两周搞定
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Node.js in Action
Mike Cantelon、Marc Harter、TJ Holowaychuk、Nathan Rajlich / Manning Publications / 2013-11-25 / USD 44.99
* Simplifies web application development * Outlines valuable online resources * Teaches Node.js from the ground up Node.js is an elegant server-side JavaScript development environment perfect for scal......一起来看看 《Node.js in Action》 这本书的介绍吧!