本文转载自微信公众号「区块链研究实验室」,作者链三丰。转载本文请联系区块链研究实验室公众号。
随着区块链给世界市场带来的革命,在作出预测之前了解基础知识至关重要。在本文中,我们将探讨权益证明的基础知识,该证明是一种区块链协议,类似于一种在区块链中伪造新区块的彩票方法。
本文的主要目标如下:
- 了解区块链领域的当前性能趋势。
- 通过GoLang中的一个工作示例学习权益证明。
- 升级您的计算机科学和Go编程技能。
这将是一个有趣的过程,让我们开始编写代码。
了解权益证明
股权证明(PoS)的基础实际上很简单。当然,该系统的核心组件是区块链本身。简而言之,区块链是一个不变的分类账,每个单独的区块都是从之前的区块以密码方式构建的。您永远都无法更改区块链的任何部分,因为连接到网络的每个人都可以轻松看到更改并驳斥您的区块链版本。
在过程中创造新的块是由您的区块链协议定义,比特币是基于工作量证明(PoW)协议构建的,当中需要越来越多的计算能力才能通过数学过程验证以前的交易,所以每次您验证区块中包含的交易列表时,都会以比特币的形式获得奖励。
因此,交易历史的证明在于您所从事的工作量,而完成这项工作的人称为“矿工”。PoW的一个日益严重的问题是,随着时间的流逝,解决这些数学难题所需的巨大计算能力。
权益证明在根本上是不同的,所以您无需在核算和扩展区块链上具有计算能力,而可以在区块链网络上“占用”一定数量的令牌(不一定是加密货币)。通常情况可以通过创建自己的“节点”来完成的,以方便您参与区块链生态系统。
如果节点采取勤恳的工作态度,您将有更大的机会在区块链中创造一个新的区块,并获得原始返还的奖励。被选择伪造下一个区块的可能性也与您在网络上投入的令牌数量成比例增加;反之如果采取散惰的工作态度,您的赌注可能会受到处罚甚至被完全撤回。这种奖励和惩罚方法旨在促进区块链中的诚实工作,而没有与工作量证明相关的计算可扩展性瓶颈。
现在我们已经有了PoS与PoW的概念概念,让我们继续在Go中编写一个有效的PoS示例。
Go中的权益证明
导入“区块链”
首先,除了定义自定义对象类型外,我们还需要在项目中包含一些Go包。这是您需要的软件包-我们将使用math/rand,crypto/sha256以及encoding/hex用于加密区块链方法。当然,这errors是Go的标准!
- package main
- import (
- "crypto/sha256"
- "encoding/hex"
- "errors"
- "fmt"
- "log"
- math "math/rand"
- "time"
- )
接下来是我们的自定义数据类型。使用Go可以使此超级简单structs。在这里我们有3个习惯types,第一个是PoSNetwork我们有一个Blockchain字段,该字段是对实例的引用的数组Block struct。我们将通过BlockchainHead字段跟踪最近添加的块,并且还将有一系列对的引用以Node struct用作Validators。
- type PoSNetwork struct {
- Blockchain []*Block
- BlockchainHead *Block
- Validators []*Node
- }
- type Node struct {
- Stake int
- Address string
- }
- type Block struct {
- Timestamp string
- PrevHash string
- Hash string
- ValidatorAddr string
- }
该Node结构将具有一个Stake字段,该字段代表它添加到网络中的令牌数量。该地址将是一个随机生成的字符串,以便我们可以跟踪哪个节点成功验证了下一个块。
最后,Block struct将包含跟踪区块链所需的信息。Timestamp创建该块时,我们将有一个for,来自上一个块的前一个哈希,Hash表示自身以及Node验证此块的的地址-所有type string。
逐块建造区块链砖
这是我们构建区块链的第一种方法。首先,在函数签名中,将此方法附加到,PoSNetwork struct并将该结构引用为n。然后,我们Node将对a的引用作为唯一参数。我们将返回一个新的Block引用数组来表示new Blockchain,对a的引用Block将是new BlockchainHead,以及可能出现error毛病的可能。
您会看到,ValidateBlockchain()我们甚至在尝试添加任何内容之前就立即调用了该方法。稍后我们将进行验证,但是只要知道我们发现要更改的区块链,就会知道有逻辑要惩罚a Node。
- func (n PoSNetwork) GenerateNewBlock(Validator *Node) ([]*Block, *Block, error) {
- if err := n.ValidateBlockchain(); err != nil {
- Validator.Stake -= 10
- return n.Blockchain, n.BlockchainHead, err
- }
- currentTime := time.Now().String()
- newBlock := &Block {
- Timestamp: currentTime,
- PrevHash: n.BlockchainHead.Hash,
- Hash: NewBlockHash(n.BlockchainHead),
- ValidatorAddr: Validator.Address,
- }
- if err := n.ValidateBlockCandidate(newBlock); err != nil {
- Validator.Stake -= 10
- return n.Blockchain, n.BlockchainHead, err
- } else {
- n.Blockchain = append(n.Blockchain, newBlock)
- }
- return n.Blockchain, newBlock, nil
- }
在检查Blockchain是否完好无损之后,我们获得了系统的当前时间,将其存储为Timestamp实例化new时的时间Block。我们还附上了Hash以前值,您可以轻松访问BlockchainHead。之后我们将在NewBlockHash()上调用方法BlockchainHead,并将输入的地址分配Node为我们的验证器地址。
一旦新块的字段被填满,我们调用ValidateBlockCandidate()上新Block,看看有没有错误。如果有,我们返回一层。如果没有,我们将把新块附加到区块链上。
但是,NewBlockHash()是如何工作的呢?我们有两个函数来完成为每个块创建唯一Hash的任务。函数 New Block Hash ()只是获取 Block 的所有信息,并将其连接成一个字符串传递给 new Hash ()。
- func NewBlockHash(block *Block) string {
- blockInfo := block.Timestamp + block.PrevHash + block.Hash + block.ValidatorAddr
- return newHash(blockInfo)
- }
- func newHash(s string) string {
- h := sha256.New()
- h.Write([]byte(s))
- hashed := h.Sum(nil)
- return hex.EncodeToString(hashed)
- }
接下来,newHash()将利用该crypto/sha256包创建一个存储为的新SHA256对象h。然后,我们将输入字符串s转换为字节数组,并将其写入h。最后,我们使用h.Sum()让h进入的格式,我们可以调用hex.EncodeToString(),使我们有一个string为我们的最终输出。
验证我们的区块链
如果你不能验证,则区块链就没有用。在这里,我们将ValidateBlockchain()方法附加到PoSNetwork结构,并返回一个可能的错误。如果区块链是空的或者只有一个区块,我们无法确保它是正确的所以我们只返回nil。
接下来,我们评估区块链中每对块之间的三个独立条件。第一个检查是前一个块的哈希值是否等于当前块为它的前一哈希值存储的值。
- func (n PoSNetwork) ValidateBlockchain() error {
- if len(n.Blockchain) <= 1 {
- return nil
- }
- currBlockIdx := len(n.Blockchain)-1
- prevBlockIdx := len(n.Blockchain)-2
- for prevBlockIdx >= 0 {
- currBlock := n.Blockchain[currBlockIdx]
- prevBlock := n.Blockchain[prevBlockIdx]
- if currBlock.PrevHash != prevBlock.Hash {
- return errors.New("blockchain has inconsistent hashes")
- }
- if currBlock.Timestamp <= prevBlock.Timestamp {
- return errors.New("blockchain has inconsistent timestamps")
- }
- if NewBlockHash(prevBlock) != currBlock.Hash {
- return errors.New("blockchain has inconsistent hash generation")
- }
- currBlockIdx--
- prevBlockIdx--
- }
- return nil
- }
我们还要检查是否在任何点上一个块的时间戳比当前块新。如果当前的Block是在2020年制造的,但之前的Blot是在2021年制造,此时就知道出问题了。
最后,我们还要直接计算Hash前一个的Block,我们仍然会取回Hash当前的Block。如果满足这些条件中的任何一个,则error由于我们的区块链处于篡改状态,我们将返回。
现在我们已经验证了整个区块链,我们需要确保要添加的下一个Block也是有效的。遵循与上述相同的条件检查,仅适用于要添加的单个新块。
- func (n PoSNetwork) ValidateBlockCandidate(newBlock *Block) error {
- if n.BlockchainHead.Hash != newBlock.PrevHash {
- return errors.New("blockchain HEAD hash is not equal to new block previous hash")
- }
- if n.BlockchainHead.Timestamp >= newBlock.Timestamp {
- return errors.New("blockchain HEAD timestamp is greater than or equal to new block timestamp")
- }
- if NewBlockHash(n.BlockchainHead) != newBlock.Hash {
- return errors.New("new block hash of blockchain HEAD does not equal new block hash")
- }
- return nil
- }
现在我们已经完成了向区块链添加新区块以及验证其正确性的步骤。那么,我们如何决定何时添加新块?
这就是我们的验证器起作用的地方,对于每个与网络有利益关系的节点,我们将通过抽奖方法随机选择一个节点,以伪造区块链中的下一个区块并获得奖励。
首先,我们首先需要一个Node。要添加新的Node给我们的PoSNetwork,我们称之为NewNode()它接受的初始股份Node,并返回一个新的数组Node引用。这里没有什么幻想,我们只是追加到n.Validators数组并调用randAddress()以为new生成唯一地址Node。
- func (n PoSNetwork) NewNode(stake int) []*Node {
- newNode := &Node{
- Stake: stake,
- Address: randAddress(),
- }
- n.Validators = append(n.Validators, newNode)
- return n.Validators
- }
- func randAddress() string {
- b := make([]byte, 16)
- _, _ = math.Read(b)
- return fmt.Sprintf("%x", b)
- }
那么,我们如何实际选择获胜者呢?有一点概率和统计信息!在该SelectWinner()方法中,我们首先通过覆盖范围找到网络内持有的全部股份n.Validators。我们还将所有权益大于零的节点添加到数组中winnerPool以进行可能的选择。
如果发现winnerPool为空,则返回error。。然后,我们使用该Intn()方法选择一个中奖号码,该方法将从0到我们的总投注额之间选择一个随机数。
- func (n PoSNetwork) SelectWinner() (*Node, error) {
- var winnerPool []*Node
- totalStake := 0
- for _, node := range n.Validators {
- if node.Stake > 0 {
- winnerPool = append(winnerPool, node)
- totalStake += node.Stake
- }
- }
- if winnerPool == nil {
- return nil, errors.New("there are no nodes with stake in the network")
- }
- winnerNumber := math.Intn(totalStake)
- tmp := 0
- for _, node := range n.Validators {
- tmp += node.Stake
- if winnerNumber < tmp {
- return node, nil
- }
- }
- return nil, errors.New("a winner should have been picked but wasn't")
- }
最后一部分是概率发挥作用的地方。为了使每个节点都有Stake与网络中的总数成正比的获胜机会,我们将Stake电流的增量累加Node到tmp变量中。如果在任何时候获胜的数字小于tmp,Node则被选为我们的获胜者。
汇集全部
我们现在所需要的就是将我们的函数和数据类型绑定在一起。我们在main()函数中做好所有的事情,第一步是设置一个随机种子与当前时间作为我们的输入。不要使用时间作为随机种子的输入,因为它实际上会在解码哈希输出时引入安全漏洞。
在这个示例中,我们需要实例化一个新的ProofStake网络,其中有一个被称为Genesis块,也就是我们所知的块。区块链中的第一个区块。一旦我们这样做,我们也设置网络的BlockchainHead等于第一个块。
- func main() {
- // set random seed
- math.Seed(time.Now().UnixNano())
- // generate an initial PoS network including a blockchain with a genesis block.
- genesisTime := time.Now().String()
- pos := &PoSNetwork{
- Blockchain: []*Block{
- {
- Timestamp: genesisTime,
- PrevHash: "",
- Hash: newHash(genesisTime),
- ValidatorAddr: "",
- },
- },
- }
- pos.BlockchainHead = pos.Blockchain[0]
- // instantiate nodes to act as validators in our network
- pos.Validators = pos.NewNode(60)
- pos.Validators = pos.NewNode(40)
- // build 5 additions to the blockchain
- for i := 0; i < 5; i++ {
- winner, err := pos.SelectWinner()
- if err != nil {
- log.Fatal(err)
- }
- winner.Stake += 10
- pos.Blockchain, pos.BlockchainHead, err = pos.GenerateNewBlock(winner)
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println("Round ", i)
- fmt.Println("\tAddress:", pos.Validators[0].Address, "-Stake:", pos.Validators[0].Stake)
- fmt.Println("\tAddress:", pos.Validators[1].Address, "-Stake:", pos.Validators[1].Stake)
- }
- pos.PrintBlockchainInfo()
- }
然后,我们添加两个节点的网络作为验证器与60和40令牌作为他们的初始股份。在五次迭代中,我们将为区块链选择一个新的赢家,如果有任何错误,我们的程序将崩溃-因为做原型我们通过新选择的赢家产生一个新的块,并打印出每一轮的每个节点的总桩。
最后,我们将打印出我们新制作的区块链:
- $ go run main.go
- Round 0
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 70
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 40
- Round 1
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 70
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 50
- Round 2
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 80
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 50
- Round 3
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 90
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 50
- Round 4
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 100
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 50
- Block 0 Info:
- Timestamp: 2021-04-12 MDT m=+0.000120025
- Previous Hash:
- Hash: c5d04de14efed52ce84889c6382f9d307d5b98093d93a84b419478
- Validator Address:
- Block 1 Info:
- Timestamp: 2021-04-12 MDT m=+0.000277288
- Previous Hash: c5d04de14efed52ce84889c6382f9d307d5b98093d93a
- Hash: d58e90a75b71ac62ef938fc0148314a7f864ad50bd702f959e2d27
- Validator Address: f8d44bb083078de97b8428f4f9548130
- Block 2 Info:
- Timestamp: 2021-04-12 MDT m=+0.000306562
- Previous Hash: d58e90a75b71ac62ef938fc0148314a7f864ad50bd702
- Hash: e6bfdd6c2c869607e2d9a81b84ddf4478756fedff78a03746cde11
- Validator Address: de6ae18584f02b3388569191a04a4b4a
- Block 3 Info:
- Timestamp: 2021-04-12 MDT m=+0.000321755
- Previous Hash: e6bfdd6c2c869607e2d9a81b84ddf4478756fedff78a0
- Hash: 8e3dbacc4a610b1665658bc9e7238963eda0d5bbbf3ce809e8fa6e
- Validator Address: f8d44bb083078de97b8428f4f9548130
- Block 4 Info:
- Timestamp: 2021-04-12 MDT m=+0.000333024
- Previous Hash: 8e3dbacc4a610b1665658bc9e7238963eda0d5bbbf3ce
- Hash: 22760f8deb96c354a4050a3c48741be062bccfa9c51571c170065a
- Validator Address: f8d44bb083078de97b8428f4f9548130
- Block 5 Info:
- Timestamp: 2021-04-12 MDT m=+0.000347521
- Previous Hash: 22760f8deb96c354a4050a3c48741be062bccfa9c5157
- Hash: d2a5047f7d8a7696c1d0fb9ec49b56d2e71bbcedaaebc83a18b7a5
- Validator Address: f8d44bb083078de97b8428f4f9548130