Python实现基于POS算法的区块链


Posted in Python onAugust 07, 2018

区块链中的共识算法

在比特币公链架构解析中,就曾提到过为了实现去中介化的设计,比特币设计了一套共识协议,并通过此协议来保证系统的稳定性和防攻击性。 并且我们知道,截止目前使用最广泛,也是最被大家接受的共识算法,是我们先前介绍过的POW(proof of work)工作量证明算法。目前市值排名前二的比特币和以太坊也是采用的此算法。

虽然POW共识算法取得了巨大的成功,但对它的质疑也从来未曾停止过。 其中最主要的一个原因就是电力消耗。据不完全统计,基于POW的挖矿机制所消耗的电量是非常巨大的,甚至比绝大多数国家耗电量还要多。这对我们的资源造成了极大的浪费,此外随着比特大陆等公司的强势崛起,造成了算力的高度集中。

基于以上种种原因,更多的共识算法被提出来 POS、DPOS、BPFT等等。 今天我们就来认识POS(proof of stake)算法。

Proof of stake,译为权益证明。你可能已经猜到了,权益证明简单理解就是拥有更多token的人,有更大的概率获得记账权利,然后获得奖励。 这个概率具体有多大呢? 下面我们在代码实现中会展示,分析也放在后面。 当然,POS是会比POW更好吗? 会更去中心化吗? 现在看来未必,所以我们这里也不去对比谁优谁劣。 我们站在中立的角度,单纯的来讨论讨论POS这种算法。

代码实战

生成一个Block

既然要实现POS算法,那么就难免要生成一条链,链又是由一个个Block生成的,所以下面我们首先来看看如何生成Block,当然在前面的内容里面,关于如何生成Block,以及交易、UTXO等等都已经介绍过了。由于今天我们的核心是实现POS,所以关于Block的生成,我们就用最简单的实现方式,好让大家把目光聚焦在核心的内容上面。

我们用三个方法来实现生成一个合法的区块

  • calculate_hash 计算区块的hash值
  • is_block_valid 校验区块是否合法
  • generate_block 生成一个区块
from hashlib import sha256
from datetime import datetime
def generate_block(oldblock, bpm, address):
 """
 :param oldblock:
 :param bpm:
 :param address:
 :return:
 """
 newblock = {
  "Index": oldblock["Index"] + 1,
  "BPM": bpm,
  "Timestamp": str(datetime.now()),
  "PrevHash": oldblock["Hash"],
  "Validator": address
 }
 newblock["Hash"] = calculate_hash(newblock)
 return newblock
def calculate_hash(block):
 record = "".join([
  str(block["Index"]),
  str(block["BPM"]),
  block["Timestamp"],
  block["PrevHash"]
 ])
 return sha256(record.encode()).hexdigest()
def is_block_valid(newblock, oldblock):
 """
 :param newblock:
 :param oldblock:
 :return:
 """
 if oldblock["Index"] + 1 != newblock["Index"]:
  return False
 if oldblock["Hash"] != newblock["PrevHash"]:
  return False
 if calculate_hash(newblock) != newblock["Hash"]:
  return False
 return True

这里为了更灵活,我们没有用类的实现方式,直接采用函数来实现了Block生成,相信很容易看懂。

创建一个TCP服务器

由于我们需要用权益证明算法来选择记账人,所以需要从很多Node(节点)中选择记账人,也就是需要一个server让节点链接上来,同时要同步信息给节点。因此需要一个TCP长链接。

from socketserver import BaseRequestHandler, ThreadingTCPServer
def run():
 # start a tcp server
 serv = ThreadingTCPServer(('', 9090), HandleConn)
 serv.serve_forever()

在这里我们用了python内库socketserver来创建了一个TCPServer。 需要注意的是,这里我们是采用的多线程的创建方式,这样可以保证有多个客户端同时连接上来,而不至于被阻塞。当然,这里这个server也是存在问题的,那就是有多少个客户端连接,就会创建多少个线程,更好的方式是创建一个线程池。由于这里是测试,所以就采用更简单的方式了。

相信大家已经看到了,在我们创建TCPServer的时候,使用到了HandleConn,但是我们还没有定义,所以接下来我们就来定义一个HandleConn

消息处理器

