精华内容
下载资源
问答
  • 打造区块链项目寻求使用Python构建基本区块链的代码。 它大部分灵感都来自在线文档。
  • 比特币的python实现,主要介绍比特币底层技术。如Base58编码、椭圆加密算法、MerkleTree、P2P对等网络、RPC通信、UTXO、虚拟机、DHT、DAG、链上数据持久化存储等。
  • python 快速实现区块链

    千次阅读 2018-07-25 04:53:32
    最近区块链越来越火了,前几天跟几个大佬在DoraHacks x BCH Faith Hack 上拿了个冠军,也是对区块链非常感兴趣,看了一段时间以太坊,比特币源码,感觉大致框架是差不多,在代码层面上来说,图灵完备和非图灵完备...

    区块链快速开发

    前言

    • 最近区块链越来越火了,前几天跟几个大佬在DoraHacks x BCH Faith Hack 上拿了个冠军,也是对区块链非常感兴趣,看了一段时间以太坊,比特币源码,感觉大致框架是差不多的,在代码层面上来说,图灵完备非图灵完备感觉就是两者最大的区别之一,下面我按照自己的理解,用python简单的实现了一个区块链,大致700多行代码,可能python真的比较吊吧,所有的加密算法都封装好了,没学过密码学的表示很爽,下面讲解下实现的过程,基本原理就不说了,可以参考这里

    实战

    环境

    • MacOs 10.13.2
    • vim 8.0
    • python 2.7

    项目地址

    • 先把项目clone下来:
    $ git clone https://github.com/MrPaoBrother/blockchain.git
    • 如果没装git的朋友直接下载也是一样的

    安装

    • 运行前先把相关的第三方库安装好:
    $ sudo pip install pycrypto
    $ sudo pip install flask

    运行

    • Blockchain Server:
    • 进入到blockchain_server目录下找到bootstrap.py文件:
    python bootstrap.py
    • Blockchain Client:
    • 进入到blockchain_cli目录下找到bootstrap.py文件:
    python bootstrap.py
    • 运行成功后,可以看到如下几张图:

    server端启动
    Server启动

    client端启动
    client
    client
    client

    核心代码讲解

    代码结构

    • app: 代表下面都是应用,应用间隔离,相关功能都抽象出来放在pyutil中
    • app/blockchain_cli: 客户端,用来测试server端的也就是底层链的,其中block_data存放节点同步过来的数据,domain/dal 是数据维护层,读者可以自行添加,作用是防止客户端直接操作数据库,wallet 是存放用户钱包地址的目录,用户新建钱包时候会将记录存在该文件中。
    • blockchain_server 是区块链服务端,下面的block_data 相当于存储的库,写的简单一些方便运行,客户端同步数据就从这里同步。
    • log 是存放日志的地方,各个app会有自己的psm(每个app文件中的settings.py文件中可以看到)定义,每个app可以通过log查看状态
    • pyutil 是通用类,也就是公共库,一些抽象出来的代码,可以放入这里面

    核心函数

        def proof_of_work(self):
            """
            工作量证明Pow
            :return: 
            """
            last_block = self.chain[-1]
            last_hash = self.hash(last_block)
            nonce = 0
            while self.valid_proof(self.chain[-1].transactions, last_hash, nonce) is False:
                nonce += 1
            return nonce
    
        def valid_chain(self, chain):
            # 1 后一个区块的hash是否等于前一个区块的hash
            # 2 每一个区块的随机数nounce是否正确
            if not chain:
                return False
            last_block = chain[0]
            current_index = 1
            while current_index < len(chain):
                block = chain[current_index]
                if block.pre_hash != self.hash(last_block):
                    return False
                transactions = block.transactions[:-1]
                transaction_elements = ['sender_address', 'recipient_address', 'value']
                transactions = [OrderedDict((k, transaction[k]) for k in transaction_elements) for transaction in
                                transactions]
                if not self.valid_proof(transactions, block['previous_hash'], block['nonce'], settings.hard):
                    return False
                last_block = block
                current_index += 1
            return True
    
    • 下图中Blockchain就是区块链的核心类,包括注册节点,创建区块,POW,解决冲突等等

    这里写图片描述

    总结

    • 该项目只是一个初稿,项目中还有很多不完善的地方,后期会慢慢完善,也是刚入门,确实有一定难度,一直认为高并发和去中心化是很难共存的,现在去中心化是可以做到,但是效率很难提高,而效率提升上去了,去中心化问题又很难保证,所以现在很多公司都打着去中心化的名义其实只是有几个超级节点,巨大的中心,哈哈。
    • 最后,读者有问题欢迎留言。

    参考资料

    [1] 以太坊白皮书

    [2] 比特币白皮书

    [3] 以太坊源码分析

    [4] python 区块链实现

    [5] 比特币维基百科

    展开全文
  • 区块链技术核心原理实现——Tiny熊老师区块链课堂笔记   3-1 python 实现区块链环境准备   1、HTTP 超文本传输协议——Postman/curl  2、Python——PyCharm 3、工具:pip、pipenv、Flask/requests   3...

    区块链技术核心原理实现——Tiny熊老师区块链课堂的笔记

     

    3-1 python 实现区块链环境准备

     

    1、HTTP 超文本传输协议——Postman/curl 

    2、Python——PyCharm

    3、工具:pip、pipenv、Flask/requests

     

    3-2 建立项目,确定区块结构

     

    cmd中:

    C:\Users\Tai Park>D:

     

    D:\>mkdir lesson1

     

    D:\>cd lesson1

     

    D:\lesson1>pip install pipenv

     

    D:\lesson1>where python

    D:\新建文件夹\python.exe

     

    D:\lesson1>pipenv --python=D:\新建文件夹\python.exe

    Creating a virtualenv for this project...

    Pipfile: D:\lesson1\Pipfile

    Using D:\新建文件夹\python.exe (3.7.0) to create virtualenv...

     

    D:\lesson1>pipenv install requests==2.18.4

     

    D:\lesson1>pipenv install flask==0.12.2

     

    3-3 实现区块类结构-添加交易

     

    整理区块链所需要的信息:

    # {

    #     "index":0,  #块的索引

    #     "timestamp":"",     #时间戳

    #     "transactions":[    #交易信息

    #         {

    #             "sender":"",    #交易的发送者

    #             "recipient":"",     #交易的接收者

    #             "amount":5,     #交易的金额

    #         }

    #     ],

    #     "proof":"",     #工作量证明

    #     "previous_hash":"",     #上一个区块的哈希值

    #

    # }

     

    PyCharm:blockchain.py

    大致函数定义:

    class Blockchain:

     

        def __init__(self):

            self.chain = []     #一个元素对应一个块

            self.current_transactions = []      #保存当前的交易信息,一个元素对应一个交易实体

     

        def new_block(self):    #新的区块

            pass

     

        def new_transaction(self,sender,recipient,amount)->int:      #新的交易(发送者,接受者,金额)

            #每个交易信息都是一个json,把它添加到交易信息的最后

            self.current_transactions.append(

                {

                    'sender':sender,

                    'recipient':recipient,

                    'amount':amount

                }

            )

            #返回索引:原索引加一

            return self.last_block['index'] + 1

     

        @staticmethod

        def hash(block):    #静态方法,计算区块的哈希值

            pass

     

        @property

        def last_block(self):   #属性,获取到区块链最后一个区块

            pass

     

    3-4 实现创建区块

     

    注:在win的PyCharm中,alt+Enter 可以实现快速import

    # {

    #     "index":0,  #块的索引

    #     "timestamp":"",     #时间戳

    #     "transactions":[    #交易信息

    #         {

    #             "sender":"",    #交易的发送者

    #             "recipient":"",     #交易的接收者

    #             "amount":5,     #交易的金额

    #         }

    #     ],

    #     "proof":"",     #工作量证明

    #     "previous_hash":"",     #上一个区块的哈希值

    #

    # }

    import hashlib

    import json

    from time import time

     

     

    class Blockchain:

     

        #构造函数

        def __init__(self):

            # 一个元素对应一个块

            self.chain = []

            # 保存当前的交易信息,一个元素对应一个交易实体

            self.current_transactions = []

     

            # 创建创世区块,不用计算,没有内容

            self.new_block(proof = 100,previous_hash = 1)

     

        # 新的区块

        def new_block(self,proof,previous_hash = None):

            #构造json对象

            block = {

                'index':len(self.chain) + 1,

                'timestamp':time(),

                'transcations':self.current_transactions,

                'proof':proof,

                # 可以是传过来的hash值或者是调用hash()计算出来的hash值

                'previous_hash':previous_hash or self.hash(self.last_block)

            }

     

            #把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了

            self.current_transactions = []

     

            #把生成的区块加入到链条中

            self.chain.append(block)

     

            return block

     

        # 新的交易(发送者,接受者,金额)

        def new_transaction(self,sender,recipient,amount)->int:

            #每个交易信息都是一个json,把它添加到交易信息的最后

            self.current_transactions.append(

                {

                    'sender':sender,

                    'recipient':recipient,

                    'amount':amount

                }

            )

            #返回索引:原索引加一

            return self.last_block['index'] + 1

     

        # 静态方法,计算区块的哈希值

        @staticmethod

        def hash(block):

            #block是json对象,先把json转化为string并对字符排序并编码

            block_string = json.dumps(block,sort_keys = True).encode()

            #传入字符串编码后的字节数组,返回hash的摘要信息

            return hashlib.sha256(block_string).hexdigest()

     

        # 属性,获取到区块链最后一个区块

        @property

        def last_block(self):

            #-1表示是数组最后一个元素

            return self.chain(-1)

     

     

    3-5 实现工作量证明

     

    # {

    #     "index":0,  #块的索引

    #     "timestamp":"",     #时间戳

    #     "transactions":[    #交易信息

    #         {

    #             "sender":"",    #交易的发送者

    #             "recipient":"",     #交易的接收者

    #             "amount":5,     #交易的金额

    #         }

    #     ],

    #     "proof":"",     #工作量证明

    #     "previous_hash":"",     #上一个区块的哈希值

    #

    # }

    import hashlib

    import json

    from time import time

     

     

    class Blockchain:

     

        #构造函数

        def __init__(self):

            # 一个元素对应一个块

            self.chain = []

            # 保存当前的交易信息,一个元素对应一个交易实体

            self.current_transactions = []

     

            # 创建创世区块,不用计算,没有内容

            self.new_block(proof = 100,previous_hash = 1)

     

        # 新的区块

        def new_block(self,proof,previous_hash = None):

            #构造json对象

            block = {

                'index':len(self.chain) + 1,

                'timestamp':time(),

                'transcations':self.current_transactions,

                'proof':proof,

                # 可以是传过来的hash值或者是调用hash()计算出来的hash值

                'previous_hash':previous_hash or self.hash(self.last_block)

            }

     

            #把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了

            self.current_transactions = []

     

            #把生成的区块加入到链条中

            self.chain.append(block)

     

            return block

     

        # 新的交易(发送者,接受者,金额)

        def new_transaction(self,sender,recipient,amount)->int:

            #每个交易信息都是一个json,把它添加到交易信息的最后

            self.current_transactions.append(

                {

                    'sender':sender,

                    'recipient':recipient,

                    'amount':amount

                }

            )

            #返回索引:原索引加一

            return self.last_block['index'] + 1

     

        # 静态方法,计算区块的哈希值

        @staticmethod

        def hash(block):

            #block是json对象,先把json转化为string并对字符排序并编码

            block_string = json.dumps(block,sort_keys = True).encode()

            #传入字符串编码后的字节数组,返回hash的摘要信息

            return hashlib.sha256(block_string).hexdigest()

     

        # 属性,获取到区块链最后一个区块

        @property

        def last_block(self):

            #-1表示是数组最后一个元素

            return self.chain[-1]

     

        #工作量证明

        def proof_of_work(self,last_proof:int)->int:

            proof = 0

            #不停尝试proof的值,验证proof是否满足条件

            while self.valid_proof(last_proof,proof) is False:

                proof += 1

            print(proof)

            return proof

     

        #验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件

        def valid_proof(self,last_proof:int,proof:int)->bool:

            #先把两个值转化成一个字符串并编码

            guess = f'{last_proof}{proof}'.encode()

            #用同样的方法拿到hash摘要

            guess_hash = hashlib.sha256(guess).hexdigest()

            print(guess_hash)

            #如果满足前面四位是以0开头则返回True

            return guess_hash[0:4] == '0000'

     

    #验证工作量证明

    testPow = Blockchain()

    testPow.proof_of_work(100)

     

    3-6 添加节点通信功能

     

    Flask官网:http://flask.pocoo.org/

     

    #初始化Flask类

    app = Flask(__name__)

    #启动Flask,提供运行入口

    if __name__ == '__main__':

        app.run(host = '0.0.0.0',port = 5000)

    服务器已经运行了但是没有任何接受URL的接口

     

    添加测试路由:

    #测试路由

    @app.route('/index',methods = ['GET'])

    def index():

        return 'Hello BlockChain'

     

    打开Postman,输入本机地址 http://127.0.0.1:5000/index:

     

    定义一个接受请求的API:

    #定义一个接受请求的API

    @app.route('/transactions/new',methods = ['POST'])

    def new_transaction():

        return "We'll add a new transaction"

    此时用Postman会报405错误是因为没有将其改成POST:

     

    继续添加:

    #定义一个用来挖矿(交易打包)的API:

    @app.route('/mine',methods = ['GET'])

    def mine():

        return "We'll mine a new block"

     

    #定义返回整个区块链信息的路由:

    @app.route('/chain',methods = ['GET'])

    def full_chain():

        return "return full chain"

     

    目前为止的代码:

    # {

    #     "index":0,  #块的索引

    #     "timestamp":"",     #时间戳

    #     "transactions":[    #交易信息

    #         {

    #             "sender":"",    #交易的发送者

    #             "recipient":"",     #交易的接收者

    #             "amount":5,     #交易的金额

    #         }

    #     ],

    #     "proof":"",     #工作量证明

    #     "previous_hash":"",     #上一个区块的哈希值

    #

    # }

    import hashlib

    import json

    from time import time

    from flask import Flask

     

     

    class Blockchain:

     

        #构造函数

        def __init__(self):

            # 一个元素对应一个块

            self.chain = []

            # 保存当前的交易信息,一个元素对应一个交易实体

            self.current_transactions = []

     

            # 创建创世区块,不用计算,没有内容

            self.new_block(proof = 100,previous_hash = 1)

     

        # 新的区块

        def new_block(self,proof,previous_hash = None):

            #构造json对象

            block = {

                'index':len(self.chain) + 1,

                'timestamp':time(),

                'transcations':self.current_transactions,

                'proof':proof,

                # 可以是传过来的hash值或者是调用hash()计算出来的hash值

                'previous_hash':previous_hash or self.hash(self.last_block)

            }

     

            #把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了

            self.current_transactions = []

     

            #把生成的区块加入到链条中

            self.chain.append(block)

     

            return block

     

        # 新的交易(发送者,接受者,金额)

        def new_transaction(self,sender,recipient,amount)->int:

            #每个交易信息都是一个json,把它添加到交易信息的最后

            self.current_transactions.append(

                {

                    'sender':sender,

                    'recipient':recipient,

                    'amount':amount

                }

            )

            #返回索引:原索引加一

            return self.last_block['index'] + 1

     

        # 静态方法,计算区块的哈希值

        @staticmethod

        def hash(block):

            #block是json对象,先把json转化为string并对字符排序并编码

            block_string = json.dumps(block,sort_keys = True).encode()

            #传入字符串编码后的字节数组,返回hash的摘要信息

            return hashlib.sha256(block_string).hexdigest()

     

        # 属性,获取到区块链最后一个区块

        @property

        def last_block(self):

            #-1表示是数组最后一个元素

            return self.chain[-1]

     

        #工作量证明

        def proof_of_work(self,last_proof:int)->int:

            proof = 0

            #不停尝试proof的值,验证proof是否满足条件

            while self.valid_proof(last_proof,proof) is False:

                proof += 1

            print(proof)

            return proof

     

        #验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件

        def valid_proof(self,last_proof:int,proof:int)->bool:

            #先把两个值转化成一个字符串并编码

            guess = f'{last_proof}{proof}'.encode()

            #用同样的方法拿到hash摘要

            guess_hash = hashlib.sha256(guess).hexdigest()

            print(guess_hash)

            #如果满足前面四位是以0开头则返回True

            return guess_hash[0:4] == '0000'

     

    #初始化Flask类

    app = Flask(__name__)

     

    #定义一个接受请求的API

    @app.route('/transactions/new',methods = ['POST'])

    def new_transaction():

        return "We'll add a new transaction"

     

    #定义一个用来挖矿(交易打包)的API:

    @app.route('/mine',methods = ['GET'])

    def mine():

        return "We'll mine a new block"

     

    #定义返回整个区块链信息的路由:

    @app.route('/chain',methods = ['GET'])

    def full_chain():

        return "return full chain"

     

    #启动Flask,提供运行入口

    if __name__ == '__main__':

        app.run(host = '0.0.0.0',port = 5000)

     

    3-7 交易接口实现

     

    实例化一个blockchain:

    blockchain = Blockchain()

    实现chain接口:

    #定义返回整个区块链信息的路由

    @app.route('/chain',methods = ['GET'])

    def full_chain():

        response = {

            #块的信息

            'chain':blockchain.chain,

            #链条数组的长度

            'length':len(blockchain.chain)

        }

        #将dict类型转换为json串

        return jsonify(response),200

    在Postman中查看:

     

    实现添加交易接口:

    @app.route('/transactions/new',methods = ['POST'])

    def new_transaction():

        #拿到客户POST过来的内容

        values = request.get_json()

     

        #如果没有values的情况

        if values is None:

            return 'Missing values',400

     

        #对三个字段进行检查,看请求时是否带上了这三个参数

        required = ['sender','recipient','amount']

        #如果k中内容有一个不在required中的话返回错误,返回400

        if not all(k in values for k in required):

            return 'Missing values',400

     

        #如果满足格式则新建一个交易,把交易添加到当前的数组,返回的是其即将所在的区块索引

        index = blockchain.new_transaction(values['sender'],

                                   values['recipient'],

                                   values['amount'])

        #把新添加的交易的信息返回给用户

        response = {'message':f'Transaction will be added to block:{index}'}

        #通常post请求去添加一条记录都是返回201

        return jsonify(response),201

    Postman中:

     

    3-8 挖矿接口实现

     

    #利用uuid随机生成节点的ID,替换掉横杠

    node_identifier = str(uuid4()).replace('-','')

     

    #定义一个用来挖矿(交易打包)的API

    @app.route('/mine',methods = ['GET'])

    def mine():

        #拿到上一个区块的信息

        last_block = blockchain.last_block

        #取出上一个块的工作量证明

        last_proof = last_block['proof']

        #计算出当前块的工作量证明

        proof = blockchain.proof_of_work(last_proof)

     

        #给自己添加一个奖励

        blockchain.new_transaction(sender ='0',

                                   recipient = node_identifier,

                                   amount = 1)

     

        #用proof新建一个块,传None的话会自己计算上一个区块的hash值

        block = blockchain.new_block(proof,None)

     

        #把包好的信息返回给用户

        response = {

            "message":"New Block Forged",

            "index":block['index'],

            "transactions":block['transcations'],

            "proof":block['proof'],

            "previous_hash":block['previous_hash']

        }

     

        return jsonify(response),200

    Postman中先Post再Get

     

    到目前为止的代码:

    # {

    #     "index":0,  #块的索引

    #     "timestamp":"",     #时间戳

    #     "transactions":[    #交易信息

    #         {

    #             "sender":"",    #交易的发送者

    #             "recipient":"",     #交易的接收者

    #             "amount":5,     #交易的金额

    #         }

    #     ],

    #     "proof":"",     #工作量证明

    #     "previous_hash":"",     #上一个区块的哈希值

    #

    # }

    import hashlib

    import json

    from uuid import uuid4

    from time import time

    from flask import Flask, jsonify, request

     

     

    class Blockchain:

     

        #构造函数

        def __init__(self):

            # 一个元素对应一个块

            self.chain = []

            # 保存当前的交易信息,一个元素对应一个交易实体

            self.current_transactions = []

     

            # 创建创世区块,不用计算,没有内容

            self.new_block(proof = 100,previous_hash = 1)

     

        # 新的区块

        def new_block(self,proof,previous_hash = None):

            #构造json对象

            block = {

                'index':len(self.chain) + 1,

                'timestamp':time(),

                'transcations':self.current_transactions,

                'proof':proof,

                # 可以是传过来的hash值或者是调用hash()计算出来的hash值

                'previous_hash':previous_hash or self.hash(self.last_block)

            }

     

            #把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了

            self.current_transactions = []

     

            #把生成的区块加入到链条中

            self.chain.append(block)

     

            return block

     

        # 新的交易(发送者,接受者,金额)

        def new_transaction(self,sender,recipient,amount)->int:

            #每个交易信息都是一个json,把它添加到交易信息的最后

            self.current_transactions.append(

                {

                    'sender':sender,

                    'recipient':recipient,

                    'amount':amount

                }

            )

            #返回索引:原索引加一

            return self.last_block['index'] + 1

     

        # 静态方法,计算区块的哈希值

        @staticmethod

        def hash(block):

            #block是json对象,先把json转化为string并对字符排序并编码

            block_string = json.dumps(block,sort_keys = True).encode()

            #传入字符串编码后的字节数组,返回hash的摘要信息

            return hashlib.sha256(block_string).hexdigest()

     

        # 属性,获取到区块链最后一个区块

        @property

        def last_block(self):

            #-1表示是数组最后一个元素

            return self.chain[-1]

     

        #工作量证明

        def proof_of_work(self,last_proof:int)->int:

            proof = 0

            #不停尝试proof的值,验证proof是否满足条件

            while self.valid_proof(last_proof,proof) is False:

                proof += 1

            print(proof)

            return proof

     

        #验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件

        def valid_proof(self,last_proof:int,proof:int)->bool:

            #先把两个值转化成一个字符串并编码

            guess = f'{last_proof}{proof}'.encode()

            #用同样的方法拿到hash摘要

            guess_hash = hashlib.sha256(guess).hexdigest()

            print(guess_hash)

            #如果满足前面四位是以0开头则返回True

            return guess_hash[0:4] == '0000'

     

    #初始化Flask类

    app = Flask(__name__)

     

    #实例化blockchain

    blockchain = Blockchain()

     

    #利用uuid随机生成节点的ID,替换掉横杠

    node_identifier = str(uuid4()).replace('-','')

     

    #定义一个新添加交易的路由,用POST发送到服务器

    @app.route('/transactions/new',methods = ['POST'])

    def new_transaction():

        #拿到客户POST过来的内容

        values = request.get_json()

     

        #如果没有values的情况

        if values is None:

            return 'Missing values',400

     

        #对三个字段进行检查,看请求时是否带上了这三个参数

        required = ['sender','recipient','amount']

        #如果k中内容有一个不在required中的话返回错误,返回400

        if not all(k in values for k in required):

            return 'Missing values',400

     

        #如果满足格式则新建一个交易,把交易添加到当前的数组,返回的是其即将所在的区块索引

        index = blockchain.new_transaction(values['sender'],

                                   values['recipient'],

                                   values['amount'])

        #把新添加的交易的信息返回给用户

        response = {'message':f'Transaction will be added to block:{index}'}

        #通常post请求去添加一条记录都是返回201

        return jsonify(response),201

     

    #定义一个用来挖矿(交易打包)的API

    @app.route('/mine',methods = ['GET'])

    def mine():

        #拿到上一个区块的信息

        last_block = blockchain.last_block

        #取出上一个块的工作量证明

        last_proof = last_block['proof']

        #计算出当前块的工作量证明

        proof = blockchain.proof_of_work(last_proof)

     

        #给自己添加一个奖励

        blockchain.new_transaction(sender ='0',

                                   recipient = node_identifier,

                                   amount = 1)

     

        #用proof新建一个块,传None的话会自己计算上一个区块的hash值

        block = blockchain.new_block(proof,None)

     

        #把包好的信息返回给用户

        response = {

            "message":"New Block Forged",

            "index":block['index'],

            "transactions":block['transcations'],

            "proof":block['proof'],

            "previous_hash":block['previous_hash']

        }

     

        return jsonify(response),200

     

    #定义返回整个区块链信息的路由

    @app.route('/chain',methods = ['GET'])

    def full_chain():

        response = {

            #块的信息

            'chain':blockchain.chain,

            #链条数组的长度

            'length':len(blockchain.chain)

        }

        #将dict类型转换为json串

        return jsonify(response),200

     

    #启动Flask,提供运行入口

    if __name__ == '__main__':

        app.run(host = '0.0.0.0',port = 5000)

     

     

     

    3-9 实现注册节点

     

    #注册节点

    def register_node(self,address:str):

        #地址格式:http://127.0.0.1:5001

        #通过urlparse解析地址

        parsed_url = urlparse(address)

        #将地址中的netloc部分提取出来

        self.nodes.add(parsed_url.netloc)

     

    #节点注册路由

    #{"nodes":["http://127.0.0.2:5000"]}

    @app.route('/nodes/register',methods = ['POST'])

    def register_nodes():

        #接受传过来的数据

        values = request.get_json()

        #接受节点信息

        nodes = values.get("nodes")

     

        #信息为空判断

        if nodes is None:

            return "Error:please supply a valid list of node",400

     

        #多node注册

        for node in nodes:

            blockchain.register_node(node)

     

        #信息返回给用户

        response = {

            "message":"New nodes have been added",

            #原来定义的是个set集合,这里转化成list

            "total_node":list(blockchain.nodes)

        }

     

        return jsonify(response),201

     

    Postman:

    到目前为止的代码:

    # {

    #     "index":0,  #块的索引

    #     "timestamp":"",     #时间戳

    #     "transactions":[    #交易信息

    #         {

    #             "sender":"",    #交易的发送者

    #             "recipient":"",     #交易的接收者

    #             "amount":5,     #交易的金额

    #         }

    #     ],

    #     "proof":"",     #工作量证明

    #     "previous_hash":"",     #上一个区块的哈希值

    #

    # }

    import hashlib

    import json

    from uuid import uuid4

    from time import time

    from flask import Flask, jsonify, request

    from  urllib.parse import urlparse

     

     

    class Blockchain:

     

        #构造函数

        def __init__(self):

            # 一个元素对应一个块

            self.chain = []

            # 保存当前的交易信息,一个元素对应一个交易实体

            self.current_transactions = []

            # 保存节点信息,set()中每个值都是独一无二的

            self.nodes = set()

     

            # 创建创世区块,不用计算,没有内容

            self.new_block(proof = 100,previous_hash = 1)

     

        #注册节点

        def register_node(self,address:str):

            #地址格式:http://127.0.0.1:5001

            #通过urlparse解析地址

            parsed_url = urlparse(address)

            #将地址中的netloc部分提取出来

            self.nodes.add(parsed_url.netloc)

     

     

        # 新的区块

        def new_block(self,proof,previous_hash = None):

            #构造json对象

            block = {

                'index':len(self.chain) + 1,

                'timestamp':time(),

                'transcations':self.current_transactions,

                'proof':proof,

                # 可以是传过来的hash值或者是调用hash()计算出来的hash值

                'previous_hash':previous_hash or self.hash(self.last_block)

            }

     

            #把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了

            self.current_transactions = []

     

            #把生成的区块加入到链条中

            self.chain.append(block)

     

            return block

     

        # 新的交易(发送者,接受者,金额)

        def new_transaction(self,sender,recipient,amount)->int:

            #每个交易信息都是一个json,把它添加到交易信息的最后

            self.current_transactions.append(

                {

                    'sender':sender,

                    'recipient':recipient,

                    'amount':amount

                }

            )

            #返回索引:原索引加一

            return self.last_block['index'] + 1

     

        # 静态方法,计算区块的哈希值

        @staticmethod

        def hash(block):

            #block是json对象,先把json转化为string并对字符排序并编码

            block_string = json.dumps(block,sort_keys = True).encode()

            #传入字符串编码后的字节数组,返回hash的摘要信息

            return hashlib.sha256(block_string).hexdigest()

     

        # 属性,获取到区块链最后一个区块

        @property

        def last_block(self):

            #-1表示是数组最后一个元素

            return self.chain[-1]

     

        #工作量证明

        def proof_of_work(self,last_proof:int)->int:

            proof = 0

            #不停尝试proof的值,验证proof是否满足条件

            while self.valid_proof(last_proof,proof) is False:

                proof += 1

            print(proof)

            return proof

     

        #验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件

        def valid_proof(self,last_proof:int,proof:int)->bool:

            #先把两个值转化成一个字符串并编码

            guess = f'{last_proof}{proof}'.encode()

            #用同样的方法拿到hash摘要

            guess_hash = hashlib.sha256(guess).hexdigest()

            print(guess_hash)

            #如果满足前面四位是以0开头则返回True

            return guess_hash[0:4] == '0000'

     

    #初始化Flask类

    app = Flask(__name__)

     

    #实例化blockchain

    blockchain = Blockchain()

     

    #利用uuid随机生成节点的ID,替换掉横杠

    node_identifier = str(uuid4()).replace('-','')

     

    #定义一个新添加交易的路由,用POST发送到服务器

    @app.route('/transactions/new',methods = ['POST'])

    def new_transaction():

        #拿到客户POST过来的内容

        values = request.get_json()

     

        #如果没有values的情况

        if values is None:

            return 'Missing values',400

     

        #对三个字段进行检查,看请求时是否带上了这三个参数

        required = ['sender','recipient','amount']

        #如果k中内容有一个不在required中的话返回错误,返回400

        if not all(k in values for k in required):

            return 'Missing values',400

     

        #如果满足格式则新建一个交易,把交易添加到当前的数组,返回的是其即将所在的区块索引

        index = blockchain.new_transaction(values['sender'],

                                   values['recipient'],

                                   values['amount'])

        #把新添加的交易的信息返回给用户

        response = {'message':f'Transaction will be added to block:{index}'}

        #通常post请求去添加一条记录都是返回201

        return jsonify(response),201

     

    #定义一个用来挖矿(交易打包)的API

    @app.route('/mine',methods = ['GET'])

    def mine():

        #拿到上一个区块的信息

        last_block = blockchain.last_block

        #取出上一个块的工作量证明

        last_proof = last_block['proof']

        #计算出当前块的工作量证明

        proof = blockchain.proof_of_work(last_proof)

     

        #给自己添加一个奖励

        blockchain.new_transaction(sender ='0',

                                   recipient = node_identifier,

                                   amount = 1)

     

        #用proof新建一个块,传None的话会自己计算上一个区块的hash值

        block = blockchain.new_block(proof,None)

     

        #把包好的信息返回给用户

        response = {

            "message":"New Block Forged",

            "index":block['index'],

            "transactions":block['transcations'],

            "proof":block['proof'],

            "previous_hash":block['previous_hash']

        }

     

        return jsonify(response),200

     

    #定义返回整个区块链信息的路由

    @app.route('/chain',methods = ['GET'])

    def full_chain():

        response = {

            #块的信息

            'chain':blockchain.chain,

            #链条数组的长度

            'length':len(blockchain.chain)

        }

        #将dict类型转换为json串

        return jsonify(response),200

     

    #节点注册路由

    #{"nodes":["http://127.0.0.2:5000"]}

    @app.route('/nodes/register',methods = ['POST'])

    def register_nodes():

        #接受传过来的数据

        values = request.get_json()

        #接受节点信息

        nodes = values.get("nodes")

     

        #信息为空判断

        if nodes is None:

            return "Error:please supply a valid list of node",400

     

        #多node注册

        for node in nodes:

            blockchain.register_node(node)

     

        #信息返回给用户

        response = {

            "message":"New nodes have been added",

            #原来定义的是个set集合,这里转化成list

            "total_node":list(blockchain.nodes)

        }

     

        return jsonify(response),201

     

    #启动Flask,提供运行入口

    if __name__ == '__main__':

        app.run(host = '0.0.0.0',port = 5000)

     

     

     

    3-10 实现共识机制-1

     

    #验证hash值,看是否是有效链

    def valid_chain(self,chain)->bool:

        #取首块

        last_block = chain[0]

        #当前索引(第一个块索引是0不用计算,则从第二个块——索引是1的开始计算)

        current_index = 1

     

        #遍历这个链

        while current_index <len(chain):

            block = chain[current_index]

            #如果当前块的前一个哈希值属性值不等于我们计算出来的上一个块的哈希值,说明链虚假,验证不通过

            if block['previous_hash'] != self.hash(last_block):

                return False

            #工作量证明可能不满足规定(这里是四个0开头)

            if not self.valid_proof(last_block['proof'],block['proof']):

                return False

     

            last_block = block

            current_index += 1

     

        return True

     

    #解决冲突

    def resolve_conflicts(self)->bool:

        #拿到节点信息

        neighbours = self.nodes

        #自身链表长度

        max_length = len(self.chain)

        #暂存链条

        new_chain = None

     

        #遍历邻居的数据,用最长的链条取代该链

        for node in neighbours:

            response = requests.get(f'http://{node}/chain')

            if response.status_code == 200:

                length = response.json()['length']

                chain = response.json()['chain']

     

                #若是较长链且是有效链则取代

                if length > max_length and self.valid_chain(chain):

                    max_length = length

                    new_chain = chain

     

        #如果new_chain存在的话,则说明它是新的最长链

        if new_chain:

            self.chain = new_chain

            return True

     

        return False

     

    3-11 实现共识机制-2 

     

    #可以调用解决冲突的路由

    @app.route('/nodes/resolve',methods = ['GET'])

    def consensus():

        #调用函数并查看链条是否被取代了

        replaced = blockchain.resolve_conflicts()

        #如果被取代了要告诉一下用户

        if replaced:

            response = {

                "message":"Our chain was replaced",

                "new_chain":blockchain.chain

            }

        else:

            response = {

                "message": "Our chain is authoritative",

                           "chain":blockchain.chain

            }

        return jsonify(response),200

     

    Postman测试:

     

    为了使每次运行可以选择不同的端口,修改之前的代码:

    #启动Flask,提供运行入口

    if __name__ == '__main__':

        #每次运行可以跑在不同的端口上,不是默认一个,而是通过参数传过来的

        #初始化一个parser用来解析命令行参数

        parser = ArgumentParser()

        #加上端口命令,举例:-p 5001 或者--port 5001

        parser.add_argument('-p','--port',default = 5000,type = int,help = 'port to listen to')

        #对其解析

        args = parser.parse_args()

        port = args.port

     

        app.run(host = '0.0.0.0',port = port)

    在终端运行测试更改利用-p指令选择了5001端口:

    (venv) D:\lesson1>python blockchain.py -p 5001

    * Serving Flask app "blockchain" (lazy loading)

    * Environment: production

       WARNING: Do not use the development server in a production environment.

       Use a production WSGI server instead.

    * Debug mode: off

    * Running on http://0.0.0.0:5001/ (Press CTRL+C to quit)

    Postman:

     

    最终代码:

    # {

    #     "index":0,  #块的索引

    #     "timestamp":"",     #时间戳

    #     "transactions":[    #交易信息

    #         {

    #             "sender":"",    #交易的发送者

    #             "recipient":"",     #交易的接收者

    #             "amount":5,     #交易的金额

    #         }

    #     ],

    #     "proof":"",     #工作量证明

    #     "previous_hash":"",     #上一个区块的哈希值

    #

    # }

    import hashlib

    import json

    import requests

    from uuid import uuid4

    from time import time

    from flask import Flask, jsonify, request

    from  urllib.parse import urlparse

    from argparse import ArgumentParser

     

    class Blockchain:

     

        #构造函数

        def __init__(self):

            # 一个元素对应一个块

            self.chain = []

            # 保存当前的交易信息,一个元素对应一个交易实体

            self.current_transactions = []

            # 保存节点信息,set()中每个值都是独一无二的

            self.nodes = set()

     

            # 创建创世区块,不用计算,没有内容

            self.new_block(proof = 100,previous_hash = 1)

     

        #注册节点

        def register_node(self,address:str):

            #地址格式:http://127.0.0.1:5001

            #通过urlparse解析地址

            parsed_url = urlparse(address)

            #将地址中的netloc部分提取出来

            self.nodes.add(parsed_url.netloc)

     

        #验证hash值,看是否是有效链

        def valid_chain(self,chain)->bool:

            #取首块

            last_block = chain[0]

            #当前索引(第一个块索引是0不用计算,则从第二个块——索引是1的开始计算)

            current_index = 1

     

            #遍历这个链

            while current_index <len(chain):

                block = chain[current_index]

                #如果当前块的前一个哈希值属性值不等于我们计算出来的上一个块的哈希值,说明链虚假,验证不通过

                if block['previous_hash'] != self.hash(last_block):

                    return False

                #工作量证明可能不满足规定(这里是四个0开头)

                if not self.valid_proof(last_block['proof'],block['proof']):

                    return False

     

                last_block = block

                current_index += 1

     

            return True

     

        #解决冲突

        def resolve_conflicts(self)->bool:

            #拿到节点信息

            neighbours = self.nodes

            #自身链表长度

            max_length = len(self.chain)

            #暂存链条

            new_chain = None

     

            #遍历邻居的数据,用最长的链条取代该链

            for node in neighbours:

                response = requests.get(f'http://{node}/chain')

                if response.status_code == 200:

                    length = response.json()['length']

                    chain = response.json()['chain']

     

                    #若是较长链且是有效链则取代

                    if length > max_length and self.valid_chain(chain):

                        max_length = length

                        new_chain = chain

     

            #如果new_chain存在的话,则说明它是新的最长链

            if new_chain:

                self.chain = new_chain

                return True

     

            return False

     

     

        # 新的区块

        def new_block(self,proof,previous_hash = None):

            #构造json对象

            block = {

                'index':len(self.chain) + 1,

                'timestamp':time(),

                'transcations':self.current_transactions,

                'proof':proof,

                # 可以是传过来的hash值或者是调用hash()计算出来的hash值

                'previous_hash':previous_hash or self.hash(self.last_block)

            }

     

            #把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了

            self.current_transactions = []

     

            #把生成的区块加入到链条中

            self.chain.append(block)

     

            return block

     

        # 新的交易(发送者,接受者,金额)

        def new_transaction(self,sender,recipient,amount)->int:

            #每个交易信息都是一个json,把它添加到交易信息的最后

            self.current_transactions.append(

                {

                    'sender':sender,

                    'recipient':recipient,

                    'amount':amount

                }

            )

            #返回索引:原索引加一

            return self.last_block['index'] + 1

     

        # 静态方法,计算区块的哈希值

        @staticmethod

        def hash(block):

            #block是json对象,先把json转化为string并对字符排序并编码

            block_string = json.dumps(block,sort_keys = True).encode()

            #传入字符串编码后的字节数组,返回hash的摘要信息

            return hashlib.sha256(block_string).hexdigest()

     

        # 属性,获取到区块链最后一个区块

        @property

        def last_block(self):

            #-1表示是数组最后一个元素

            return self.chain[-1]

     

        #工作量证明

        def proof_of_work(self,last_proof:int)->int:

            proof = 0

            #不停尝试proof的值,验证proof是否满足条件

            while self.valid_proof(last_proof,proof) is False:

                proof += 1

            print(proof)

            return proof

     

        #验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件

        def valid_proof(self,last_proof:int,proof:int)->bool:

            #先把两个值转化成一个字符串并编码

            guess = f'{last_proof}{proof}'.encode()

            #用同样的方法拿到hash摘要

            guess_hash = hashlib.sha256(guess).hexdigest()

            print(guess_hash)

            #如果满足前面四位是以0开头则返回True

            return guess_hash[0:4] == '0000'

     

    #初始化Flask类

    app = Flask(__name__)

     

    #实例化blockchain

    blockchain = Blockchain()

     

    #利用uuid随机生成节点的ID,替换掉横杠

    node_identifier = str(uuid4()).replace('-','')

     

    #定义一个新添加交易的路由,用POST发送到服务器

    @app.route('/transactions/new',methods = ['POST'])

    def new_transaction():

        #拿到客户POST过来的内容

        values = request.get_json()

     

        #如果没有values的情况

        if values is None:

            return 'Missing values',400

     

        #对三个字段进行检查,看请求时是否带上了这三个参数

        required = ['sender','recipient','amount']

        #如果k中内容有一个不在required中的话返回错误,返回400

        if not all(k in values for k in required):

            return 'Missing values',400

     

        #如果满足格式则新建一个交易,把交易添加到当前的数组,返回的是其即将所在的区块索引

        index = blockchain.new_transaction(values['sender'],

                                   values['recipient'],

                                   values['amount'])

        #把新添加的交易的信息返回给用户

        response = {'message':f'Transaction will be added to block:{index}'}

        #通常post请求去添加一条记录都是返回201

        return jsonify(response),201

     

    #定义一个用来挖矿(交易打包)的API

    @app.route('/mine',methods = ['GET'])

    def mine():

        #拿到上一个区块的信息

        last_block = blockchain.last_block

        #取出上一个块的工作量证明

        last_proof = last_block['proof']

        #计算出当前块的工作量证明

        proof = blockchain.proof_of_work(last_proof)

     

        #给自己添加一个奖励

        blockchain.new_transaction(sender ='0',

                                   recipient = node_identifier,

                                   amount = 1)

     

        #用proof新建一个块,传None的话会自己计算上一个区块的hash值

        block = blockchain.new_block(proof,None)

     

        #把包好的信息返回给用户

        response = {

            "message":"New Block Forged",

            "index":block['index'],

            "transactions":block['transcations'],

            "proof":block['proof'],

            "previous_hash":block['previous_hash']

        }

     

        return jsonify(response),200

     

    #定义返回整个区块链信息的路由

    @app.route('/chain',methods = ['GET'])

    def full_chain():

        response = {

            #块的信息

            'chain':blockchain.chain,

            #链条数组的长度

            'length':len(blockchain.chain)

        }

        #将dict类型转换为json串

        return jsonify(response),200

     

    #节点注册路由

    #{"nodes":["http://127.0.0.2:5000"]}

    @app.route('/nodes/register',methods = ['POST'])

    def register_nodes():

        #接受传过来的数据

        values = request.get_json()

        #接受节点信息

        nodes = values.get("nodes")

     

        #信息为空判断

        if nodes is None:

            return "Error:please supply a valid list of node",400

     

        #多node注册

        for node in nodes:

            blockchain.register_node(node)

     

        #信息返回给用户

        response = {

            "message":"New nodes have been added",

            #原来定义的是个set集合,这里转化成list

            "total_node":list(blockchain.nodes)

        }

     

        return jsonify(response),201

     

    #可以调用解决冲突的路由

    @app.route('/nodes/resolve',methods = ['GET'])

    def consensus():

        #调用函数并查看链条是否被取代了

        replaced = blockchain.resolve_conflicts()

        #如果被取代了要告诉一下用户

        if replaced:

            response = {

                "message":"Our chain was replaced",

                "new_chain":blockchain.chain

            }

        else:

            response = {

                "message": "Our chain is authoritative",

                "chain":blockchain.chain

            }

        return jsonify(response),200

     

     

    #启动Flask,提供运行入口

    if __name__ == '__main__':

        #每次运行可以跑在不同的端口上,不是默认一个,而是通过参数传过来的

        #初始化一个parser用来解析命令行参数

        parser = ArgumentParser()

        #加上端口命令,举例:-p 5001 或者--port 5001

        parser.add_argument('-p','--port',default = 5000,type = int,help = 'port to listen to')

        #对其解析

        args = parser.parse_args()

        port = args.port

     

        app.run(host = '0.0.0.0',port = port)

     

    个人博客:www.unconstraint.cn

     

    展开全文
  • HackPython 致力于有趣有价值编程教学简介在逛 github 时候看见这个项目 blockchain,该项目通过 python 实现了最简单...

    HackPython 致力于有趣有价值的编程教学

    简介

    在逛 github 的时候看见这个项目 blockchain,该项目通过 python 实现了最简单的区块链,我将它复现了一下,并做了一些修改,比如创世区块需要挖矿才生成,而不直接生成,交易时会查看整条区块链确保账号金额足够,简化工作量证明算法等等,同时还加了详细的中文注释,项目地址如下,欢迎给星 myblockchain

    项目使用的环境:mac pro,python3.6.3

    第三方库:flask,requests

    文章比较长,请耐心阅读。

    共识

    再编写项目前,先有几个共识

    • 1. 区块链是通过区块的 hash 链接起来的,具有不可修改的特性

    • 2. 要创建区块必须要进行 PoW(工作量证明)

    • 3. 区块中存放交易记录、上个区块的 hash 和 PoW

    • 4. 区块链网络是分布式、去中心化的,每个网络节点都存有一个区块链

    • 5. 每个节点的区块链都一样

    如果对上面的共识有疑惑或者不知道说什么,建议去看上一篇文章《比特币原理》,有了这些知识才能更好的理解区块链

    创建区块链类

    创建名为 Blockchain 类,该类用于存放区块链相关的所有内容,定义如下

    class Blockchain:
        def __init__(self):
            # 交易列表
            self.current_transactions = []
            # 区块链
            self.chain = []
            # 网络节点
            self.nodes = set()
    

    init() 方法中可以看到,我们使用 current_transactions 这个 list 来存放交易,chain 来存放区块链,nodes 来存放网络节点

    解释一下,首先整个项目通过 Flask 运行到服务端,客户端通过访问服务器的某些接口进行挖矿、交易数字货币或者检查区块链合法新。这里通过 nodes 这个 set 类型变量来存放区块链网络中的节点,set 类型确保了其中的数据不重复,我们需要知道区块链网络中的节点,才能进行共识算法的运算,使全网的区块链都一致,而区块链本身我们使用 list 简单存储一下,实际上区块链本身就是一个数据结构,这里为了简便,才直接使用 list 的

    创建新区块

    Blockchain 类(区块链类)创建好了,就往 Blockchain 类中加方法吧,首先要编写的 new_block () 方法,该方法用于创建一个新的区块,并将该区块添加到区块链中,具体定义如下:

    def new_block(self, proof, previous_hash):
       '''
       创建一个新区块加入到区块链中
       :param proof: 工作量证明
       :param previous_hash: 上一个区块的hash
       :return: 一个新区块
       '''
       # 区块
       block = {
           # 索引
           'index':len(self.chain) + 1,
           # 时间戳
           'timestamp': time(),
           # 交易账本
           'transactions':self.current_transactions,
           # 工作量证明
           'proof':proof,
           # 上一块区块的hash
           'previous_hash':previous_hash or self.hash(self.chain[-1])
       }
       # 从新置空
       self.current_transactions = []
       # 将区块添加到区块链中,此时交易账本是空,工作量证明是空
       self.chain.append(block)
       return block
    

    这个方法的代码很简单,首先创建了 block 这个 dict 类型的变量,它存储着一个完整区块所需的所有数据,其中有

    • index: 区块唯一索引,标明第几个区块

    • timestamp: 创建区块时的时间戳

    • transactions: 交易账本,其实记录着要存入这个区块的所有交易信息

    • proof: 工作量证明,需要通过穷举法运算出来

    • previous_hash: 上一个区块的 hash

    其中 transactions 交易账本直接获取 self.currenttransactions 中的值,previoushash 上个区块的 hash 要么使用参数,要么通过 hash 方法直接计算出上个区块的 hash

    这里顺带引出 hash () 方法的实现,在 python 是实现 hash 算法非常简单,导入 hashlib 这个库则可,hash () 方法具体实现如下,该方法也在 Blockchain 类中,通过 staticmethod 定义成静态方法。

    @staticmethod
    def hash(block):
        '''
        使用SHA256哈希算法计算区块的哈希
        :param block: 区块
        :return: 区块hash
        '''
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()
    

    其中使用 json 的 dumps 方法将区块这个 dict 序列化为 json 格式,注意要使用 sort_keys=True,这样可以让 json 解析后获得的 dict 通过 key 排序,还有要使用 encode () 方法对 json 字符串进行 utf-8 编码,不然 hashlib.sha256 加密时会报编码类型错误

    定义完 block 区块结构后,我们清空了交易 list,避免已经加入区块的交易在下次创建新区块时又加入新的区块中。最后将定义好的区块加到 chain 区块链中

    创建新交易

    在创建新区块时,我们将交易添加到了区块链中,但是到目前为止还没编写创建交易的方法,接着就来编写创建新交易的方法,具体代码如下:

        def new_transaction(self, sender, recipient, amount):
            '''
            创建一个新的交易,添加到我创建的下一个区块中
            :param sender: 发送者地址,发送数字币的地址
            :param recipient: 接受者地址,接受数字币的地址
            :param amount: 数字币数量
            :return: 记录本次交易的区块索引
            '''
            money = 0
            if sender != '0':
                for block in self.chain:
                    for transactions in block['transactions']:
                        if transactions['sender'] == sender:
                            money -= transactions['amount']
                        if transactions['recipient'] == sender:
                            money += transactions['amount']
                if money < amount:
                    return -1
            # 交易账本,可包含多个交易
            self.current_transactions.append({
                'sender':sender,
                'recipient':recipient,
                'amount':amount
            })
            if not self.chain:
                return 0
            # 交易账本添加到最新的区块中
            return self.last_block['index'] + 1
    

    创建交易的代码就也很简洁了,无非就是构建一个 dict 字典,将交易信息存到 dict 中构建出一个交易,然后将这个交易 dict 添加到 self.current_transactions 这个 list 中,并且返回这个交易将要被存放到的区块索引。

    从代码中可以看出,构成一次交易的信息非常简单,就是发送者的地址、接受者的地址、交易时数字货币的数量,但是完成一次交易是有条件的,就是发送者拥有的数字货币数量大于要发送的数量,不然就无法完成交易,因为不够钱,判断也很简单,就是循环整个区块链,将与发送者有关的交易都统计一遍,获得当下发送者拥有的数字货币。如果发送者地址为 0,说明是系统奖励给区块建立者的数字货币,就不必做这样的判断。

    最后返回要存储这次交易记录的区块索引

    工作量证明 (PoW) 算法实现

    要创建一个合法的区块就需要进行 PoW,PoW 大致原理在《比特币原理》那篇文章中也提过,所以就直接来实现一下

        def proof_of_work(self, last_block):
            '''
            简单的工作量证明算法
            找到一个数,使得区块的hash前4位为0
            :param last_proof 上一个区块的工作量证明
            :return: 特殊的数
            '''
            # 工作量证明--->穷举法计算出特殊的数
            last_proof = last_block['proof']
            last_hash = self.hash(last_block)
            proof = 0
            while self.valid_proof(proof,last_hash) is False:
                proof += 1
            return proof
        @staticmethod
        def valid_proof(proof, last_hash):
            '''
            验证工作量证明,计算出的hash是否正确
            对上一个区块的proof和hash与当期区块的proof最sha256运算
            :param last_proof: 上一个区块的工作量
            :param proof: 当前区块的工作量
            :param last_hash: 上一个区块的hash
            :return: True 工作量是正确的 False 错误
            '''
            # f-string pyton3.6新的格式化字符串函数
            guess = f'{proof}{last_hash}'.encode()
            guess_hash = hashlib.sha256(guess).hexdigest()
            return guess_hash[:4] == "0000"
    

    可以看到代码比较少,注释占多数。

    工作量证明算法我们定义了两个方法,先看 proofofwork () 方法,该方法简单讲就是通过穷举法计算出一个特殊的数,这个特殊的数就是本区块的工作量证明,其中通过 valid_proof () 方法来判断这个数是否特殊

    再看到 valid_proof () 方法,首先用 f 函数格式化字符串,然后同样通过 hashlib.sha256 () 来计算 hash,然后判断该 hash 是否前 4 位为 0,如果是,那么就找到了本区块的工作量证明了

    在 valid_proof () 方法中计算工作量证明时只使用了上一个区块的 hash,这其实就够了,需要注意的是,工作量证明虽然叫工作量证明,但它不单只是用于证明工作量,我们还需要用它来保护区块链,让区块链就有不可修改的特性,而计算时使用上一个区块的 hash 就能满足这两个要求,当区块中的数据改变时,该区块的 hash 也就变了,那么下一个区块就需要重新做工作量证明,这就需要花费大量的算了,因为我们这里只要求 hash 前 4 个为 0,所以计算相对简单,但现实中没那么轻松,然计算在一个复杂度上才能避免他人算力的攻击。如果在计算区块的工作量证明时不使用上一个区块的 hash,而是使用上一个区块中的其他数据,如上一个区块的 PoW,虽然这样也能计算出本区块的 PoW,但是就无法防御他人的算力攻击了,他人可以修改区块中的数据,但是不改这个区块的 PoW,这样他就不用重新计算其他区块的 PoW 了,虽然区块的 hash 变了,但变了就变了,因为计算一个区块的 hash 很快就可以完成,又拥有了目前区块链中所有区块的 PoW,完全可以造出一个一样长度的区块链,让网络中的节点难辨真假

    验证区块链

    通过上面的努力,我们已经可以创建区块链了,接着就来编写验证区块链的逻辑,之所以要验证是因为在区块链是分布式去中心的,在区块链网络中会存在其他节点,此时我们无法确定我们手中的区块链就是最长的,需要获取网络中其他节点的区块链,为了避免其他节点发送非法的区块链给我们,就需要验证区块链,定义 valid_chain () 方法,用于验证区块链

        def valid_chain(self, chain):
            '''
            检验区块链是否是合法的
            1.检查区块链是否连续
            2.检查工作量证明是否正常
            :param chain: 一份区块链
            :return: True 区块链合法,False 区块链非法
            '''
            last_block = chain[0] #创世区块
            current_index = 1
            # 循环检查每个区块
            while current_index < len(chain):
                block = chain[current_index]
                print(f'{last_block}')
                print(f'{block}')
                print('\n-----------\n')
                # 本区块存储的hash是否等于上一个区块通过hash函数计算出的hash,判断区块链是否连续
                if block['previous_hash'] != self.hash(last_block):
                    return False
                # 验证工作量证明是否计算正确
                if not self.valid_proof(block['proof'], block['previous_hash']):
                    return False
                last_block = block
                current_index += 1
            return True
    

    验证区块链主要做两个动作

    • 1. 检查区块链是否连续,其实就是判断每个区块中存的上一个区块的 hash 是否真的就是上一个区块的 hash

    • 2. 检查区块中的 PoW 是否正确

    valid_chain () 方法从创世区块开始判断,代码注释都很清楚,就不细讲了

    添加区块链网络节点

    上面一直在说为了确认自身的区块链是否最长,需要向区块链网络中的其他节点请求他们的区块链,下面就来实现这部分逻辑

    首先是添加节点,回忆一下,在一开始的 init() 方法中我们定义了 set 类型的 nodes 变量用于存放节点,定义 register_node () 方法,将其他节点的 url 地址添加进来则可

        def register_node(self, address):
            '''
            添加新的节点进入区块链网络,分布式的目的是去中心化
            :param address: 新节点网络地址,如:http://192.168.5.20:1314
            '''
            # urlparse解析url <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
            parsed_url = urlparse(address)
            # 将新节点的url地址添加到节点中
            self.nodes.add(parsed_url.netloc)
    

    共识算法

    共识算法用于回答:网络中有多个节点,如何区别节点间区块链一致性?这个问题,答案也很简单,网络中所有节点都使用最长的合法区块链则可,合法且最长表示该区块链消耗的运算资源最多

    定义 resolve_conflicts () 方法实现共识算法,该方法做的事情也很简单,GET 请求每个节点的接口,叫该节点将它的区块链发送给你,你判断一下发送过来的区块链是合法的,再判断一下发过来的区块链和你现在已有的区块链谁长,谁长用谁,具体代码如下

        def resolve_conflicts(self):
            '''
            共识算法,确保区块链网络中每个网络节点存储的区块链都是一致的,通过长区块链替换短区块链实现
            :return: True 替换 False不替换
            '''
            neighbours = self.nodes # 邻居节点
            new_chain = None
            # 我拥有区块链的长度
            max_length = len(self.chain)
            for node in neighbours:
                # 访问节点的一个接口,拿到该接口的区块链长度和区块链本身
                try:
                    response = requests.get(f'http://{node}/chain')
                    if response.status_code == 200:
                        length = response.json()['length']
                        chain = response.json()['chain']
                        # 判断邻居节点发送过来的区块链长度是否最长且是否合法
                        if length > max_length and self.valid_chain(chain):
                            # 使用邻居节点的区块链
                            max_length = length
                            new_chain = chain
                except:
                    # 节点没开机
                    pass
            if new_chain:
                self.chain = new_chain
                return True
            return False
    

    看到网络请求,可能你也就知道 Flask 的用途了,响应这些请求,必须节点都是在网络上,那么必须有处理请求的功能,使用一个现成的 web 框架是最简单的做法

    到这里 Blockchain 类就编写完了,还差一个简单的方法,用于获取区块链中最后一个区块,代码如下:

    @property
    def last_block(self):
       '''
       :return: 区块链中最后一个区块
       '''
       return self.chain[-1]
    

    创建区块

    前面讲了这么久,其实就是定义一个 Blockchain 类,它提供了很多核心方法,但我们似乎还是没办法创建区块,因为没有些调用 new_block () 方法的逻辑啊,现在就来写写。

    因为整个项目是搭建在 Flask 上,所以我们定义一个接口专门用于生成新的区块,也就是俗称的挖矿,接口名为 mine,具体代码如下

    @app.route('/mine', methods=['GET'])
    def mine():
        '''
        建立新区块
        :return:
        '''
        if not blockchain.chain:
            blockchain.new_transaction(sender='0', recipient=node_identifier, amount=50)
            block = blockchain.new_block(previous_hash='1', proof=100)
            response = {
                'message': '创世区块建立',
                'index': block['index'],
                'transactions': block['transactions'],
                'proof': block['proof'],
                'previous_hash': block['previous_hash'],
            }
            return jsonify(response), 200
        last_block = blockchain.last_block
        proof = blockchain.proof_of_work(last_block)
        # 挖矿获得一个数字货币奖励,将奖励的交易记录添加到账本中,其他的交易记录通过new_transaction接口添加
        blockchain.new_transaction(sender='0', recipient=node_identifier, amount=6)
        previous_hash = blockchain.hash(last_block)
        block = blockchain.new_block(proof, previous_hash)
        response = {
            'message':'新区块建立',
            'index':block['index'],
            'transactions':block['transactions'],
            'proof':block['proof'],
            'previous_hash':block['previous_hash'],
        }
        return jsonify(response),200
    

    当有人通过 GET 方法来访问 mine 接口时,就会调用 mine () 方法,mine () 方法中的逻辑很简单,首先判断区块链是否存在,不存在,说明要创建创世区块,创建区块时系统会奖励给区块创建者数字货币,代码实现就是通过 new_transaction () 方法添加一个新的交易,发送者的地址为 0,表示系统,接受者通过 uuid4 () 方法生成一个 ID,这个 ID 就唯一表示这个接受者,可以理解成他的钱包地址,uuid4 () 基于伪随机数来生成一串数字,因为是基于伪随机数,所以生成的这串数字可能会重复的。

    交易添加好,就调用 new_bloc () 方法创建创世区块就好了

    如果区块链本来就存在了,说明区块链中已经有区块了,那么就通过区块链中最后一个区块的 hash 来计算本区块的工作量证明 (使用 proofofwork () 方法),然后给在新区块中添加系统奖励的交易记录,最后调用 new_block () 方法创建出区块

    创建新交易

    现在区块时可以创建了,但是区块上记录的交易只有系统奖励,太单调了,所以接着来编写创建新交易的方法,同样定义一个接口,客户端将交易信息发送到这个接口就可以创建出新的交易,具体代码如下:

    @app.route('/transactions/new', methods=['POST'])
    def new_transaction():
        '''
        将新的交易添加到最新的区块中
        :return:
        '''
        values = request.get_json()
        required = ['sender', 'recipient', 'amount']
        if not all(k in values for k in required):
            return '缺失必要字段',400
        index = blockchain.new_transaction(values['sender'], values['recipient'],values['amount'])
        if index == -1:
            return '余额不足',400
        response = {'message':f'交易账本被添加到新的区块中{index}'}
        return jsonify(response),201
    

    首先受到客户端的交易请求信息,判断其中是否具有发起者地址 sender、接受者地址 recipient、交易数字货币量 amount,有才是个合法的请求,然后调用 new_transaction () 方法创建交易就好了

    可能有人会迷惑,创建交易似乎和创建区块分离开了?其实没有,当我们调用 newtransaction () 方法创建交易时,其实就是往 self.currenttransactions 这个 list 中添加信息,而调用 newblock () 方法创建新区块时,会将 self.currenttransactions 存到区块中,完成区块和交易的绑定

    添加网络节点

    不用多说了,区块链要去中心就需要多个节点,当区块链网络上有其他节点时,可以将它注册到进该节点,只有注册了,本节点才能感知到其他节点,同样定义一个接口来注册节点,代码如下

    @app.route('/nodes/register', methods=['POST'])
    def register_nodes():
        values = request.get_json()
        nodes = values.get('nodes')
        if nodes is None:
            return 'Error:提供有效节点列表',400
        for node in nodes:
            blockchain.register_node(node)
        response = {
            'message': '新节点加入区块链网络',
            'total_nodes':list(blockchain.nodes),
        }
        return jsonify(response),201
    

    没啥难度,就是解析一下客户端发来的注册节点请求,然后调用 register_node () 方法注册一下就好了

    区块链一致性

    节点注册好后,就需要确保这些节点中区块链是一致的了,同样定义一个接口来做这个事情,其实就是调用 resolve_conflicts () 方法

    @app.route('/node/resolve', methods=['GET'])
    def consensus():
        replaced = blockchain.resolve_conflicts()
        if replaced:
            response = {
                'message':'本节点区块链被替换',
                'new_chain':blockchain.chain
            }
        else:
            response={
                'message':'本节点区块链是权威的(区块链网络中最长的)',
                'chain':blockchain.chain
            }
        return  jsonify(response),200
    

    最后为了方便,我们定义出一个接口用于展示整个区块链

    @app.route('/chain', methods=['GET'])
    def full_chain():
        '''
        获得完整的区块链
        :return: 整份区块链
        '''
        response = {
            'chain':blockchain.chain,
            'length':len(blockchain.chain),
        }
        return jsonify(response),200
    

    最后写一段简单的开启服务器的代码就好了

    if __name__ == '__main__':
        from argparse import ArgumentParser
        parser = ArgumentParser()
        parser.add_argument('-p','--port', default=5000, type=int, help='服务启动时对应的端口')
        args = parser.parse_args()
        port = args.port
        app.run(host='0.0.0.0', port=port)
    

    具体效果

    到这里,代码就全部编写完成了,可以跑一波看看是否符合预期了,因为项目运行到 Flask 中,所以要弄个客户端来请求上面定义好的接口,直接使用 postman 来发送请求就好了

    通过下面命令将区块链服务开启,并绑定到 5000 端口

    python myblockchain.py -p 5000
    

    先使用 postman 请求 chain 接口,看看此时节点上有无区块链,发现是空的,因为还没有开始创建区块

    那么就请求 mine 接口来创建区块吧,第一次请求 mine 接口,创建出了创世区块,创世区块中记录了唯一一个交易就是系统奖励,奖励了 50 个数字货币给创世区块的创造者,比特币的创世区块也是奖励 50 个币,这里向本聪哥致敬

    继续请求 mine 接口,创造第二个区块

    第二个区块依旧没有交易,只有系统奖励,6 个数字货币

    其实这里已经有一个节点地址了,就是 f675e46d829a44fc85783a87f1b52284,它创建了 2 个区块,已经有了 56 个数字货币,那我让他发 5 个数字货币给 ayuliao,接济一下我这个穷人

    这个交易是要写入第三个区块中的,但是此时第三个区块还没被创建出来,那么在第三个区块创建出来前,这些交易都会写到这个区块中,比如我再让他发 50 个币给我

    交易完后,调用 mine 接口,创建第三块区块,它就包含了刚刚的两个交易记录和系统对区块建立者的奖励

    此时调用 chain 接口,就可以发现该节点已经有 3 个区块了

    此时你已经转了 55 个币给我了,你就剩下一个数字货币了,此时你又想转 10 币给我是会失败的

    为了验证前面编写的共识算法,也就是多节点区块链的一致性,这里再开启一个服务,你可以开在其他电脑,或者同一台电脑的不同端口

    python myblockchain.py -p 5001
    

    开始了 5001 后,请求 5001 的 mine 接口,让 5001 创建区块,让他创建 2 个区块,也就是请求两次 mine 接口,此时 5001 的区块链长为 2,而且跟 5000 节点的完全不同

    此时将 5000 节点注册到 5001 节点中,调用 5001 的 /nodes/register 接口 (你可以通过注册多个节点)

    然后我们来访问 5001 的 node/resolve 接口,使用其共识算法,让 5001 这个节点的区块链与区块链网络上的一直,其实就是判断 5001 自己手上的区块链跟网络上其他节点的区块链相比是不是最长的,其实我们知道目前为止应该是 5000 节点拥有的区块链最长,所以 5001 节点上的区块链会被替换成 5000 节点上的区块链,以求网络上所有节点的区块链保持一致

    到这里这个项目也就表演完了

    结尾

    这个项目虽然简单,但是区块链核心的东西都体现出来了,可以说麻雀虽小,五脏俱全,当前这个项目还是有些失真的,现实中的区块链项目会比这个复杂很多,也正是因为从头开发一个区块链项目是很复杂,才会出现 Ethereum (以太坊) 这样的平台型区块链项目,它已经将区块链的底层都帮我们实现好了,你可以直接将你的代码通过智能合约的形式写到 Ethereum 中,这样你的项目凭借 Ethereum 平台就自动拥有了区块链的所有特性,在 Ethereum 中虽然智能合约、智能合约的叫,但当你接触后,你就会发现,智能合约并不智能。

    还有一点需要提一下,在这个项目中,建立创世区块时,建立者获得了系统 50 个数字货币的奖励,这 50 个是可以花费的,但是在现实的比特币中,创世区块奖励的比特币是不能使用的,原因如下

    创世块的收益花不掉,原因如下:比特币客户端把区块和交易分开存贮在两个数据库中,当客户端发现区块数据库为空时,用代码直接生成一个创世块,但是没有生成这个交易,所以客户端中的交易数据库中是没有发送到上述地址这个交易的,因而一旦收到要花掉该收益的交易时,都会拒绝,所以无法得到任何确认,就花不掉这 50 个币。出现这种情况很可能是中本聪故意的

    在创建创世区块时,系统将 50 个币发送到了这个地址 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa,据说是中本聪本人,虽然这个地址上的比特币不能使用,但是还有有人将比特币源源不断的转到这个账号,事实上,这个创世区块里的地址目前有 66.76869249 个比特币,是通过 1174 笔交易发送的,他们将一笔笔钱发送到这个不可交易的账号上可能有点荒谬,但这其实人们(对中本聪)表达感谢的方式

    最后还有点很有意思

    中本聪挖出创世区块花了六天的时间。在早期的比特币论坛上有人猜测,这可能中本聪故意而为之,模仿圣经中关于创世的描述。《创世纪》第 2 章第 2 节上记载:“到第七日,上帝结束了他的工作,便休息了。”

    最后欢迎学习 HackPython 的教学课程并感觉您的阅读与支持。

    ????????

    展开全文
  • 安装该项目的依赖项 pipenv install 激活该项目的virtualenv pipenv shell 用法 创建一个钱包 $ python cli.py createwallet Your new address: 17Y288D5DnFwU6cj5M8YHnxYNnDhN6f5FK 创建区块链并获得第一笔采矿...
  • 不到 200 行代码的区块链 Python 实现 一个简单区块链的 Python 实现。 不到 200 行代码。 描述 Pysimplechain实现几乎完全集中在哈希分类帐功能上。 它不包括任何高级功能,如分布式账本或通过工作证明的共识协议。...
  • Python实现一条基于POS算法的区块链

    千次阅读 2018-08-03 18:34:22
    区块链共识算法 代码实战 生成一个Block 创建一个TCP服务器 消息处理器 POS算法实现 测试POS记账方式 添加节点连接到TCPServer 测试POS记账方式 生成区块链 总结 项目地址 参考 区块链共识...

    区块链中的共识算法

    在比特币公链架构解析中,就曾提到过为了实现去中介化的设计,比特币设计了一套共识协议,并通过此协议来保证系统的稳定性和防攻击性。 并且我们知道,截止目前使用最广泛,也是最被大家接受的共识算法,是我们先前介绍过的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.py
    • 打开3个终端
    • 分别输入下面命令
      • nc localhost 9090

    终端如果输出

    Enter token balance: 

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

    测试POS的记账方式

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

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

    生成区块链

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

    总结

    在上面的代码中,我们实现了一个完整的基于POS算法记账的链,当然这里有许多值得扩展与改进的地方。
    - python中多线程开销比较大,可以改成协程的方式
    - TCP建立的长链接是基于TCPServer,是中心化的方式,可以改成P2P对等网络
    - 链的信息不够完整
    - 系统可以拓展成分布式,让其更健壮

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

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

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

    项目地址

    参考

    展开全文
  • Python区块链仿真实现教程及源码

    千次阅读 2020-04-02 09:33:12
    在区块链或数字货币领域,Python并不是...在这个教程里,我们将学习如何使用Python从零开发一个多节点的区块链网络,并基于这个仿真区块链网络,开发一个去中心化的数据分享应用。 相关教程链接:区块链毕业论文 |...
  • 本文仅仅对字符串情况进行了处理,因为发送时候,公钥是公开,而且以字符创形式放入json,广播到区块链的P2P网络上。 需要安装python的ecdsa库,终端输入命令:pip install ecdsa即可。本文基于python...
  • 适用于Ark区块链的Python简单密码术实现。 首席维护者: 贡献指南 开始贡献之前,请花一些时间并查看我们官方,该遵循被广泛接受PEP8 Python样式指南。 :fountain_pen: 在GitHub上分叉存储库。 运行...
  • 一键创建你人工智能项目译者 | kidney,略有删改https://hackernoon.com/learn-blockchains-by-building-one-117428612f46来源 | Python开发相信你和我一样对数字货币崛起感到新奇,并且想知道其背后技术——...
  • 区块链技术渐渐发展起来了,目前来看有着非常广阔前景。虽然在2018年时候就已经接触过区块链了,而且还买了3本书,然而由于工作原因没有时间去系统化地学习。...1、深入浅出区块链python实现):ht...
  • 关注「实验楼」,每天分享一个项目教程 完全搞懂区块链并非...对数字货币的崛起感到新奇的我们,并且想知道其背后的技术——区块链是怎样实现的。但是完全搞懂区块链并非易事,我喜欢在实践中学习,通过写代码来学习技
  • 请查看和以获取有关此项目的信息。 需要Python 3.7+。 通过键入python3确保您默认python版本> = 3.7。 如果您使用是NAT,则子网外对等点启动时可能很难到达您。 您可以在路由器上启用或添加NAT(针对IPv4,...
  • SimpleCoin简介SimpleCion是一个简单的,不太安全,不太完整的用Python制作的区块链实现。这个项目的目标是制作一个有效的区块链货币,尽可能的使它简洁,可以用作教材。这个项目只为了满足个人兴趣。如果你想制作...
  • Python制作的加密货币只是一个非常简单,不安全和不完整的区块链实现。 该项目的目标是制造一种有效的区块链货币,使其尽可能简单并用作教学材料。 这个项目只是为了好玩而已。 如果您想制作自己的加密货币,您...
  • 区块链入门--原来利用...FISCO-BCOS/python-sdk是一个十分优秀的区块链项目,你可以通过这个区块链机制搭建你的区块链项目。 FISCO-BCOS/python-sdk 下面我会用Python-Flask开发作为例子,带着大家实现调用Hell...
  • 我感谢zack-bitcoin致力于开发最小的区块链。 该项目的目标: 可读代码 解释区块链功能的模块化服务 Blockhain是对等方之间的共识协议。 客户端如何处理一切? CLI或GUI可以尝试使用加密货币。 使人们能够学习...
  • 我们还提供了使用Java、golang、C#、python和node.js编程语言编写链下应用程序连接公共城市节点网关访问部署在BSN上链码示例。 问题1:使用本机当前JDK版本(Java11.0.5)会出现部分代码处于低版本而无法...
  • Python 该存储库包含各种项目和独立代码文件,这些文件由文件夹分开,如下所示。 1.机器学习 所有与机器学习相关代码都在...使用python和flask包实现区块链 11. BOT 尝试为instagram和twitter创建BOT 12. GUI
  • 项目是以非常模块化的方式实现的,因此,如果有人想更改组件的功能,则他们可以更改或替换该特定组件,并重复使用其余代码以测试其更改而无需进行很多更改。 提供了其他工具,例如用于创建创世块,创建和签名交易,...
  • 通用的区块链,加密货币和智能合约的实现内容在notebooks/ 。 智能合约建立在加密货币直觉的基础上,而加密货币直觉本身则基于区块链的概念。 该实现已使用Jupyter Notebooks完成。 这样可以直接在此存储库中查看...
  • 项目实现了论文理论和实验设置,目前正在同行评审中。 安装 系统依赖 这些工具需要安装, , , 和可选 。 该项目包括C ++优化代码。 为了编译项目的C ++部分,需要安装以及库。 节点依赖性 克隆git仓库并...
  • 例如:第一章 Python 机器学习入门之pandas使用 提示:写完文章后,目录可以自动生成,如何生成可参考右边帮助文档 文章目录系列文章目录前言开始动手独立部署Truora1.适合以下场景:2.部署服务包括:3.使用...
  • 比特币机器学习分析纸的实现 内容 关于项目 动机 算法 结果 使用技术/框架 关于项目 我自己执行论文,《使用机器学习分析比特币特性如何影响比特币价格》(英文) 使用BlockChain API并在多项式回归上实现...
  • 本套餐包含Spark技术,...本课程使用Python3开发,核心代码150行左右,任何人都能读懂的代码,任何人人都能实现的区块链的功能。 既会Spark大数据计算又会Docker容器云技术,还会区块链的人才,找不到高薪工作都难!
  • 区块链基本原理简介

    千次阅读 2019-02-24 21:12:00
    一个简单的区块链模型,学习区块链的笔记,使用python3 实现了区块链的基本原理。使用Flask 构建 peer to peer 网络,项目地址 简单区块链系统实现之使用方法 简单区块链系统实现之程序开发 比特币简介: 比特币...
  • 我们小学期实践项目,要求用python开发一个基于区块链的商品追溯系统。实现了简单功能,采用flask框架制作,内含详细用户手册,实现功能较简单,可参考学习。

空空如也

空空如也

1 2 3 4
收藏数 74
精华内容 29
关键字:

python实现的区块链项目

python 订阅