使用 Python 搭建简易区块链 Demo

使用 Python 搭建区块链 Demo

引言

区块链是一个不可变且有序的记录链条,以块为单位,他可以包含各种交易信息,文件或者其他数据,区块链使用 Hash 连接在一起。

关于 Hash 的解释:
HashWiki

准备工作

以下是开始前需要准备的内容和工具:

  • Python 3.6+ with Flask & Requests 库
pip install Flask==0.12.2 requests==2.18.4
  • HTTP 相关知识和 HTTP 客户端(Postman or cURL)
    Postman 获取: getpostman

正式开始

创建区块链

# block_chain.py
# ----------------------------------------------------
# 1. Create an initialized blank list, which stores the block chain,
#    and create another one for transaction record.
# 2. Block chain aims to manage linked data(record the transaction &
#    create new block)
# ----------------------------------------------------

class BlockChain(object):
    def __init__(self):
        self.chain = []
        self.current_transactions = []


    def new_block(self):
        # Creates a new block and adds it to the chain
        pass


    def new_transaction(self):
        # Adds a new transaction to the list of transactions
        pass


    @staticmethod
    def hash(block):
        # Hashes a block
        pass


    @property
    def last_block(self):
        # Returns the last block in the chain
        return 0

区块

每个区块都有一些属性,索引,时间戳,事务列表,一个校验和前块散列,例子如下:

# ------------------------------------------------------------
# Seven attributes of block:
# 1. index: the index of block, list height is its another name.
# 2. hash: the ID of block.
# 3. Previous hash: the ID of previous block.
# 4. timestamp: time record
# 5. difficulty: the difficulty factor of block.
# 6. nonce: random value, for creating next block.
# 7. data: contains corresponding info of this block.
# -------------------------------------------------------------