下面我们来实现Handler函数,Handler函数在跟Client Node通信的时候,需要我们的Node实现下面的功能

  • Node可以输入balance(token数量) 也就是股权数目
  • Node需要能够接收广播,方便Server同步区块以及记账人信息
  • 添加自己到候选人名单 (候选人为持有token的人)
  • 输入BPM生成Block
  • 验证一个区块的合法性

感觉任务还是蛮多的,接下来我们看代码实现

import threading
from queue import Queue, Empty
# 定义变量
block_chain = []
temp_blocks = []
candidate_blocks = Queue() # 创建队列,用于线程间通信
announcements = Queue()
validators = {}
My_Lock = threading.Lock()
class HandleConn(BaseRequestHandler):
 def handle(self):
  print("Got connection from", self.client_address)
  # validator address
  self.request.send(b"Enter token balance:")
  balance = self.request.recv(8192)
  try:
   balance = int(balance)
  except Exception as e:
   print(e)
  t = str(datetime.now())
  address = sha256(t.encode()).hexdigest()
  validators[address] = balance
  print(validators)
  while True:
   announce_winner_t = threading.Thread(target=annouce_winner, args=(announcements, self.request,),
             daemon=True)
   announce_winner_t.start()
   self.request.send(b"\nEnter a new BPM:")
   bpm = self.request.recv(8192)
   try:
    bpm = int(bpm)
   except Exception as e:
    print(e)
    del validators[address]
    break
   # with My_Lock:
   last_block = block_chain[-1]
   new_block = generate_block(last_block, bpm, address)
   if is_block_valid(new_block, last_block):
    print("new block is valid!")
    candidate_blocks.put(new_block)
   self.request.send(b"\nEnter a new BPM:\n")
   annouce_blockchain_t = threading.Thread(target=annouce_blockchain, args=(self.request,), daemon=True)
   annouce_blockchain_t.start()

这段代码,可能对大多数同学来说是有难度的,在这里我们采用了多线程的方式,同时为了能够让消息在线程间通信,我们使用了队列。 这里使用队列,也是为了我们的系统可以更好的拓展,后面如果可能,这一节的程序很容易拓展为分布式系统。 将多线程里面处理的任务拆分出去成独立的服务,然后用消息队列进行通信,就是一个简单的分布式系统啦。(是不是很激动?)

由于这里有难度,所以代码还是讲一讲吧

# validator address
  self.request.send(b"Enter token balance:")
  balance = self.request.recv(8192)
  try:
   balance = int(balance)
  except Exception as e:
   print(e)
  t = str(datetime.now())
  address = sha256(t.encode()).hexdigest()
  validators[address] = balance
  print(validators)

这一段就是我们提到的Node 客户端添加自己到候选人的代码,每链接一个客户端,就会添加一个候选人。 这里我们用添加的时间戳的hash来记录候选人。 当然也可以用其他的方式,比如我们代码里面的client_address

announce_winner_t = threading.Thread(target=annouce_winner, args=(announcements, self.request,),
            daemon=True)
  announce_winner_t.start()
def annouce_winner(announcements, request):
 """
 :param announcements:
 :param request:
 :return:
 """
 while True:
  try:
   msg = announcements.get(block=False)
   request.send(msg.encode())
   request.send(b'\n')
  except Empty:
   time.sleep(3)
   continue

然后接下来我们起了一个线程去广播获得记账权的节点信息到所有节点。

self.request.send(b"\nEnter a new BPM:")
   bpm = self.request.recv(8192)
   try:
    bpm = int(bpm)
   except Exception as e:
    print(e)
    del validators[address]
    break
   # with My_Lock:
   last_block = block_chain[-1]
   new_block = generate_block(last_block, bpm, address)
   if is_block_valid(new_block, last_block):
    print("new block is valid!")
    candidate_blocks.put(new_block)

根据节点输入的BPM值生成一个区块,并校验区块的有效性。 将有效的区块放到候选区块当中,等待记账人将区块添加到链上。

annouce_blockchain_t = threading.Thread(target=annouce_blockchain, args=(self.request,), daemon=True)
  annouce_blockchain_t.start()
def annouce_blockchain(request):
 """
 :param request:
 :return:
 """
 while True:
  time.sleep(30)
  with My_Lock:
   output = json.dumps(block_chain)
  try:
   request.send(output.encode())
   request.send(b'\n')
  except OSError:
   pass

最后起一个线程,同步区块链到所有节点。

看完了,节点跟Server交互的部分,接下来是最重要的部分,

