go语言实现PoS共识机制 - Go语言中文社区

go语言实现PoS共识机制


1.新建main.go文件

package main

import (
    "bufio"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "math/rand"
    "net"
    "os"
    "strconv"
    "sync"
    "time"

    "github.com/davecgh/go-spew/spew"    //spew包将区块链输出到终端
    "github.com/joho/godotenv"           //godotenv从前面的.env文件中读取配置信息
)
// Block represents each 'item' in the blockchain
type Block struct {
    Index     int
    Timestamp string
    BPM       int
    Hash      string
    PrevHash  string
    Validator string                     //由原先的difficulty改为了现在validator,即记账节点的地址
}

// Blockchain is a series of validated Blocks
var Blockchain []Block
var tempBlocks []Block
// tempBlocks是临时存储单元,在区块被选出来并添加到BlockChain之前,临时存储在这里

// candidateBlocks handles incoming blocks for validation
var candidateBlocks = make(chan Block)
//chan建立的是一个fifo队列,fifo先进先出
//candidateBlocks 是 Block 的通道,任何一个节点在提出一个新块时都将它发送到这个通道

// announcements broadcasts winning validator to all nodes
var announcements = make(chan string)
// announcements也是一个通道,主Go TCP服务器将向所有节点广播最新的区块链

var mutex = &sync.Mutex{}
//是一个标准变量,允许我们控制读/写和防止数据竞争

// validators keeps track of open validators and balances
var validators = make(map[string]int)
//validators 是节点的存储地址,同时也会保存每个节点持有的令牌数

//calculateBlockHash函数将一个block的所有字段连接到一起后,再调用 calculateHash 将字符串进行求hash。
// SHA256 hasing
// calculateHash is a simple SHA256 hashing function
func calculateHash(s string) string {
    h := sha256.New()
    h.Write([]byte(s))
    hashed := h.Sum(nil)
    return hex.EncodeToString(hashed)
}

//calculateBlockHash returns the hash of all block information
func calculateBlockHash(block Block) string {
    record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
    return calculateHash(record)
}

//与前一个区块的hash值,生成区块
func generateBlock(oldBlock Block, BPM int, address string) (Block, error) {

    var newBlock Block

    t := time.Now()

    newBlock.Index = oldBlock.Index + 1
    newBlock.Timestamp = t.String()    //时间戳
    newBlock.BPM = BPM
    newBlock.PrevHash = oldBlock.Hash
    newBlock.Hash = calculateBlockHash(newBlock)
    newBlock.Validator = address

    return newBlock, nil
}

//验证区块内容
// isBlockValid makes sure block is valid by checking index
// and comparing the hash of the previous block
func isBlockValid(newBlock, oldBlock Block) bool {
    if oldBlock.Index+1 != newBlock.Index {
        return false
    }

    if oldBlock.Hash != newBlock.PrevHash {
        return false
    }

    if calculateBlockHash(newBlock) != newBlock.Hash {
        return false
    }

    return true
}

func handleConn(conn net.Conn) {
    defer conn.Close()
//defer语句延迟执行一个函数,该函数被推迟到当包含它的程序return时再执行。

    go func() {
        for {
            msg := <-announcements  //通道的值赋给msg
            io.WriteString(conn, msg)
        }
    }()

    var address string   // 验证者地址,即Validator的地址

    // 验证者输入他所拥有的 tokens,tokens 的值越大,越容易获得新区块的记账权
    // allow user to allocate number of tokens to stake
    // the greater the number of tokens, the greater chance to forging a new block
    io.WriteString(conn, "Enter token balance:")
    scanBalance := bufio.NewScanner(conn)  //在终端输入获取token余额
// 允许验证者输入他持有的令牌数量,然后,该验证者被分配一个 SHA256地址,随后该验证者地址和验证者的令牌数被添加到验证者列表validators 中。
    for scanBalance.Scan() {  // scanBalance.Scan()遇到 n 或者rn循环一次,每输入一行内容必须回车,退出循环按组合键 Ctrl +D
        balance, err := strconv.Atoi(scanBalance.Text())  //strconv包实现字符串到数字的转换
        if err != nil {
            log.Printf("%v not a number: %v", scanBalance.Text(), err)
            return
        }
        t := time.Now()
        address = calculateHash(t.String())
        validators[address] = balance    
//余额赋给validators集合,集合的键值为其地址即哈希值,address为对时间序列求的hash值,防止有相同的address
        fmt.Println(validators)
        break
    }

    io.WriteString(conn, "nEnter a new BPM:")


    scanBPM := bufio.NewScanner(conn)   //BPM可以看作区块上要记录的数据

    go func() {
        for {
            // take in BPM from stdin and add it to blockchain after conducting necessary validation
            for scanBPM.Scan() {
                bpm, err := strconv.Atoi(scanBPM.Text())
                // if malicious party tries to mutate the chain with a bad input, delete them as a validator and they lose their staked tokens
//对作恶者进行惩罚(即输入),删除他作为一个记录者的权利,并扣除他的余额
                if err != nil {
                    log.Printf("%v not a number: %v", scanBPM.Text(), err)
		    delete(validators, address)
//delete()函数从map中删除一组键值对,delete(map, 键)
                    conn.Close()
                }

                mutex.Lock()   //互斥锁,即在一个并发进程中,加锁中的语句只能在解锁后再执行,不能并发同时执行
                oldLastIndex := Blockchain[len(Blockchain)-1]
                mutex.Unlock()

                // create newBlock for consideration to be forged
                newBlock, err := generateBlock(oldLastIndex, bpm, address)
                if err != nil {
                    log.Println(err)
                    continue
                }
                if isBlockValid(newBlock, oldLastIndex) {
                    candidateBlocks <- newBlock
                }
                io.WriteString(conn, "nEnter a new BPM:")
            }
        }
    }()

//一直输出区块信息数据
    // simulate receiving broadcast
    for {
        time.Sleep(time.Minute) //休眠1分钟
        mutex.Lock()
        output, err := json.Marshal(Blockchain)
        mutex.Unlock()
        if err != nil {
            log.Fatal(err)
        }
        io.WriteString(conn, string(output)+"n")
    }

}