block = {
    'index': 7,
    'timestamp': 15060577746.321594,
    'transactions': [
        {
            'sender': "8527147fe1f5426f9dd545de4b27ee00",
            'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
            'amount': 5,
        }
    ],
    'proof': 324984774000,
    'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}

完善区块链类

实现函数来添加交易到区块,其返回值为交易被添加到区块的索引值的下一个索引值,这个索引值可以被下一个提交交易的用户所使用。注意,每个新块都包含在其内的前一个块的 散列 。 这是至关重要的,因为这是 区块链 不可改变的原因:如果攻击者损坏 区块链 中较早的块,则所有后续块将包含不正确的哈希值。

另外,当我们的 Blockchain 被实例化后,我们需要将创世区块(genesis block 一个没有前导区块的区块)添加进去进去。我们还需要向我们的起源块添加一个 证明,这是挖矿的结果(或工作证明)。 我们稍后会详细讨论挖矿。

补全函数:

"""
block_chain.py
------------------------------------------------------------------
1. Create an initialized blank list, which stores the block chain,
   and create another one for transaction record.
2. Block chain aims to manage linked data(record the transaction &
   create new block).
------------------------------------------------------------------
"""

import hashlib
import json
from time import time


class BlockChain(object):
    def __init__(self):
        self.chain = []
        self.current_transactions = []

        # Create genesis block
        self.new_block(previous_hash=1, proof=100)

    def new_block(self, proof, previous_hash=None):
        """
        Creates a new block and adds it to the chain.
        :param proof: <int> proof created by proof of work(PoW) algorithm
        :param previous_hash: (Optional) <str> hash value of previous block
        :return: <dict> new block
        """

        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,
            'proof': proof,
            'previous_hash': previous_hash or self.hash(self.chain[-1])
        }

        # Reset current transaction record.
        self.current_transactions = []

        self.chain.append(block)
        return block

    def new_transaction(self, sender, recipient, amount):
        """
        Adds a new transaction to the list of transactions.
        :param sender: <str> sender address
        :param recipient: <str> recipient address
        :param amount: <int> amount
        :return: <int> the index of the block which will hold this transaction
        """

        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })

        return self.last_block['index'] + 1

    @staticmethod
    def hash(block):
        """
        Generate SHA-256 value for a block.
        :param block: <dict> block
        :return: <str>
        """

        # Block should be sorted, or we will get chaotic dictionary.
        block_string = json.dump(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

    @property
    def last_block(self):
        # Returns the last block in the chain.
        return self.chain[-1]

工作证明算法(workload proof algorithm)

工作证明算法(PoW),用来证明在区块链上创建或挖掘的新区块。PoW 的目标是计算出一个符合特定条件的数字,此数字对所有人而言是难于计算但易于验证的,这就是工作算法证明的核心思想。

PoW 算法的实现可以基于 Hash 运算,卷积求导或者大质数分解等复杂运算。具体详细内容参照连接:
详解PoW工作原理

以下是一个相似PoW算法,算法的思想是:找到一个数字 x, 使它与前一个区块的 proof 拼接成的字符串的 Hash 值以 0000 开头

    def proof_of_work(self, last_proof):
        """
        A simple PoW algorithm:
        1. Find a number x' such that hash(xx') starts with '0000', where x is
           the previous x'.
        2. x is the previous proof, and x' is the new proof.
        :param last_proof: <int> x
        :return: <int> x'
        """

        proof = 0
        while self.valid_proof(last_proof, proof) is False:
            proof += 1

        return proof

    @staticmethod
    def valid_proof(last_proof, proof):
        """
        Validates the proof, if hash(xx') starts with '0000' or not?
        :param last_proof: <int> previous proof x
        :param proof: current proof x'
        :return: <bool> flag of validation
        """

        guess = f'{last_proof}{proof}'.encode()
        guess_hash = hashlib.sha256(guess).hexdigest()
        return guess_hash[:4] == "0000"

如果想修改算法的复杂度,只需要增加零的个数即可,多一个零,算法的复杂度将会大大增加。

将 BlockChain 作为 API 接口

使用 Python Flask(轻量Web应用框架) 框架将网络请求映射到 Python 函数上。
创建三个接口:

  • /transactions/new 创建一个交易并添加到区块
  • /mine 挖掘新区块
  • /get_chain 返回整个区块链

首先完成返回整个区块链函数:

# block_chain.py
from uuid import uuid4
from flask import Flask, jsonify, request

# ...

# Instantiate node.
app = Flask(__name__)

# Generate a global unique address for the node.
node_id = str(uuid4()).replace('-', '')

# Instantiate the block chain
block_chain = BlockChain()

# Generate API interfaces
@app.route('/mine', methods=['GET'])
def mine():
    return "Starting to mine a new block..."

@app.route('/transactions/new', methods=['POST'])
def new_transaction():
    return "Adding a new transaction..."

@app.route('/get_chain', methods=['GET'])
def get_chain():
    response = {
        'chain': block_chain.chain,
        'length': len(block_chain.chain),
    }
    return jsonify(response), 200

if __name__ == '__main__':
    app.run(host='127.218.0.1', port=8080)

然后是交易接口:

@app.route('/transactions/new', methods=['POST'])
def new_transaction():
    values = request.get_json()

    # Check if the required fields are in the POST'ed data
    required = ['sender', 'recipient', 'amount']
    if not all(k in values for k in required):
        return 'Missing values', 400

    # Create a new transaction
    index = block_chain.new_transaction(values['sender'], values['recipient'], values['amount'])

    response = {'message': f'Transaction will be added to the block {index}'}
    return jsonify(response), 201

最后是挖矿接口:

app.route('/mine', methods=['GET'])
def mine():
    # Run the PoW algorithm to get next proof
    last_block = block_chain.last_block
    last_proof = last_block['proof']
    proof = block_chain.proof_of_work(last_proof)

    # Reward for finding the proof. Sender is "0" suggests that
    # the node has mined a new coin.
    block_chain.new_transaction(
        sender="0",
        recipient=node_id,
        amount=1,
    )

    # Forge the new block by adding the block to the chain
    previous_hash = block_chain.hash(last_block)
    block = block_chain.new_block(proof, previous_hash)

    response = {
        'message': "New block forged",
        'index': block['index'],
        'transactions': block['transactions'],
        'proof': block['proof'],
        'previous_hash': block['previous_hash'],
    }
    return jsonify(response), 200

试运行

使用上文介绍的 postman 和 API 进行交互
首先在 Pycharm 或者 cmd 中启动Server:


Figure 1 Start Server

通过 postman 发送 http://127.0.0.1:5000/mine,得到一个新币:

Figure 2 Get Coin

然后使用 http://127.0.0.1:5000/get_chain获得整个block chain

Figure 3 Get Whole Chain

实际上,挖矿的过程就是所谓的哈希碰撞,如果多个矿工在进行挖矿的过程中,大家都在试图使用算力进行哈希碰撞以寻找到符合 PoW 算法要求的特定哈希值(如上文中所说的以“0000”开头的哈希值)。

以比特币为例,先完成一个页单的满足条件的哈希值的一个矿工,拥有对区块链上注册新的区块的注册权,而这页新的页单需要被所有节点验证并共同持有,这也就是我们所说的为什么 PoW 算法一定要难于计算但易于验证,如果验证过程过于复杂,所有节点上的验证过程需要消耗大量的算力。

下一个部分我们讨论有关节点验证的共识算法,之后我们陆续加入多线程挖矿的竞争型挖矿机制。

共识算法

区块链除了交易的挖矿的特性之外,还有一个很重到的性质,就是分布式,如上所说,我们需要保证所有的节点都持有相同的哈希解的页单,即所谓的区块链记录。

一致性问题就是需要在网络上的多个节点使用共同的一致性算法来达成页单认证的一致性,这个一致性算法就是我们将的共识算法。下面我们就来讨论共识算法的实现。

注册节点

首先,我们需要让网络中的各个节点“互通”,即每个节点都需要保存一份整个网络中其他节点分布的记录。这里我们新增一个接口来实现节点列表的获取。

...
from urllib.parse import urlparse
...
class BlockChain(object):
    def __init__(self):
        ...
        # Node registration
        self.nodes = set()
        ...

        def node_register(self, address):
        """
        Add a new node to the list of nodes.
        :param address: <str> address of node such as "http://192.168.0.10:5000"
        :return: None
        """
        parsed_url = urlparse(address)
        self.nodes.add(parsed_url.netloc)

共识算法

现在我们实现共识算法,在这个算法中,我们认定最长的有效区块链条是最权威的规则,这样可以解决多个节点中链条不统一的问题。

首先我们实现一个函数valid_chain()用来检查一个链上的每个块是否有效。然后实现一个函数resolve_conflicts()一个遍历我们所有邻居节点的方法,下载它们的链并使用上面的方法验证它们。 如果找到一个长度大于我们的有效链条,我们就取代我们的链条。

...
import requests
...

    def valid_chain(self, chain):
        """
        Validate a block chain.
        :param chain: <list> given block chain
        :return: <bool> true if valid, vice versa
        """

        last_block = chain[0]
        cnt = 1

        while len(chain) > cnt:
            block = chain[cnt]
            print(f'{last_block}')
            print(f'{block}')
            print("\n----------------------\n")

            # Check the hash of the block is valid or not
            if not self.valid_proof(last_block['proof'], block['proof']):
                return False

            last_block = block
            cnt += 1

        return True

    def resolve_conflicts(self):
        """
        Resolve conflicts links by replacing chain on each node with
        the longest chain record in the network.
        :return: <bool> true if replacement successful, false if not.
        """

        neighbours = self.nodes
        new_chain = None

        # Record the longest chain length
        len_max = len(self.chain)

        # Grab and validate all the chain from the nodes in network
        for node in neighbours:
            response = requests.get(f'http://{node}/get_chain')

            if response.status_code == 200:
                length = response.json()['length']
                chain = response.json()['chain']

                if length > len_max and self.valid_chain(chain):
                    len_max = length
                    new_chain = chain

        # Replace the chain if new_chain found
        if new_chain:
            self.chain = new_chain
            return True

        return False

接下来在API接口中添加两个接口,一个用来添加相邻节点,另一个用于解决冲突。

@app.route('nodes/register', methods=['POST'])
def register_nodes():
    nodes = request.get_json().get('nodes')

    if nodes is None:
        return "Error: list of nodes is invalid.", 400
    else:
        for node in nodes:
            blockchain.register_node(node)

    response = {
        'message': 'New nodes are add successfully.',
        'all_nodes': list(blockchain.nodes),
    }
    return jsonify(response), 201


@app.route('/nodes/resolve', methods=['GET'])
def consensus():
    replaced = blockchain.resolve_conflicts()

    if replaced:
        response = {
            'message': 'Chain is replaced successfully.',
            'chain': blockchain.chain
        }
    else:
        response = {
            'message': 'Chain is authoritative.',
            'chain': blockchain.chain
        }

    return jsonify(response), 200

测试

使用之前实现的节点注册函数进行节点注册:


register1

register2

运行之后得到结果:


register_result

此时我们的区块链节点数达到了4个,分别是端口号从5000到5002以及5007的节点。注意要在4个terminal中启动不同的节点,以保证每个节点都能进行挖矿和同步通信操作。如图:


fourNodes

接下来我们在不同的节点上进行挖掘,产生不同长度的链条:


n50001

n5002

n5007

如上图,5000节点上的区块链长度为3, 5001上为1(未截图), 5002上为7, 5007上为5。然后我们在端口为5000的节点上调用 GET /nodes/resolve 接口,将此节点上的短链取代为5002端口节点上的长链条,保持不同节点上的区块链的一致性:

n50002

至此,共识算法和同步性处理演示完毕。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容