python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)

参考链接:https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
参考链接:https://xiaozhuanlan.com/topic/1547608293

1区块链数据结构(BlockChain.py)

一个区块主要结构如下:

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

具体说也就是当前区块的索引。
创建区块时间戳
区块里面包含的交易
知识证明
前一个区块的hash值

1.1 区块链初始化:

    def __init__(self):
        self.chain = []
        self.currentTransaction = []

        #Create the genesis block
        self.newBlock(proof=100, previousHash=1)

有一个区块链的数组
以及需要区块链里面的交易的数组
一个一个创世区块

1.2. 创建新区块

    def newBlock(self, proof, previousHash = None):
        # Creates a new Block and adds it to the chain
        """
        生成新块
        :param proof:  The proof given by the Proof of Work algorithm
        :param previous_hash: (Optional)  Hash of previous Block
        :return:  New Block
        """
        block = {
            'index' : len(self.chain) + 1,
            'timestamp' : time(),
            'transactions' : self.currentTransaction,
            'proof' : proof,
            'previousHash' : previousHash or self.hash(self.chain[-1])
        }

        # Reset the current list of transactions
        self.currentTransaction = []

        self.chain.append(block)
        return block

前面的交易需要加入到新生成的区块中
因此新生成的区块包含前面的交易,生成之后,当前交易数组清空,最后返回当前区块

1.3. 创建新的交易

    def newTransaction(self, sender, recipient, amount):
        # Adds a new transaction to the list of transactions
        """
        生成新交易信息,信息将加入到下一个待挖的区块中
        :param sender:  Address of the Sender
        :param recipient:  Address of the Recipient
        :param amount:  Amount
        :return:  The index of the Block that will hold this transaction
        """
        self.currentTransaction.append({
            'sender' : sender,
            'recipient' : recipient,
            'amount' : amount,
        })

        #下一个待挖的区块中
        return self.lastBlock['index'] + 1

交易有三个属性

  1. 发送方
  2. 接收方
  3. 数量

返回值的意思是,需要将交易记录在下一个区块中

1.4. 区块hash

    @staticmethod
    def hash(block):
        # Hashes a Block
        """
        生成块的 SHA-256 hash值
        :param block:  Block
        :return: 
        转化为json编码格式之后hash,最后以16进制的形式输出
        """
        # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
        blockString = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(blockString).hexdigest()

使用python装饰器语法糖将hash方法设置为静态方法
将block的字符串通过SHA-256生成hash值,然后转变为16进制的数字返回

1.5. get最后区块方法

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

1.6. 工作量证明

新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。

为了方便理解,举个例子:

假设一个整数 x 乘以另一个整数 y 的积的 Hash 值必须以 0 结尾,即 hash(x * y) = ac23dc…0。设变量 x = 5,求 y 的值?

让我们来实现一个相似PoW算法,规则是:寻找一个数 p,使得它与前一个区块的 proof 拼接成的字符串的 Hash 值以 4 个零开头。

    @staticmethod
    def validProof(lastProof, proof):
        """
        验证证明: 是否hash(last_proof, proof)以4个0开头?
        :param last_proof:  Previous Proof
        :param proof:  Current Proof
        :return:  True if correct, False if not.
        """
        guess = f'{lastProof}{proof}'.encode()
        guessHash = hashlib.sha256(guess).hexdigest()
        return guessHash[:4] == '0000'

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

        return proof

逻辑是:当一台机器算出新的proof值之后,才能够创建新的区块


2.使用Flask框架来简历区块服务器(FlaskServer.py)

2.1. 准备工作

import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4

from flask import Flask, jsonify, request
from BlockChain import Blockchain

# Instantiate our Node
app = Flask(__name__)

# Generate a globally unique address for this node
# 基于随机数生成唯一的id
nodeIdentifier = str(uuid4()).replace('-', '')

# Instantiate the BlockChain
blockchain = Blockchain()

2.2. 挖矿实现

#创建/mine GET接口
@app.route('/mine', methods=['GET'])
def mine():
    # 1.计算工作量证明PoW
    # 2.通过新增一个交易授予矿工(自己)一个币
    # 3.构造新区块并且添加到链中
    # We run the proof of work algorithm to get the next proof...
    lastBlock = blockchain.lastBlock
    lastProof = lastBlock['proof']
    proof = blockchain.proofOfWork(lastProof)

    # 给工作量证明的节点提供奖励.
    # 发送者为 "0" 表明是新挖出的币
    blockchain.newTransaction(
        sender='0',
        recipient=nodeIdentifier,
        amount=1
    )

    #加入到链中
    block = blockchain.newBlock(proof)

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

先获取到上一个区块的工作量证明,然后算出新的工作量证明
区块的第一个交易为:
自己收到的一个比特币的奖励,假设发送方为:‘0’
如果成功,那么返回200ok,并且返回新区块的json

