Python实现区块链(代码+思维导图)附详细注释

根据Tiny熊老师《区块链入门与去中心化应用实战》一课整理

 

思维导图png版:链接:https://pan.baidu.com/s/1VhUyC8MtvLLImP-8vpzdCA

密码:sl6e

思维导图pdf版:链接:https://pan.baidu.com/s/1mMXjt9jwqln2eKA4TwFs9Q

密码:75jj

代码:

# {
#     "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)

 

个人主页:http://www.unconstraint.cn

  • 3
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值