200行Python实现简单的区块链系统

简介

本系统在基本上参考了这篇博客,访问可能需要搭梯子;本文主要是进行了翻译和验证,同时提供了一个基本的思路,肯定有很多不足之处,希望dalao们积极指正,因为是刚开始学习区块链。。。需要少部分的Flask的知识,这是一个轻型的web框架,在这里我们用来模拟结点联网的操作。

本系统的可以实现的功能:

  1. 产生区块,把区块添加到区块链上。
  2. 根据加密算法,验证交易的合法性 。
  3. 验证区块的合法性。
  4. 验证区块链的合法性,选择最长的有效链作为主链。
  5. 进行简单的加密货币交易 。

本系统的不足:
1. 没有签名验证机制
2. 所有的数据都是存储在内存中的,暂时没有存储到硬盘中。
3. 一个区块中存储的交易是线性表存储的,没有使用Merkle树的数据结构,因此必须进行线性的计算验证,而不是对数级别的。
4. 交易机制有缺陷,无法验证交易金额的合理性。假设A可以向B支付任意数值的比特币。
5. 挖矿机制存在缺陷,仅仅是根据哈希数值计算出来的,只要是先计算出首位是4个0的,就可以获得记账权;但是比特币是每10分钟,看谁的计算力更强。
6. 全部都是零确认交易,很容易出现双重支付攻击。
7. 广播算法存在问题,可能会引起循环播放

总体说明:本系统主要是为了学习区块链基本思想的,仅仅是学习参考。后期我会逐步完善,整取做一个更加完善的系统,可以持续关注。

区块的结构

区块的数据用dict格式存放,正好方便与json格式进行交互。一个区块包含以下结构:

  • 在区块链中的编号
  • 时间戳,创建区块的时间
  • 包含的交易,如果区块入链,那么交易加入区块链;否则是待验证的交易。(list)
  • 用于证明工作量的数据。该数据随机生成,与前一个区块的proof进行哈希计算,之后得到算力证明的数据

之后,按照区块链的设计思路,一个区块分为区块头和区块体,区块体包含交易,区块头包含其余的信息。在这里单独介绍一下交易的数据结构。因为本系统仅仅是验证使用的,我们不检验交易的合理性,用户也没有自己的钱包,一笔交易的数据结构只有发送方、接收方和交易的比特币。

区块链的结构

一个区块链包含如下结构:

  • 主链,包含了当前所有经过验证的区块(list)
  • 缓冲链,包含了未经过验证的区块和有关的交易信息(list)
  • 结点集合,因为一个结点包含一条区块链,因此把邻接点的集合也放在主链中。(set)
  • 区块,包含了交易信息

加密算法检验有效性

真正的比特币的签名机制我在这篇博客中有详细介绍。但是,由于本文仅仅介绍基本工作流程,这里仅仅是验证区块的数据没有被更改过和工作量的有效性。本文的具体检验步骤如下:

  1. 工作量的证明。把前一个区块的proof和本区块的proof一起进行哈希,检验开头四个字符是否都是0
  2. 链的有效性证明。把一个区块链从前往后所有的区块执行1的操作,根据哈希值是否匹配来判断链的有效性。因为在创建新区块的时候,就把前一个区块的哈希值添加到了新区块的头部了。而创世区块的前一个区块的哈希值默认1,proof默认100。

工作量证明和挖矿机制

把前一个区块的proof值和当前区块的proof值进行哈希,如果前四个字符是0,表示工作量是有效的。寻找的过程如下:从0开始,逐渐累加,不断与前一个区块的proof进行hash,直到满足条件。挖矿的过程就是一个寻找proof的过程。

代码说明:

全部代码的github地址:https://github.com/StudentErick/BlockChains_in_python/tree/master

头文件

用到的头文件请自行安装,需要Flask框架。

import hashlib
import json
from time import time
from urllib.parse import urlparse
from uuid import uuid4
import requests

from flask import Flask, jsonify, request

区块的数据结构

初始化代码。self.current_transactions是未加入区块链的交易列表;self.chain是合法的区块链;self.node作为邻接结点,使用集合维护;同时,包含一个创世区块。

class BlockChains(object):
    def __init__(self):
        self.current_transactions = []  # 当前区块的交易
        self.chain = []  # 区块链
        self.new_block(previous_hash=1, proof=100)  # 产生创世区块
        self.nodes = set()  # 区块链中的结点,防止重复结点的出现

     ......

