这可能是全网最简单的POS共识机制算法 - Go语言中文社区

这可能是全网最简单的POS共识机制算法


共识算法是区块链非常重要的一种算法,简单来说,共识算法就是为了使网络中的节点得到一致的认可。就比如说合作双方达成一致,共同遵守某个合约。

传统的分布式系统中,由于有着中心服务器进行统一管理,各个子服务器只需要跟着中心服务器进行操作即可。而区块链作为一种去中心化分布式系统,并没有传统意义上的中心服务器,因此传统的分布一致性算法并不适用于区块链网络。每个服务器都可以是中心服务器,那么这样就需要一种新的算法来维护系统中的一致性了。现在区块链中,主流的共识算法一般为三种:POW,POS和DPOS。

工作量证明(Proof Of Work)

工作量证明算法之前已经提到过,在实现微型区块链时用的就是pow算法,这是最传统最经典的共识算法,应用在比特币中。它的原理就是寻找一个以若干个0为开头的哈希串。举个例子,给定字符串“blockchain”,我们给出的工作量要求是,可以在这个字符串后面连接一个称为nonce的整数值串,对连接后的字符串进行sha256哈希运算,如果得到的哈希结果(以十六进制的形式表示)是以若干个0开头的,则验证通过。为了达到这个工作量证明的目标,我们需要不停地递增nonce值,对得到的新字符串进行sha256哈希运算。

原理非常的简单,也可以说是只需要算就行了。

这样也存在一个问题,那就是它只要求算力,不要求理解区块链的本身,也就造成了现如今比特币的大量矿机无脑挖矿。就像演唱会的黄牛,买票只为了获取它的利益,并不在意演唱会本身的内容。为了避免这种靠纯运算的共识机制,相继提出来很多新的共识算法。

权益证明(Proof Of Stake)

今天重点讲讲POS机制。

权益证明,它提出来币龄的概念,币龄 = 持有的货币数 * 持有时间,比如说我有100币,持有了30天,那么我的币龄就是3000。币龄越大的节点呢获取记账权(也就是生成区块)的概率越大。每次记完账之后,该节点的币龄就清空。当然,刚获取的币不能直接参与币龄计算,一般是30天之后开始计算币龄。

那么这样会产生一个问题:囤币来获取绝对记账权。为了防止这种情况发生,会设置一个最大概率,一般设置90天时获得最大记账概率,之后不再增加。

但是POS本质上还是类似POW的暴力计算,只不过币多的人更有可能挖到矿,降低了POW的全网一致难度,谁算力强谁记账的局限性。

具体来看代码怎么实现它的吧:


导入所需包:

package main

import (
  "bytes"
  "crypto/sha256"
  "encoding/binary"
  "fmt"
  "math"
  "math/big"
  "math/rand"
  "time"
)

定义常量设置最大最小概率,这里由于不可能等30天之后再计算币龄,我设置10秒之后开始计算,并且防止数据过大,按分钟计时:

const (
  dif         = 2
  INT64_MAX   = math.MaxInt64
  MaxProbably = 255
  MinProbably = 235
  MaxCoinAge  = 10
  Minute      = 60
)

定义币的数据结构和一个币池,这里币中提出了地址Address概念,一般理解为钱包地址,一般一个钱包属于一个用户,表明这个币的所有权是这个地址对应的用户:

type Coin struct {
  Time    int64
  Num     int
  Address string
}

var CoinPool []Coin

定义区块和链:

type Block struct {
  PrevHash  []byte
  Hash      []byte
  Data      string
  Height    int64
  Timestamp int64
  Coin      Coin
  Nonce     int
  Dif       int64
}

type BlockChain struct {
  Blocks []Block
}

init函数,设置随机数种子和币池初始化,会在main函数开始前自动执行:

func init() {
  rand.Seed(time.Now().UnixNano())
  CoinPool = make([]Coin, 0)
}

生成创世块,传入的参数是区块上的数据data和挖矿地址addr,这里每个区块的币随机给出1到5,币池增加创世块的币:

func GenesisBlock(data string, addr string) *BlockChain {
  var bc BlockChain
  bc.Blocks = make([]Block, 1)
  newCoin := Coin{
    Time:    time.Now().Unix(),
    Num:     1 + rand.Intn(5),
    Address: addr,
  }
  bc.Blocks[0] = Block{
    PrevHash:  []byte(""),
    Data:      data,
    Height:    1,
    Timestamp: time.Now().Unix(),
    Coin:      newCoin,
    Nonce:     0,
  }
  bc.Blocks[0].Hash, bc.Blocks[0].Nonce, bc.Blocks[0].Dif = ProofOfStake(dif, addr, bc.Blocks[0])
  CoinPool = append(CoinPool, newCoin)
  return &bc
}

生成新区块函数,还是一样,prevHash对应上一个节点的Hash,随机给出1-5币奖励,记录该币的所有者地址addr,币池增加新区块的币:

func GenerateBlock(bc *BlockChain, data string, addr string) {
  prevBlock := bc.Blocks[len(bc.Blocks)-1]
  newCoin := Coin{
    Time:    time.Now().Unix(),
    Num:     1 + rand.Intn(5),
    Address: addr,
  }
  b := Block{
    PrevHash:  prevBlock.Hash,
    Data:      data,
    Height:    prevBlock.Height + 1,
    Timestamp: time.Now().Unix(),
  }
  b.Hash, b.Nonce, b.Dif = ProofOfStake(dif, addr, b)
  b.Coin = newCoin
  bc.Blocks = append(bc.Blocks, b)
  CoinPool = append(CoinPool, newCoin)
}

权益证明算法:

POS机制的核心算法了,设置的是10s之后开始计算币龄,根据币龄coinAge调整当前难度Dif以获取真实难度realDif。挖矿还是类似POW寻找一个nonce加在尾部,寻找到第一个满足真实难度realDif的sha256哈希串:

func IntToHex(num int64) []byte {
  buff := new(bytes.Buffer)
  err := binary.Write(buff, binary.BigEndian, num)
  if err != nil {
    panic(err)
  }
  return buff.Bytes()
}

func ProofOfStake(dif int, addr string, b Block) ([]byte, int, int64) {
  var coinAge int64
  var realDif int64
  realDif = int64(MinProbably)
  curTime := time.Now().Unix()

  for k, i := range CoinPool {
    if i.Address == addr && i.Time+MaxCoinAge < curTime {
      //币龄增加, 并设置上限
      var curCoinAge int64
      if curTime-i.Time < 3*MaxCoinAge {
        curCoinAge = curTime - i.Time
      } else {
        curCoinAge = 3 * MaxCoinAge
      }
      coinAge += int64(i.Num) * curCoinAge
      //参与挖矿的币龄置为0
      CoinPool[k].Time = curTime
    }
  }

  if realDif+int64(dif)*coinAge/Minute > int64(MaxProbably) {
    realDif = MaxProbably
  } else {
    realDif += int64(dif) * coinAge / Minute
  }

  target := big.NewInt(1)
  target.Lsh(target, uint(realDif))
  nonce := 0
  for ; nonce < INT64_MAX; nonce++ {
    check := bytes.Join(
      [][]byte{
        b.PrevHash,
        []byte(b.Data),
        IntToHex(b.Height),
        IntToHex(b.Timestamp),
        IntToHex(int64(nonce)),
      },
      []byte{})
    hash := sha256.Sum256(check)
    var hashInt big.Int
    hashInt.SetBytes(hash[:])
    if hashInt.Cmp(target) == -1 {
      return hash[:], nonce, 255 - realDif
    }
  }

  return []byte(""), -1, 255 - realDif
}

再写个打印区块和打印币池函数:

func Print(bc *BlockChain) {
  for _, i := range bc.Blocks {
    fmt.Printf("PrevHash: %xn", i.PrevHash)
    fmt.Printf("Hash: %xn", i.Hash)
    fmt.Println("Block's Data: ", i.Data)
    fmt.Println("Current Height: ", i.Height)
    fmt.Println("Timestamp: ", i.Timestamp)
    fmt.Println("Nonce: ", i.Nonce)
    fmt.Println("Dif: ", i.Dif)
  }
}

func PrintCoinPool() {
  for _, i := range CoinPool {
    fmt.Println("Coin's Num: ", i.Num)
    fmt.Println("Coin's Time: ", i.Time)
    fmt.Println("Coin's Owner: ", i.Address)
  }
}

写个main函数测试看看,虚拟两个地址,当然啊真实区块链的地址不可能这么简单,需要使用公钥生成地址算法。

给addr1先记账,获取一定币,等待可以计算币龄时再次记账,再给新地址addr2记账,看看难度对比:

func main() {
  addr1 := "192.168.1.1"
  addr2 := "192.168.1.2"
  bc := GenesisBlock("reigns", addr1)
  GenerateBlock(bc, "send 1$ to alice", addr1)
  GenerateBlock(bc, "send 1$ to bob", addr1)
  GenerateBlock(bc, "send 2$ to alice", addr1)
  time.Sleep(11 * time.Second)
  GenerateBlock(bc, "send 3$ to alice", addr1)
  GenerateBlock(bc, "send 4$ to alice", addr2)
  Print(bc)
  PrintCoinPool()
}

看看打印结果吧:

可以看到啊,addr1记前四笔账的时候难度都是20,第五笔的时候,由于之前延迟了11秒,开始计算币龄,难度减小到了15,第六笔账由addr2记账,难度又回到了20,同时看币池,参与币龄计算的币的Time统一到第5笔记账时间,也就是刷新了币龄。

当然啊,真实环境中肯定不是顺序记账,每个节点都会竞争记账权,最先计算出满足难度的hash串的节点优先记账,并向全网广播,开始下一次记账权争夺。

好了,今天到这里了~下次讲讲更有效率的共识算法,也是三大主流算法的最后一个:DPOS。

如果对区块链感兴趣,可以关注下面这个公众号哦,推送的全是区块链干货~

 

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/reigns_/article/details/103058257
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-04-19 18:03:24
  • 阅读 ( 1119 )
  • 分类:算法

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