Python创建一个简单的区块链

区块链(Blockchain)是一种分布式账本(listributed ledger),它是一种仅供增加(append-only),内容不可变(immutable)的有序(ordered)链式数据结构,该数据结构由网络中的一系列节点共同维护,并且这些节点之间互不信任。区块链是如今盛行的BitcoinEthereum等数字货币背后的底层技术,本文通过Python从0开始创建一个简单的区块链模型,对于理解区块链基本原理很有帮助。

准备

所需条件: python3.6,pip,flask,requests,cURL

flask和requests可通过如下命令下载:

pip install falsk, requests

创建文件blockchain.py,所有代码均写在此文件中。

开始

代码主要分为两部分,首先是一个Blockchain类,其中包含区块链的属性和特性方法;然后是相应的路由处理方法,以flask服务器作为区块链网络中的一个节点来处理客户端请求对区块链完成相应操作,以及和网络中其它节点进行交互。

相关数据结构
  • block:区块,以dict表示,包含属性索引(index),时间戳(timestamp),交易集合(transactions),工作量证明(proof),上一个区块的hash值(previous_hash)。其中,previous_hash用于链接这些有序区块,并且保证其中内容不被更改。

  • tansaction:交易,dict形式,存放于区块中,包含属性:发出者(sender),接收者(recipient),数额(amount)

Blockchain类的属性
  • chain:区块链中所有区块的list集合,每个元素都是一个block

  • current_transactions: 当前即将加入区块的交易集合,list表示

  • nodes:当前节点的其他相邻节点的set集合

Blockchain类的方法
  • new_block():生成新的区块,接收工作量证明proof作为参数,将block加入chain中,并且清空当前交易集合,返回生成的block。在__init__方法中会自动生成一个创世区块(genesis block),初始proof为100,previous_hash为’1’

  • new_transaction():生成新的交易,接收三个参数:发出者,接受者,数额,并将交易加入到当前交易集合中,返回该交易将会加入的区块的索引

  • hash():生成区块的hash值,接收一个block作为参数,首先利用json.dumps()将block对象转化为json格式,然后利用hashlib.sha256()计算其hash值

  • proof_of_work():工作量证明算法(PoW),通过循环找一个数p,使得与前一个区块的proof拼接起来的字符串的hash值的前4位为’0000’,接收上一个区块的proof作为参数,返回找到的符合要求的proof

  • register_node():注册节点,接收一个URL作为参数,利用urllib.parse.urlparse()解析该地址,获取其中的内容ip:port,并加入到相邻节点集合nodes中

  • resolve_confict(): 共识算法(consensus)解决冲突,即使用网络中最长且有效的链。遍历其他节点的集合nodes(集合中保存了这些节点的ip和port),利用requests.get()通过路由/chain获取到这些节点的链chain。然后先进行长度判断,如果某节点的链长大于当前节点的链长;则对该链进行有效判断,即遍历该链,判断每个区块的previous_hash和proof值是否满足要求。如果该链更长且有效,则用该chain替换掉自身的链,解决冲突。

URL处理函数

用户提交的数据和服务器的响应数据均使用json格式,服务器利用Flask.request.get_json()获取表单中的json数据,利用Flask.jsonify()将response数据转化为json格式返回。

  • mine():挖矿,对应路由为/mine ,请求方法为GET。主要完成任务有:计算工作量证明,通过新建交易给予矿工(自身节点)1个币的奖励,新增一个区块并加入链中。

  • new_transaction():新增交易,对应路由为/transactions/new,请求方法为POST。通过request.get_json()获取用户提交的json格式表单,判断交易是否符合要求,调用类中添加交易的方法。

  • full_chain():查看整条链,对应路由为/chain,请求方法为GET

  • register_nodes(): 注册节点,对应路由为/node/register,方法为POST。接收提交的节点集合,调用register_node()加入这些节点,返回nodes集合

  • consensus():共识,对应路由为/node/resolve,方法为GET。调用resolve_conflict()解决冲突,返回共识之后的链。

全部代码
import hashlib
import json
from time import time
from uuid import uuid4
from flask import Flask, jsonify, request
from urllib.parse import urlparse
import requests
import sys