创建新节点和添加区块的代码。区块的结构使用字典维护,方便后期与json格式转化;区块中包含一个交易列表、时间戳、前一个区块的哈希值和工作量证明proof。其实这里还有完善的地方,实际情况中,由于网络延时等的原因,交易列表里面会有很多信息没有经过验证,直接清空会有问题;不过,在这里仅仅作为示范,我们假设所有的交易都经过了验证。

class BlockChains(object):

........

   def register_node(self, address):
        '''
        在区块链中添加新的结点
        :param address: <str>结点的地址,格式为'http://192.168.0.5:5000'
        :return: None
        '''
        parsed_url = urlparse(address)
        self.nodes.add(parsed_url.netloc)  # 自动忽略已经存在的结点

    def new_block(self, proof, previous_hash=None):
        '''
        生成新的区块
        :param proof:
        :param previous_hash:  前一个区块的哈希值
        :return: <dict> 新的区块
        '''
        s = str(previous_hash).encode('utf-8')
        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,
            'proof': proof,
            'previous_hash': s or self.hash(self.chain[-1])
        }

        self.current_transactions = []
        self.chain.append(block)
        return block
        @property

    def last_block(self):
        return self.chain[-1]  # 返回最后一个区块

...........

哈希以工作量证明部分的代码。正如之前提到的那样,我们把整个区块的内容进行哈希操作,得到一个256位哈希值的字符串作为标识。工作量证明也像之前提到的那样,前一个区块的proof与当前区块的proof进行哈希操作。区块链的有效性证明也像之前提到的那样。

class BlockChains(object):

..........

    @staticmethod
    def hash(block):

        '''
        生成区块的SHA-256哈希值
        :param block: <Block>当前区块的哈希值
        :return: <str>
        '''
        block_string = json.dumps(block, sort_keys=True)
        return hashlib.sha256(block_string).hexdigest()

    def proof_of_work(self, last_proof):

        '''
        寻找一个p,使得与前一个区块proof哈希后的数值有4个0开头
        :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):
        '''
        验证是否以4个0开头
        :param last_proof: 上一个区块的proof
        :param proof: 当前的proof
        :return: <bool>
        '''
        guess = (str(last_proof) + str(proof)).encode()  # 相当于一个字符串的拼接
        guess_hash = hashlib.sha256(guess).hexdigest()
        return guess_hash[:4] == "0000"

    def valid_chain(self, chain):
        '''
        判断当前的区块链是否是有效的,检查
        :param chain: <list>一个区块链的列表
        :return: <bool>
        '''
        last_block = chain[0]
        current_index = 1

        # 从前往后,逐个检查区块的合理性
        while current_index < len(chain):
            block = chain[current_index]
            print(str(last_block))
            print(str(block))
            print("\n-----------\n")
            # 检查区块的哈希值是否正确
            if block['previous_hash'] != self.hash(last_block):
                return False
            # 检查工作量证明是否是正确的
            if not self.valid_proof(last_block['proof'], block['proof']):
                return False

            last_block = block
            current_index += 1

        return True

..........

解决区块链冲突的问题,系统中可能有多个不同的区块链,结点应该选择当前最长的有效链作为主链。代码如下:

class BlockChains(object):
..........

    def resolve_conclicts(self):
        '''
        检查区块链的冲突,必须是选择网络中最长的区块链
        :return:<bool> True如果当前链被取代,否则False
        '''
        neighbours = self.nodes  # 获取所有的邻接结点的消息
        new_chain = None

        max_length = len(self.chain)

        for node in neighbours:
            s = str(node)
            st = 'http://' + s + '/chain'
            response = requests.get(st)

            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

        # 决定是否替换
        if new_chain:
            self.chain = new_chain
            return True
        return False

网络测试端

以下是网络端,我们在这里进行本地模拟即可。Flask部分可以查看有关文档,仅需要一些基本功能即可。需要postman或者curl进行查看。

app = Flask(__name__)  # 初始化结点
node_identifier = str(uuid4()).replace('-', '')  # 为每一个结点生成唯一的编号
blockchain = BlockChains()  # 初始化区块链


@app.route('/mine', methods=['GET'])
def mine():
    last_block = blockchain.last_block
    last_proof = 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():
    values = request.get_json()
    required = ['sender', 'recipient', 'amount']
    if not all(k in values for k in required):
        return 'Missing values', 400

    index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
    s = str(index)
    response = {'message': 'Transaction will be added to Block' + s}
    return jsonify(response), 201


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


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

测试结果

我是用了postman客户端,大家根据需要自己调整即可。
程序运行:

挖矿:

查看区块链:

进行交易:

猜你喜欢

转载自blog.csdn.net/qq_35976351/article/details/80560682