共计 5017 个字符,预计需要花费 13 分钟才能阅读完成。
作者:ReganYue
起源:恒生 LIGHT 云社区
一、前言
后面咱们简略的介绍了一个基于 PoS 共识算法的例子,明天咱们来解析一个升级版的例子。如果喜爱博主的话,记得点赞,关注,珍藏哦~
二、本例中的一些数据结构
type Block struct {
Index int
TimeStamp string
BPM int
HashCode string
PrevHash string
Validator string
}
var Blockchain []Block
var tempBlocks []Block
var candidateBlocks = make(chan Block)
var announcements = make(chan string)
var validators = make(map[string]int)
首先是定义了一个区块构造体 Block,而后定义一条区块链 Blockchain,其实就是区块数组。这个 tempBlocks 是区块缓冲区。candidateBlocks 是候选区块,任何一个节点提议一个新块时,都会将它发送到这个管道。announcements 是来播送的通道。validators 是验证者列表,存节点地址和他领有的 tokens。
三、生成区块和计算哈希
func generateBlock(oldBlock Block, BPM int, address string) Block {
var newBlock Block
newBlock.Index = oldBlock.Index + 1
newBlock.TimeStamp = time.Now().String()
newBlock.BPM = BPM
newBlock.PrevHash = oldBlock.HashCode
newBlock.Validator = address
newBlock.HashCode = GenerateHashValue(newBlock)
return newBlock
}
func GenerateHashValue(block Block) string {
var hashcode = block.PrevHash +
block.TimeStamp + block.Validator +
strconv.Itoa(block.BPM) + strconv.Itoa(block.Index)
return calculateHash(hashcode)
}
func calculateHash(s string) string {var sha = sha256.New()
sha.Write([]byte(s))
hashed := sha.Sum(nil)
return hex.EncodeToString(hashed)
}
这个真的后面每个例子都在讲,这里真的不想再讲了,不了解的小伙伴能够看一看本专栏后面的例子。
四、主逻辑
func main() {err := godotenv.Load()
if err != nil {log.Fatal(err)
}
genesisBlock := Block{}
genesisBlock = Block{0, time.Now().String(), 0,
GenerateHashValue(genesisBlock), "",""}
spew.Dump(genesisBlock)
Blockchain = append(Blockchain, genesisBlock)
port := os.Getenv("PORT")
server, err := net.Listen("tcp", ":"+port)
if err != nil {log.Fatal(err)
}
log.Println("HTTP Server Listening on port :", port)
defer server.Close()
go func() {
for cadidate := range candidateBlocks {mutex.Lock()
tempBlocks = append(tempBlocks, cadidate)
mutex.Unlock()}
}()
go func() {
for {pickWinner()
}
}()
for {conn, err := server.Accept()
if err != nil {log.Fatal(err)
}
go handleConn(conn)
}
}
咱们先来看一看主逻辑,先是加载本地的.env 文件,这个文件能够存储很多参数,这里咱们存储一个端口号 9000.
而后是创立创世区块,创世区块留神它的高度为 0.
spew.Dump(genesisBlock)
就是把创世区块通过命令行格式化输入。
Blockchain = append(Blockchain, genesisBlock)
这行代码是将创世区块增加到区块链。
port := os.Getenv("PORT")
后面说.env 文件中存储了端口号,这里就获取这个文件中的端口号到 port 变量中。
而后启动服务过程监听下面获取的端口。
defer server.Close()
要养成启动服务就书写提早敞开的习惯,不然前面任意遗记开释资源。
而后是并发操作,循环读取 candidateBlocks,一旦这个管道有一个区块进入,马上把它读取到缓冲区。接着并发判断哪个节点应该去挖矿。
而后一直接管验证者节点的连贯,连上就解决终端发送过去的信息。
五、获取记账权的节点
func pickWinner() {time.Sleep(30 * time.Second)
mutex.Lock()
temp := tempBlocks
mutex.Unlock()
lotteryPool := []string{}
if len(temp) > 0 {
OUTER:
for _, block := range temp {
for _, node := range lotteryPool {
if block.Validator == node {continue OUTER}
}
mutex.Lock()
setValidators := validators
mutex.Unlock()
k, ok := setValidators[block.Validator]
if ok {
for i := 0; i < k; i++ {lotteryPool = append(lotteryPool, block.Validator)
}
}
}
s := rand.NewSource(time.Now().Unix())
r := rand.New(s)
lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))]
for _, block := range temp {
if block.Validator == lotteryWinner {mutex.Lock()
Blockchain = append(Blockchain, block)
mutex.Unlock()
for _ = range validators {announcements <- "\nvalidator:" + lotteryWinner + "\n"}
break
}
}
}
mutex.Lock()
tempBlocks = []Block{}
mutex.Unlock()}
这里就是 PoS 的精华,依据代币 tokens 数量来确定领有记账权的节点。
先是每次选出领有记账权的节点就得劳动 30 秒,不能始终不停的选吧。
每次选领有记账权的节点之前,将缓冲区的区块拷贝一份局部,而后操作正本。
咱们先申明一个彩票池来搁置验证者地址。
而后判断缓冲区是否为空,如果缓冲区正本不为空,就遍历缓冲区正本,而后如果区块的验证者在彩票池就持续遍历,如果不在就执行前面的内容。
而后是获取一个验证者列表正本,获取下面不在彩票池中的验证者节点的 token 代币数量,而后向彩票池中增加和代币数量一样多的验证者地址字符串放入彩票池。
彩票池填充结束后,就开始选幸运儿了。通过随机数来选取,而后将获胜者的区块加到区块链下面,再播送这个获胜者的区块音讯。
如果长期缓冲区为空,咱们就将让他等于一个空区块。
六、解决命令行的申请
func handleConn(conn net.Conn) {defer conn.Close()
go func() {
for {
msg := <-announcements
io.WriteString(conn, msg)
}
}()
var address string
io.WriteString(conn, "Enter token balance:")
scanBalance := bufio.NewScanner(conn)
for scanBalance.Scan() {balance, err := strconv.Atoi(scanBalance.Text())
if err != nil {log.Printf("%v not a number: %v", scanBalance.Text(), err)
return
}
address = calculateHash(time.Now().String())
validators[address] = balance
fmt.Println(validators)
break
}
io.WriteString(conn, "\nEnter a new BPM:")
scanBPM := bufio.NewScanner(conn)
go func() {
for {for scanBPM.Scan() {bmp, err := strconv.Atoi(scanBPM.Text())
if err != nil {log.Printf("%v not a number: %v", scanBPM.Text(), err)
delete(validators, address)
conn.Close()}
mutex.Lock()
oldLastIndex := Blockchain[len(Blockchain)-1]
mutex.Unlock()
newBlock := generateBlock(oldLastIndex, bmp, address)
if err != nil {log.Println(err)
continue
}
if isBlockValid(newBlock, oldLastIndex) {candidateBlocks <- newBlock}
}
}
}()
for {time.Sleep(time.Second * 20)
mutex.Lock()
output, err := json.Marshal(Blockchain)
mutex.Unlock()
if err != nil {log.Fatal(err)
}
io.WriteString(conn, string(output)+"\n")
}
}
func isBlockValid(newBlock, oldBlock Block) bool {
if oldBlock.Index+1 != newBlock.Index {return false}
if oldBlock.HashCode != newBlock.PrevHash {return false}
if GenerateHashValue(newBlock) != newBlock.HashCode {return false}
return true
}
先是延时开释连贯资源。
defer conn.Close()
而后从管道中读取选出幸运儿的音讯,并将其输入到连贯 conn。
而后在命令行窗口接管该节点的 tokens 数量。
而后依据以后工夫生成验证者的地址。
address = calculateHash(time.Now().String())
再将验证者地址和他领有的 tokens 存到 validators 中。
而后再依据提醒输出交易信息。如果输出的交易信息非法,就将该节点删除。
delete(validators, address)
conn.Close()
之后的逻辑是取上一个区块,而后生成新的区块信息,而后简略的验证区块是否非法,非法的话就将区块放入 candidateBlocks 管道,期待抽取幸运儿。
此处验证区块是否非法的办法很简略,就是验证以后区块的高度是不是上一个模块加一,而后判断新区块的 PrevHash 是不是等于上一个区块的哈希值。而后再一次测验哈希值是否正确。
七、运行后果
想向技术大佬们多多取经?开发中遇到的问题何处探讨?如何获取金融科技海量资源?
恒生 LIGHT 云社区,由恒生电子搭建的金融科技业余社区平台,分享实用技术干货、资源数据、金融科技行业趋势,拥抱所有金融开发者。
扫描下方小程序二维码,退出咱们!