POS算法实现

def pick_winner(announcements):
  """
  选择记账人
  :param announcements:
  :return:
  """
  time.sleep(10)
  while True:
    with My_Lock:
      temp = temp_blocks
    lottery_pool = [] #
    if temp:
      for block in temp:
        if block["Validator"] not in lottery_pool:
          set_validators = validators
          k = set_validators.get(block["Validator"])
          if k:
            for i in range(k):
              lottery_pool.append(block["Validator"])
      lottery_winner = choice(lottery_pool)
      print(lottery_winner)
      # add block of winner to blockchain and let all the other nodes known
      for block in temp:
        if block["Validator"] == lottery_winner:
          with My_Lock:
            block_chain.append(block)
          # write message in queue.
          msg = "\n{0} 赢得了记账权利\n".format(lottery_winner)
          announcements.put(msg)
          break
    with My_Lock:
      temp_blocks.clear()

这里我们用pick_winner 来选择记账权利,我们根据token数量构造了一个列表。 一个人获得记账权利的概率为:

p = mount['NodeA']/mount['All']

文字描述就是其token数目在总数中的占比。 比如总数有100个,他有10个,那么其获得记账权的概率就是0.1, 到这里核心的部分就写的差不多了,接下来,我们来添加节点,开始测试吧

测试POS的记账方式

在测试之前,起始还有一部分工作要做,前面我们的run方法需要完善下,代码如下:

def run():
  # create a genesis block
  t = str(datetime.now())
  genesis_block = {
    "Index": 0,
    "Timestamp": t,
    "BPM": 0,
    "PrevHash": "",
    "Validator": ""
  }
  genesis_block["Hash"] = calculate_hash(genesis_block)
  print(genesis_block)
  block_chain.append(genesis_block)
  thread_canditate = threading.Thread(target=candidate, args=(candidate_blocks,), daemon=True)
  thread_pick = threading.Thread(target=pick_winner, args=(announcements,), daemon=True)
  thread_canditate.start()
  thread_pick.start()
  # start a tcp server
  serv = ThreadingTCPServer(('', 9090), HandleConn)
  serv.serve_forever()
def candidate(candidate_blocks):
  """
  :param candidate_blocks:
  :return:
  """
  while True:
    try:
      candi = candidate_blocks.get(block=False)
    except Empty:
      time.sleep(5)
      continue
    temp_blocks.append(candi)
if __name__ == '__main__':
  run()

添加节点连接到TCPServer

为了充分减少程序的复杂性,tcp client我们这里就不实现了,可以放在后面拓展部分。 毕竟我们这个系统是很容易扩展的,后面我们拆分了多线程的部分,在实现tcp client就是一个完整的分布式系统了。

所以,我们这里用linux自带的命令 nc,不知道nc怎么用的同学可以google或者 man nc

Python实现基于POS算法的区块链

启动服务 运行 python pos.py

打开3个终端

分别输入下面命令

nc localhost 9090

终端如果输出

Enter token balance:

说明你client已经链接服务器ok啦.

测试POS的记账方式

接下来依次按照提示操作。 balance可以按心情来操作,因为这里是测试,我们输入100,
紧接着会提示输入BPM,我们前面提到过,输入BPM是为了生成Block,那么就输入吧,随便输入个9. ok, 接下来就稍等片刻,等待记账。

输出如同所示

Python实现基于POS算法的区块链

依次在不同的终端,根据提示输入数字,等待消息同步。

生成区块链

下面是我这边获得的3个block信息。

Python实现基于POS算法的区块链

总结

在上面的代码中,我们实现了一个完整的基于POS算法记账的链,当然这里有许多值得扩展与改进的地方。

  • python中多线程开销比较大,可以改成协程的方式
  • TCP建立的长链接是基于TCPServer,是中心化的方式,可以改成P2P对等网络
  • 链的信息不够完整
  • 系统可以拓展成分布式,让其更健壮

大概列了以上几点,其他还有很多可以拓展的地方,感兴趣的朋友可以先玩玩, 后者等到我们后面的教程。 (广告打的措手不及,哈哈)

当然了,语言不是重点,所以在这里,我也实现了go语言的版本源码地址

go语言的实现感觉要更好理解一点,也显得要优雅一点。这也是为什么go语言在分布式领域要更抢手的原因之一吧!

项目地址

https://github.com/csunny/py-bitcoin/

参考