2.3. 新的交易

交易是放在区块上的,并且会放在下一个区块上,随着下一个区块的生成而被记录。

#创建/transactions/new POST接口,可以给接口发送交易数据
@app.route('/transactions/new', methods=['POST'])
def newTransaction():
    values = request.get_json()

    # Check that 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 = blockchain.newTransaction(values['sender'], values['recipient'], values['amount'])

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

首先获得post请求的json值
然后解析该json,如果没有sender…这些数据返回400,表示客户端错误
之后产生新的交易,返回新的交易将要放在的区块的索引。

2.4. 打印整个区块

#创建/chain接口, 返回整个区块链。
@app.route('/chain', methods=['GET'])
def fullChain():
    response = {
        'chain' : blockchain.chain,
        'length' : len(blockchain.chain),
    }
    return jsonify(response), 200

2.5. 运行整个服务

if __name__ == '__main__':
    #服务运行在端口5000上.
    app.run(host='127.0.0.1', port=5000)

3.区块链测试

使用postman进行测试,可以自行下载
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第1张图片

3.1 运行

使用本地的IDE可以直接运行
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第2张图片
需要注意的是,当更改代码之后,需要重新运行
运行之后,使用get请求: http://localhost:5000/chain
可以看到:
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第3张图片
只有一个区块(创世区块),里面是没有交易的。

3.2 创建交易

使用post请求:http://localhost:5000/transactions/new
post请求json格式有很多种方法
一种是直接使用代码的形式:
表明交易的发送方,接收方,交易的金额
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第4张图片
需要注意的是:一定要转化为json格式
在这里插入图片描述
还可以在headers里面自行添加:
在这里插入图片描述
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第5张图片
可以看到交易成功,返回201,表示更新了服务器数据
并且说这个交易即将被更新到区块2上面

3.3 创建区块

发送get请求到:http://localhost:5000/minepython从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第6张图片
可以看到,区块的第一个交易是创建这个区块的时候挖矿得到的金钱(比特币)
第二个交易就是刚我们使用post请求产生的交易
这个时候我们再查看整个区块:
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第7张图片
可以看到所有的交易都被保存下来了。
当2号区块创建之后,之后所有的交易就会保存在将来被挖出来的第三个区块上面啦


4 实现分布式共识算法

4.1 验证区块链有效性

为了验证区块链的有效性,从两个方面:

  1. 是否被篡改,也就是验证hash值
  2. 工作量证明对不对
    def validChain(self, chain):
        """
        Determine if a given blockchain is valid
        :param chain:  A blockchain
        :return:  True if valid, False if not
        """
        previousBlock = chain[0]
        currentIndex = 1

        while currentIndex < len(chain):
            block = chain[currentIndex]
            print(f'{previousBlock}')
            print(f'{block}')
            print("\n-----------\n")
            # Check that the hash of the block is correct
            if block['previousHash'] != self.hash(previousBlock):
                return False

            # Check that the Proof of Work is correct
            if not self.validProof(previousBlock['proof'], block['proof']):
                return False

            previousBlock = block
            currentIndex += 1

        return True

4.2 解决冲突

冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。

    def resolveConflicts(self):
        """
        共识算法解决冲突
        使用网络中最长的链.
        :return:  True 如果链被取代, 否则为False
        """
        neighbours = self.nodes
        newChain = None

        # We're only looking for chains longer than ours
        maxLength = len(self.chain)

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

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

                # Check if the length is longer and the chain is valid
                if length > maxLength and self.validChain(chain):
                    maxLength = length
                    newChain = chain

        if newChain:
            self.chain = newChain
            return True

        return False

添加节点解决冲突的路由:

@app.route('/nodes/resolve', methods=['GET'])
def consensus():
    replaced = blockchain.resolveConflicts()
    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), 2

4.3 注册节点

首先对节点进行注册。注册的代码如下:

    def __init__(self):
        self.chain = []
        self.currentTransaction = []
        self.nodes = set()

        #Create the genesis block
        self.newBlock(proof=100, previousHash=1)

    def registerNode(self, address):
        """
        Add a new node to the list of nodes
        :param address:  Address of node. Eg. 'http://192.168.0.5:5000'
        :return: None
        """
        parsedUrl = urlparse(address)
        self.nodes.add(parsedUrl.netloc)

添加节点注册的路由:

@app.route('/nodes/register', methods=['POST'])
def registerNodes():
    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.registerNode(node)
    response = {
        'message': 'New nodes have been added',
        'totalNodes': list(blockchain.nodes),
    }
    return jsonify(response), 201

4.4 测试

使用不同的端口号运行不同的区块链
有一个长链5001
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第8张图片
有一个短链5000
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第9张图片
短链注册:
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第10张图片
解决冲突:
长链没有变化:
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第11张图片
短链被替换:
python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)_第12张图片

你可能感兴趣的:(区块链)