//选择获取记账权的节点
// pickWinner creates a lottery pool of validators and chooses the validator who gets to forge a block to the blockchain
// by random selecting from the pool, weighted by amount of tokens staked
func pickWinner() {
    time.Sleep(30 * time.Second)  //休眠30秒钟
    mutex.Lock()
    temp := tempBlocks
    mutex.Unlock()

    lotteryPool := []string{}
    if len(temp) > 0 {

        // slightly modified traditional proof of stake algorithm
        // from all validators who submitted a block, weight them by the number of staked tokens
        // in traditional proof of stake, validators can participate without submitting a block to be forged
    OUTER:
        for _, block := range temp {
            // if already in lottery pool, skip
            for _, node := range lotteryPool {
                if block.Validator == node {
                    continue OUTER
                }
            }

            // lock list of validators to prevent data race
            mutex.Lock()
            setValidators := validators
            mutex.Unlock()

            k, ok := setValidators[block.Validator]
            if ok {
                for i := 0; i < k; i++ {
                    lotteryPool = append(lotteryPool, block.Validator)
                }
            }
        }

        // randomly pick winner from lottery pool
        s := rand.NewSource(time.Now().Unix())    //time.Now().Unix()将时间戳转化为时间格式
//rand.NewSource()生成伪随机数 time.Now().Unix()为种子值
// 使用当前的时间生成一个随机源,也就是随机种子

        r := rand.New(s)
 // 生成一个rand
        lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))]
//r.Intn 返回一个随机的格式化后的时间n,0 <= n <=len(lotteryPool)

	 // add block of winner to blockchain and let all the other nodes know
        for _, block := range temp {
            if block.Validator == lotteryWinner {
                mutex.Lock()
                Blockchain = append(Blockchain, block)
                mutex.Unlock()
                for _ = range validators {
                    announcements <- "nwinning validator: " + lotteryWinner + "n"
                }
                break
            }
        }
    }

    mutex.Lock()
    tempBlocks = []Block{}
    mutex.Unlock()
}

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatal(err)
    }

    // create genesis block
//初始化创世区块
    t := time.Now()
    genesisBlock := Block{}
    genesisBlock = Block{0, t.String(), 0, calculateBlockHash(genesisBlock), "", ""}
    spew.Dump(genesisBlock)
//使用spew.Dump 这个函数可以以非常美观和方便阅读的方式将 struct、slice 等数据打印在控制台里,方便我们调试。
    Blockchain = append(Blockchain, genesisBlock)
//将创世区块添加到结构体数组Blockchain中

    // start TCP and serve TCP server
    server, err := net.Listen("tcp", ":"+os.Getenv("ADDR"))   //os.Getenv()从文件中获取环境变量
    if err != nil {
        log.Fatal(err)
    }
    defer server.Close()

    go func() {
        for candidate := range candidateBlocks {
            mutex.Lock()
            tempBlocks = append(tempBlocks, candidate)
            mutex.Unlock()
        }
    }()

    go func() {
        for {         //无限循环下去
            pickWinner()
        }
    }()

    for {
        conn, err := server.Accept()
        if err != nil {
            log.Fatal(err)
        }
        go handleConn(conn)
    }
}

2.新建 .env文件,并写入以下代码

ADDR=9000

3.运行main.go文件
在这里插入图片描述

export GOPATH=/home/go
go run main.go

4.进入9000端口

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

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