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常用算法学习基础教程
Apr 13 Python
用Python将mysql数据导出成json的方法
Aug 21 Python
Python传递参数的多种方式(小结)
Sep 18 Python
学python安装的软件总结
Oct 12 Python
Python中__repr__和__str__区别详解
Nov 07 Python
Pandas实现dataframe和np.array的相互转换
Nov 30 Python
Python imutils 填充图片周边为黑色的实现
Jan 19 Python
Tensorflow训练模型越来越慢的2种解决方案
Feb 07 Python
python GUI框架pyqt5 对图片进行流式布局的方法(瀑布流flowlayout)
Mar 12 Python
学会python自动收发邮件 代替你问候女友
May 20 Python
python连接mysql有哪些方法
Jun 24 Python
Python django中如何使用restful框架
Jun 23 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
SMARTY学习手记
2007/01/04 PHP
php setcookie函数的参数说明及其用法
2014/04/20 PHP
destoon公司主页模板风格的添加方法
2014/06/20 PHP
php强大的时间转换函数strtotime
2016/02/18 PHP
PHP钩子与简单分发方式实例分析
2017/09/04 PHP
thinkphp5.0整合phpsocketio完整攻略(绕坑)
2018/10/12 PHP
PHP echo()函数讲解
2019/02/15 PHP
PHP程序守护进程化实现方法详解
2020/07/16 PHP
身份证号码前六位所代表的省,市,区, 以及地区编码下载
2007/04/12 Javascript
让js弹出窗口居前显示的实现方法
2013/07/10 Javascript
Jquery 实现弹出层插件
2015/01/28 Javascript
jQuery使用load()方法载入另外一个网页文件内的指定标签内容到div标签的方法
2015/03/25 Javascript
异步JS框架的作用以及实现方法
2015/10/29 Javascript
浅谈jQuery中的checkbox问题
2016/08/10 Javascript
js获取页面引用的css样式表中的属性值方法(推荐)
2016/08/19 Javascript
学习 NodeJS 第八天:Socket 通讯实例
2016/12/21 NodeJs
Vue.js手风琴菜单组件开发实例
2017/05/16 Javascript
深入理解React Native原生模块与JS模块通信的几种方式
2017/07/24 Javascript
Vue数据监听方法watch的使用
2018/03/28 Javascript
JavaScript递归函数解“汉诺塔”算法代码解析
2018/07/05 Javascript
微信小程序用户拒绝授权的处理方法详解
2019/09/20 Javascript
redux处理异步action解决方案
2020/03/22 Javascript
Python实现的二维码生成小软件
2014/07/11 Python
用python写扫雷游戏实例代码分享
2018/05/27 Python
python redis连接 有序集合去重的代码
2019/08/04 Python
python路径的写法及目录的获取方式
2019/12/26 Python
Python ADF 单位根检验 如何查看结果的实现
2020/06/03 Python
JAVA SWT事件四种写法实例解析
2020/06/05 Python
基于Python编写一个计算器程序,实现简单的加减乘除和取余二元运算
2020/08/05 Python
Django自带的用户验证系统实现
2020/12/18 Python
HTML5 form标签之解放表单验证、增加文件上传、集成拖放的使用方法
2013/04/24 HTML / CSS
大专生毕业的自我评价
2014/02/06 职场文书
股东合作协议书范本
2014/04/14 职场文书
酒店辞职书范文
2015/02/26 职场文书
初中体育教学随笔
2015/08/15 职场文书
在pycharm中无法import所安装的库解决方案
2021/05/31 Python