在上一篇讲到如何快速实现一个简单的区块链,相信朋友们已经对其有了一个基础的认识。接下来,我们在此基础上进行重构,加入当下热议的一项技术,就是权益证明(Proof of Stake)的代码实现。在实现之前,我们先对它做一个基本的介绍。
- 共识机制(Consensus mechanism)
- 工作证明(PoW)与权益证明(PoS)
- PoS实现
- 测试运行
1、共识机制
在实现之前,先了解一下区块链中的一个概念,“共识机制”。
所谓“共识机制”,是通过特殊节点的投票,在很短的时间内完成对交易的验证和确认;对一笔交易,如果利益不相干的若干个节点能够达成共识,我们就可以认为全网对此也能够达成共识。区块链作为一种按时间顺序存储数据的数据结构,可支持不同的共识机制。共识机制是区块链技术的重要组件。区块链共识机制的目标是使所有的诚实节点保存一致的区块链视图,同时满足两个性质:
1)一致性。所有诚实节点保存的区块链的前缀部分完全相同。
2)有效性。由某诚实节点发布的信息终将被其他所有诚实节点记录在自己的区块链中。
现今区块链的共识机制可分为四大类:工作量证明机制、权益证明机制、股份授权证明机制和Pool验证池。
2、工作量证明和权益证明
工作量证明(PoW)
简单来说,现今的区块链实现的工作量证明,大多数采取的一种方式,就是节点通过计算随机的哈希散列数值,根据一定的规则数字,哪个节点优先计算得出,就可以争夺记账权。求得正确的数值,生成区块的能力是节点算力的具体表现,也就是工作量的证明。
但经过长期的运行,和越来越多的人加入到区块链大军之后。这种机制同样的,也带来了一定的负面影响。比如吸引了来自全球计算机大部分的算力,工作量证明机制的挖矿行为还造成了大量的资源浪费,达成共识所需要的周期也较长。
权益证明
Proof of Stake,权益证明机制的运作方式是,当创造一个新区块时,矿工需要创建一个“币权”交易,交易会按照预先设定的比例把一些币发送给矿工本身。权益证明机制根据每个节点拥有代币的比例和时间,依据算法等比例地降低节点的挖矿难度,从而加快了寻找随机数的速度。
相对于工作证明,现在许多较大的公司使用了大量的硬件设备,建造矿场来争夺账本。这种方式以至于普通人多年来都无法在自己的笔记本电脑上采矿。 因此,许多人认为,证明权益实际上更民主化,因为任何人都可以至少参与自己的笔记本电脑,而不需要建立一个巨大的采矿设备。 他们不需要昂贵的硬件,只需足够的token就可以从中获利。
许多处于技术前沿的区块链项目,也正在向PoS这样的技术进行更加深入的探讨和研究,如NXT、NEO、ETH等等。ETH以太坊中的Casper项目也已经在测试环境中进行投入研究。
那我们就来看看,如何用Go对PoS进行实现。
3、PoS实现
在上一篇中,我们对区块链的实现进行了简单的构建。如果还没有阅读的朋友,建议可以先去阅读一下,在进行接下来的代码编码和理解。不想看的朋友或者理解能力强的朋友也可以继续往下阅读,我会尽量在代码当中注释其中的含义。不足之处还请理解。
大致步骤如下:
- 定义区块
- 哈希计算
- 块哈希计算
- 校验块
- 广播区块
- 选举winner
- TCP Server
为方便起见,这里统一写在一个文件(创建pos.go文件)里面,后期扩展,可以按照开发需求进行分类。
(1)定义区块
与上一篇多增加了一个Validator属性,用于权益证明的验证。
定义需要使用的变量
(2)哈希计算方法定义
(3)计算块哈希
(4)创建块(Block)
(5)校验块是否符合
(6)广播区块节点
我们采用的是用Go去实现的一个TCP Server,来对其它的节点进行联系(在这里使用到块中所定义的验证器)。
根据每个节点放置的token的数量,其中一个节点将被随机(按照token的数量加权)选作胜利者(winner),并将其块添加到区块链中。
(由于之前的代码与上一篇的代码基本类似,本主为了方便使用了截图(就是比较懒,orz),接下来的部分属于重构,将代码附上。)
//处理逻辑 func handleConn(conn net.Conn) { defer conn.Close() //在方法结束前关闭连接 go func() { for { //These announcements will be who the winning validator is when one is chosen //被选中的winner进行传入到 announcements中 msg := <-announcements io.WriteString(conn, msg) } }() // validator address var address string // allow user to allocate number of tokens to stake // the greater the number of tokens, the greater chance to forging a new block // 允许验证者输入他想要加入的tokens的数量 // 拥有足够多的tokens,就更有机会获得新的块 io.WriteString(conn, "Enter token balance:") //使用natcat进行输入值 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 } t := time.Now() address = calculateHash(t.String()) validators[address] = balance fmt.Println(validators) break } io.WriteString(conn, "\nEnter a new BPM:") scanBPM := bufio.NewScanner(conn) go func() { for { //进行输入的BPM验证 for scanBPM.Scan() { bpm, err := strconv.Atoi(scanBPM.Text()) //如果恶意方试图用错误的输入来改变链,则将此map删除 //在这使用了一个简单的逻辑,就是判断输入的BMP是否为一个整数格式 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() //创建新块block,并考虑起是否伪造 newBlock, err := generateBlock(oldLastIndex, bpm, address) if err != nil { log.Println(err) continue //输出所有log err } if isBlockValid(newBlock, oldLastIndex) { candidateBlocks <- newBlock } io.WriteString(conn, "\nEnter a new BPM:") } } }() //模拟接收广播 for { time.Sleep(time.Minute) mutex.Lock() //用一个规整的json格式输出区块 output, err := json.MarshalIndent(Blockchain, "", "\t") mutex.Unlock() if err != nil { log.Fatal(err) } io.WriteString(conn, string(output)+"\n") } }
如果验证器试图提出一个伪造块(恶意输入),在我们的例子中是一个不是整数的BPM,它会抛出一个错误,我们立即从验证器列表中删除验证器。 他们将不再有资格建立新的块,并且同时受到一定的惩罚,扣除一些balance(如以太坊、比特币中的余额)。
(7)选举Winner
在这里使用了time间隔30秒来明显区分(选一位获胜者),为每位验证者提供时间来提出新的区块。 然后,我们需要创建一个lotteryPool,其中包含验证者的地址,这些验证者可以被选为winner。 然后我们检查一下,在我们的逻辑处理之前,如果len(temp)> 0,在temp中存在这些区块。//选择winner,通过随机选择块来选择验证者来伪造一个区块链,并通过标记的数量加权 func pickWinner() { time.Sleep(time.Second * 30) mutex.Lock() temp := tempBlocks mutex.Unlock() lotteryPool := []string{} if len(temp) > 0 { OUTER: //使用这个循环来判断是否已经存在相同的验证在temp当中 for _, block := range temp { //索引值不用,设"_" // if already in lottery pool, skip 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 := 1; i < k; i++ { lotteryPool = append(lotteryPool, block.Validator) } } } //从池(lotteryPool)中随机选取winner s := rand.NewSource(time.Now().Unix()) r := rand.New(s) lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))] //添加winner中的块block 并让所有节点知道 for _, block := range temp { if block.Validator == lotteryWinner { mutex.Lock() Blockchain = append(Blockchain, block) mutex.Unlock() for range validators { announcements <- "\n winning validator: " + lotteryWinner + "\n" } break } } mutex.Lock() tempBlocks = []Block{} mutex.Unlock() } }
(8)构建Server
创建主方法,构建TCP server:
func main() { //在同目录下创建prop.env文件("PORT=8088") err := godotenv.Load("prop.env") if err != nil { log.Fatal(err) } //构建创世块genesisBlock t := time.Now() genesisBlock := Block{} genesisBlock = Block{0, t.String(), 0, calculateBlockHash(genesisBlock), "", ""} spew.Dump(genesisBlock) Blockchain = append(Blockchain, genesisBlock) //读取.env文件,获取Server端口8088 httpPort := os.Getenv("PORT") //监听server server, err := net.Listen("tcp", ":"+httpPort) if err != nil { log.Fatal(err) } log.Println("HTTP Server Listening on port : ", httpPort) defer server.Close() go func() { for candidate := range candidateBlocks { mutex.Lock() tempBlocks = append(tempBlocks, candidate) mutex.Unlock() } }() go func() { for { pickWinner() //选举winner } }() for { conn, err := server.Accept() //开启服务 if err != nil { log.Fatal(err) } go handleConn(conn) //处理连接 } }
4、测试运行
使用go run命令运行:
打开两个终端,使用natcat命令:
$> nc localhost 8088
分别输入balance,BPM
首先得出winner:
后台得到 validators:
其次,等待......(30s),得到广播结果:
测试成功。
-------------------------------------------
后续就可以参与更加复杂的区块链程序编写了,如工作证明PoW,智能合约,Dapps,侧链,P2P,网络广播机制,IPFS存储等。
有任何建议或问题,欢迎加微信一起学习交流,欢迎从事IT,热爱IT,喜欢深挖源代码的行业大牛加入,一起探讨。
个人微信号:bboyHan