class Blockchain(object):
    def __init__(self):
        # 当前即将加入区块的交易集合
        self.current_transactions = []
        self.chain = []
        self.nodes = set()
        # create the genesis block
        self.new_block(proof=100, previous_hash='1')

    def new_block(self, proof, previous_hash=None):
        """
        生成新块
        :param proof: <int> The proof given by the PoW algorithm
        :param previous_hash: (Optional) <str> hash of Previous block
        :return: <dict> new block
        """
        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,        # a list
            'proof': proof,
            'previous_hash': previous_hash or self.hash(self.chain[-1]),
        }
        # 当前交易集合在加入区块后清空
        self.current_transactions = []
        self.chain.append(block)
        return block

    def new_transaction(self, sender, recipient, amount):
        """
        生成新的交易信息,将加入下一个待挖的区块中
        :param sender: <str> Address of the Sender
        :param recipient: <str> Address of the Recipient
        :param amount: <int> Amount
        :return: <int> The index of the Block that will hold this transaction
        """
        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })
        return self.last_block['index'] + 1

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

    @staticmethod
    def hash(block):
        """
        生成区块的 SHA-256 hash值
        :param block: <dict> Block
        :return: <str> the hash value
        """
        # we must make sure that the dict is ordered, or we'll have inconsistent hashes
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

    def proof_of_work(self, last_proof):
        """
        工作量证明:
         - 查找一个 p' 使得 hash(pp')以4个0开头
         - p是上一个块的证明, p' 是当前的证明
        :param last_proof: <int>
        :return: <int>
        """
        proof = 0
        while self.valid_proof(last_proof, proof) is False:
            proof += 1
        return proof

    @staticmethod
    def valid_proof(last_proof, proof):
        """
        验证证明:是否hash(last_proof, proof)
        :param proof: <int> previous proof
        :param last_proof: <int> current proof
        :return: <bool> True if correct, Flase if not.
        """
        guess_hash = hashlib.sha256((str(last_proof) + str(proof)).encode()).hexdigest()
        return guess_hash[:4] == '0000'

    def register_node(self, address):
        """
        Add a new node to the list of nodes
        :param address: <str> Address of node.  EG. 'http://192.168.0.5:5000'
        :return: None
        """
        node = urlparse(address).netloc
        self.nodes.add(node)

    def valid_chain(self, chain):
        """
        Determine if a blockchain is valid
        :param chain: <list> a blockchain
        :return: <bool> True if valid, False if not
        """
        for i in range(1, len(chain)):
            block = chain[i]
            previous_block = chain[i-1]
            if self.hash(previous_block) != block['previous_hash']:
                return False
            if not self.valid_proof(previous_block['proof'], block['proof']):
                return False
        return True

    def resolve_conflict(self):
        """
        共识算法解决冲突,使用网络中最长且有效的链
        :param chain: <list> other blockchain
        :return: <bool> True 链被取代,False 链未被取代
        """
        flag = False
        for node in self.nodes:
            r = requests.get('http://{}/chain'.format(node))
            if r.status_code == 200:
                chain = r.json()['chain']
                length = r.json()['length']
                if length > len(self.chain) and self.valid_chain(chain):
                    self.chain = chain
                    flag = True
        return flag


# Instantiate our Node
app = Flask(__name__)


# Generate a globally unique address for this Node
node_identifier = str(uuid4()).replace('-', '')
# Instantiate the Blockchain
blockchain = Blockchain()


@app.route('/mine', methods=['GET'])
def mine():
    # We run the PoW algorithm to get the next proof...
    last_proof = blockchain.last_block['proof']
    proof = blockchain.proof_of_work(last_proof)
    # 给工作量证明的节点提供奖励,
    # 发送者为 '0' 表面是新挖出的币
    blockchain.new_transaction(
        sender='0',
        recipient=node_identifier,
        amount=1,
    )
    block = blockchain.new_block(proof)
    response = {
        'message': 'New Block Forged',
        'index': block['index'],
        'transactions': block['transactions'],
        'proof': block['proof'],
        'previous_hash': block['previous_hash'],
    }
    return jsonify(response), 200


@app.route('/transactions/new', methods=['POST'])
def new_transaction():
    # json.loads(request.get_data())
    values = request.get_json()
    # Check that the required fields are in 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 = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
    response = {'message': 'Transaction will be added to Block {}'.format(index)}
    return jsonify(response), 201


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


@app.route('/node/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 nodes", 400
    for node in nodes:
        blockchain.register_node(node)
    response = {
        'message': 'New nodes have been added',
        'total_nodes': list(blockchain.nodes)
    }
    return jsonify(response), 201


@app.route('/node/resolve', methods=['GET'])
def consensus():
    is_replaced = blockchain.resolve_conflict()
    if is_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


if __name__ == '__main__':
    myport = 5000
    if len(sys.argv) > 1:
        myport = int(sys.argv[1])
    app.run(host='0.0.0.0', port=myport)

测试

  • 在一台机器开启多个终端分别运行源代码,通过监听多个不同的端口来模拟多节点网络。这里模拟包含两个节点的区块链网络。

    python3 blockchain.py 5000         #在终端1运行
    python3 blockchain.py 5001         #在终端2运行
  • 挖矿:新建另一个终端3进行通过curl命令进行操作。对节点1进行一次挖矿操作,此时链中有两个区块。

    curl http://127.0.0.1:5000/mine

    这里写图片描述

  • 发送交易:向节点1发送一个交易。

    curl -X POST -H "Content-Type: application/json" -d '{"sender": "5000", "recipient": "5001", "amount": 100}' "http://127.0.0.1:5000/transactions/new"

    这里写图片描述

  • 查看区块链:先进行一次mine操作,使刚刚发送的交易进入第3个区块,然后查看整个区块链的数据信息,此时该链有3个区块,其中第3个区块包含两条交易。

    curl http://127.0.0.1:5000/mine
    curl http://127.0.0.1:5000/chain

    这里写图片描述

  • 注册节点:向节点2(端口为5001,1个block)发送节点1的地址(端口为5000,3个block)。

    curl -X POST -H "Content-Type: application/json" -d '{"nodes": ["http://127.0.0.1:5000"]}' "http://127.0.0.1:5001/node/register"

    这里写图片描述

  • 共识:使节点2完成与相邻节点的共识,用节点1的链(长度为3)替换节点2的链(长度为1)。

    curl http://127.0.0.1:5001/node/resolve

    这里写图片描述

参考文章:https://learnblockchain.cn/2017/10/27/build_blockchain_by_python/

猜你喜欢

转载自blog.csdn.net/zhayujie5200/article/details/79468117