https://medium.com/@mycoralhealth/code-your-own-proof-of-stake-blockchain-in-go-610cd99aa658

总结

以上所述是小编给大家介绍的Python实现基于POS算法的区块链,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
Python获取网页上图片下载地址的方法
Mar 11 Python
python自然语言编码转换模块codecs介绍
Apr 08 Python
Python中super()函数简介及用法分享
Jul 11 Python
详解Django中间件执行顺序
Jul 16 Python
python 美化输出信息的实例
Oct 15 Python
使用CodeMirror实现Python3在线编辑器的示例代码
Jan 14 Python
Django缓存系统实现过程解析
Aug 02 Python
python3.6生成器yield用法实例分析
Aug 23 Python
Python CSS选择器爬取京东网商品信息过程解析
Jun 01 Python
利用keras使用神经网络预测销量操作
Jul 07 Python
使用qt quick-ListView仿微信好友列表和聊天列表的示例代码
Jun 13 Python
如何用六步教会你使用python爬虫爬取数据
Apr 06 Python
tensorflow学习教程之文本分类详析
Aug 07 #Python
Django添加feeds功能的示例
Aug 07 #Python
Python爬虫实现抓取京东店铺信息及下载图片功能示例
Aug 07 #Python
Django添加favicon.ico图标的示例代码
Aug 07 #Python
Python实现的json文件读取及中文乱码显示问题解决方法
Aug 06 #Python
Python装饰器模式定义与用法分析
Aug 06 #Python
Python实现的建造者模式示例
Aug 06 #Python
You might like
实战mysql导出中文乱码及phpmyadmin导入中文乱码的解决方法
2010/06/11 PHP
php实现将base64格式图片保存在指定目录的方法
2016/10/13 PHP
laravel框架关于搜索功能的实现
2018/03/15 PHP
(function($){...})(jQuery)的意思
2010/07/22 Javascript
JS中动态添加事件(绑定事件)的代码
2011/01/09 Javascript
javascript数字格式化通用类 accounting.js使用
2012/08/24 Javascript
基于jQuery实现图片的前进与后退功能
2013/04/24 Javascript
jQuery中fadeIn、fadeOut、fadeTo的使用方法(图片显示与隐藏)
2013/05/08 Javascript
jquery处理json对象
2014/11/03 Javascript
jQuery中[attribute]选择器用法实例
2014/12/31 Javascript
浅析JavaScript访问对象属性和方法及区别
2015/11/16 Javascript
Backbone中View之间传值的学习心得
2016/08/09 Javascript
vue.js开发环境安装教程
2017/03/17 Javascript
layui表格实现代码
2017/05/20 Javascript
详解如何解决Vue和vue-template-compiler版本之间的问题
2018/09/17 Javascript
Vue.js 时间转换代码及时间戳转时间字符串
2018/10/16 Javascript
如何自动化部署项目?折腾服务器之旅~
2019/04/16 Javascript
js神秘的电报密码 哈弗曼编码实现
2019/09/10 Javascript
Vue.js标签页组件使用方法详解
2019/10/19 Javascript
python list 合并连接字符串的方法
2013/03/09 Python
Python正则表达式匹配和提取IP地址
2019/06/06 Python
python元组和字典的内建函数实例详解
2019/10/22 Python
PyCharm设置注释字体颜色以及是否倾斜的操作
2020/09/16 Python
Python django框架 web端视频加密的实例详解
2020/11/20 Python
使用Filters滤镜弥补CSS3的跨浏览器问题以及兼容低版本IE
2013/01/23 HTML / CSS
Html5踩坑记之mandMobile使用小记
2020/04/02 HTML / CSS
法国太阳镜店:Sunglasses Shop
2016/08/27 全球购物
Laravel的加密解密与哈希实例讲解
2021/03/24 PHP
大学同学聚会邀请函
2014/01/29 职场文书
2015年教研工作总结
2015/05/23 职场文书
中小学教师继续教育心得体会
2016/01/19 职场文书
毕业生的自我鉴定表范文
2019/05/16 职场文书
家长必看:义务教育,不得以面试 评测等名义选拔学生
2019/07/09 职场文书
实用干货:敬酒词大全,帮你应付各种场合
2019/11/21 职场文书
Nginx配置文件详解以及优化建议指南
2021/09/15 Servers
浅谈MySQL中的六种日志
2022/03/23 MySQL