源代码链接:https://github.com/dvf/blockchain

大家好!这是本人踩在巨人的肩膀上,实现的第一个区块链小程序。在实现代码的过程中,我遇到了很多问题,不过,幸好都解决了。在这里,我会尽可能的将操作步骤写得更详细。接下来,让我们一起走进区块链吧!

一、初识区块链

区块链是由不可变的、有顺序记录的区块组成。他们可以包含交易数据、文件数据或者其他你想要记录的数据。不过最重要的是,这些区块通过哈希表链接在一起。

提到哈希表,当然离不开散列函数。哈希函数只是一个接收输入值的函数,并从该输入创建一个输入值以确定的输入值。对于任何x输入值,只要运行哈希函数,您将始终收到相同的y输出值。这样,每个输入都有一个确定的输出。

二、开发环境及工具

1、Python 3.6以上:下载安装教程请参考 https://www.cnblogs.com/Yanjy-OnlyOne/p/9764143.html

2、开发工具:本人喜欢用PyCharm,下载安装教程请参考如下链接

https://jingyan.baidu.com/article/636f38bb9d8e27d6b84610ba.html

(注意:请在PyCharm中安装好Flask以及Requests库)

3、HTTP客户端:比如 Postman,下载安装教程请参考https://www.cnblogs.com/xiaxiaoxu/p/8858437.html

三、开始创建区块链

在PyCharm中,创建一个新的文件,命名为 blockchain.py。整个项目,我们都只会用到这一个文件。

1、创建Blockchain类

首先,创建一个Blockchain类,在其构造函数中创建了两个空列表,一个用于储存区块链,一个用于储存交易。

在这里,Blockchain参数的作用是管理区块链,可以存储交易信息和添加区块。

​
class Blockchain(object):def __init__(self):self.chain = []self.current_transactions = []def new_block(self):# 创建一个新的区块,并将其加入到链中passdef new_transaction(self):# 向交易列表中添加一个新的交易pass@staticmethoddef hash(block):# Hashes a Block 散列块pass@propertydef last_block(self):# 返回区块链中最新的区块pass

在区块链中,每一个区块包含一个索引、一个时间戳、一个交易列表、一个证明(之后更多)和前一个区块的哈希值。如下所示:

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

由此可见,区块链的原理:每一个区块包含它自己本身的一些变量,以及前一个区块的哈希值。更重要的是,哈希值保证了区块链不可篡改的特性。如果一个区块受到攻击,导致哈希值变了,那么后面的所有区块的哈希值都会为之改变。

然后,我们可以在区块上,添加交易。那我们怎么在区块上添加交易呢?可以使用new_transaction()参数。如下所示:

class Blockchain(object):...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

在区块链创建完成后,我们需要创建一个创世区块(也就是区块链上的第一个区块)。当然,创世区块也需要被证明,这需要通过PoW的挖矿机制。除了在构造函数中创建创世区块,我们还需要用new_block()、new_transaction() 和 hash()参数对其进行完善。代码如下:

import hashlib
import json
from time import timeclass Blockchain(object):def __init__(self):self.current_transactions = []self.chain = []# 创建创世区块self.new_block(previous_hash=1, proof=100)def new_block(self, proof, previous_hash=None):"""生成新块:param proof: <int> The proof given by the Proof of Work 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,'proof': proof,'previous_hash': previous_hash or self.hash(self.chain[-1]),}# 重置当前交易列表self.current_transactions = []self.chain.append(block)return blockdef 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@propertydef last_block(self):return self.chain[-1]@staticmethoddef hash(block):"""生成块的 SHA-256 hash值:param block: <dict> Block:return: <str>"""# 我们必须确保字典是有序的,否则我们会有不一致的哈希值block_string = json.dumps(block, sort_keys=True).encode()return hashlib.sha256(block_string).hexdigest()

接下来,我们来看看挖矿到底是如何进行的?在此之前,我们需要先理解工作量证明(PoW),因为新的区块需要工作量证明算法来构造。工作量证明的目的就是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。

那么,为了方便大家理解,我们举个例子:

某个整数 x 乘以另外一个数 y ,所得结果的哈希值必须是以 0 结尾,即:hash(x * y) = ac23dc...0。所以,我们的目标是找到满足这个条件的一个 y 值。为了方便理解,我们暂定x=5。下面我们就用Python来做这样一个运算:

from hashlib import sha256x = 5
y = 0
while sha256(f'{x * y}'.encode()).hexdigest()[-1] != "0":y += 1
print(f'The solution is y = {y}')

最终,计算结果是 y=21。因此,生成的以 0 结尾的哈希值是:

hash(5* 21) = 1253e9373e781b7500266caa55150e08e210bc8cd8cc70d89985e3600155e860

在比特币中,PoW算法被称为Hashcash,原理跟上面例子差不多。矿工们为了能创建一个新区块,铆足劲儿做着上面的数学题(只有胜出者才能添加区块)。一般而言,证明的难度取决于字符串中搜索的字符数量,先找到正确数字的矿工就能够在每笔交易中获得比特币作为奖励。

下面在我们刚刚创建好的区块链上,来实现一个相似的工作量证明算法。规则与上边那个简单的例子相似,找到一个数字 p ,它和前边一个区块的解决数字进行散列,生成前4位为 0 的哈希值。代码如下:

​
import hashlib
import jsonfrom time import time
from uuid import uuid4class Blockchain(object):...def proof_of_work(self, last_proof):"""简单的工作量证明:- 查找一个 p' 使得 hash(pp') 以4个0开头- p 是上一个块的证明,  p' 是当前的证明:param last_proof: <int>:return: <int>"""proof = 0while self.valid_proof(last_proof, proof) is False:proof += 1return proof@staticmethoddef valid_proof(last_proof, proof):"""验证证明: 是否hash(last_proof, proof)以4个0开头?:param last_proof: <int> Previous Proof:param proof: <int> Current Proof:return: <bool> True if correct, False if not."""guess = f'{last_proof}{proof}'.encode()guess_hash = hashlib.sha256(guess).hexdigest()return guess_hash[:4] == "0000"

我们可以通过修改哈希值前 0 的数量,来调整算法的难度。一般来说,4位已经是足够了。每在哈希值前多加一个0,计算所花费的时间将呈指数倍增加。

到这儿,我们的类基本写好了。下面,我们准备通过 HTTP 请求与其交互。

2、创建API

我们打算使用 Python 的 Flask 框架,它是一个轻型框架,可以很容易实现端点到Python函数的映射。这样,我们就可以使用 HTTP 请求通过网页访问我们的区块链了。

我们用以下三个方法创建:

/transactions/new 为一个区块创建一个新的交易;

/mine 告诉我们的服务器开采一个新的区块;

/chain 返回完整的 Blockchain 类。

接下来,我们来搭建Flask框架。我们的服务器会在区块链网络中形成单个节点。下面来创建一些样板代码:

import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4from flask import Flaskclass Blockchain(object):...# 实例化我们的节点;加载 Flask 框架
app = Flask(__name__)# 为我们的节点创建一个随机名称
node_identifier = str(uuid4()).replace('-', '')# 实例化 Blockchain 类
blockchain = Blockchain()# 创建 /mine 端点,这是一个GET请求
@app.route('/mine', methods=['GET'])
def mine():return "We'll mine a new Block"# 创建 /transactions/new 端点,这是一个 POST 请求,我们将用它来发送数据
@app.route('/transactions/new', methods=['POST'])
def new_transaction():return "We'll add a new transaction"# 创建 /chain 端点,它是用来返回整个 Blockchain 类
@app.route('/chain', methods=['GET'])
def full_chain():response = {'chain': blockchain.chain,'length': len(blockchain.chain),}return jsonify(response), 200# 设置服务器运行端口为 5000
if __name__ == '__main__':app.run(host='0.0.0.0', port=5000)

然后,我们的交易是以什么形式发起请求的?下面我们看看用户发送到服务器的一段请求代码:

{"sender": "my address","recipient": "someone else's address","amount": 5
}

因为我们已经写好了将交易打包到区块上的代码,剩下的部分就简单了。只需要调用这个方法,从而实现添加交易的功能。下面是具体代码实现:

import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4from flask import Flask, jsonify, request...@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'])response = {'message': f'Transaction will be added to Block {index}'}return jsonify(response), 201

然后,我们来理解以下挖矿节点是怎么回事?挖矿节点是整个过程中最有趣的部分,它必须要达到三个目的:

1. 计算工作量证明;

2. 通过打包交易奖励矿工一个币;

3. 通过将新块添加到链中来伪造新块。

import hashlib
import jsonfrom time import time
from uuid import uuid4from flask import Flask, jsonify, request...@app.route('/mine', methods=['GET'])
def mine():# 我们运行工作证明算法来获得下一个证明last_block = blockchain.last_blocklast_proof = last_block['proof']proof = blockchain.proof_of_work(last_proof)# 由于找到了证据,我们会收到一份奖励# sender为“0”,表示此节点已挖掘了一个新货币blockchain.new_transaction(sender="0",recipient=node_identifier,amount=1,)# 将新块添加到链中打造新的区块previous_hash = blockchain.hash(last_block)block = blockchain.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

注意,这里的开采区块的接收者是我们节点的地址。在这里完成的大部分工作只是与Blockchain类中的方法进行交互。下面可以开始与我们的区块链交互啦。

3、实现与 Blockchain 类交互

接下来,我们用Postman 通过网络和刚才生成的 API 进行交互。

首先,启动服务器,也就是运行我们的blockchain.py文件。

然后,在Postman中,通过向以下地址发送请求,我们可以尝试一下挖矿。

下面我们通过向下面链接发送post请求,来创建一个新的交易:http://localhost:5000/transactions/new 。

请求中需要包含我们的交易结构。

完成上面步骤之后,需要重启下服务器。这时候,我挖出了2个区块,获得了3个币的奖励。这里,我们还可以像以下地址发送请求,来对整条链进行检查。

{"chain": [{"index": 1,"previous_hash": "1","proof": 100,"timestamp": 1556419069.1459346,"transactions": []},{"index": 2,"previous_hash": "2ec1df0d062b439bce88c4c7134ae901e38ec89551778c1d7e3f89214bc58f50","proof": 68862,"timestamp": 1556419347.283694,"transactions": [{"amount": 1,"recipient": "9f9769c7ebaa449289c4c53fcb2199ba","sender": "0"}]},{"index": 3,"previous_hash": "b910a925273a6059fde1bb2b3e85f36b78baba18cdfa03d189fb139592c10e88","proof": 109124,"timestamp": 1556419580.4322467,"transactions": [{"amount": 5,"recipient": "someone-other-address","sender": "d4ee26eee15148ee92c6cd394edd974e"},{"amount": 1,"recipient": "9f9769c7ebaa449289c4c53fcb2199ba","sender": "0"}]}],"length": 3
}

4、形成共识

在上面的步骤中,我们已经创建完成了一个简单的区块链,并且能够实现交易、挖矿等基本功能。 不过,区块链上的节点应该是分散的。 如果它们是分散的,我们究竟如何确保它们记录的都是同一条链? 这就叫共识问题。如果我们的网络中需要多个节点,我们必须实现共识算法。

首先,注册新节点。

在我们实现共识算法之前,需要解决一个问题:在同一个网络上,让其中一个节点知道它的相邻节点有哪些。每一个节点需要网络上的其他节点进行注册。因此,我们将需要更多的节点:

1. /nodes/register 接受URL形式的新节点列表。

2. /nodes/resolve 实现我们的共识算法,它可以解决任何争议,保证节点具有正确的链。

下面,我们需要修改Blockchain类的结构,以及找到注册节点实现的方法。

...
from urllib.parse import urlparse
...class Blockchain(object):def __init__(self):...self.nodes = set()...def register_node(self, address):"""向节点列表添加新节点:param address: <str> Address of node. Eg. 'http://192.168.0.5:5000':return: None"""parsed_url = urlparse(address)self.nodes.add(parsed_url.netloc)

注意,set()函数用来保存节点列表。 它是确保添加的新节点具有幂等性的方法,意思是无论我们使用这个方法添加特定节点多少次,它都只会出现一次。

先前提到的,当某个节点与另一个节点的记录不一致时,会「打架」。为了解决这个冲突,我们需要制定一个规则,即最长而有效的链是最有权威性的。换句话说,网络上最长的链就是事实。使用这个算法,我们就可以在我们的网络上达成共识。

...
import requestsclass Blockchain(object)...def valid_chain(self, chain):"""确认指定的区块链是否有效:param chain: <list> A blockchain:return: <bool> True if valid, False if not"""last_block = chain[0]current_index = 1while current_index < len(chain):block = chain[current_index]print(f'{last_block}')print(f'{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 Falselast_block = blockcurrent_index += 1return True# resolve_conflicts()是遍历我们所有相邻节点的方法,会下载它们的链,然后使用上述方法去验证.# 如果找到一个有效的链,其长度大于我们的链,就将我们的链条替换为该链.def resolve_conflicts(self):"""这是我们的共识算法,它解决了冲突通过用网络中最长的链替换我们的链.:return: <bool> True if our chain was replaced, False if not"""neighbours = self.nodesnew_chain = None# 我们只找比我们的链更长的链max_length = len(self.chain)# 抓取并验证网络中所有节点的链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 = lengthnew_chain = chain# 如果我们发现新的有效链条比我们的链条长,请更换链条if new_chain:self.chain = new_chainreturn Truereturn False

下面我们在API中,注册两个节点,一个用于添加相邻节点,另一个用于解决冲突:

@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 nodes", 400for 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('/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

此时,你可以使用不同机器(或使用同一台机器的不同端口)启动不同的节点。 我是使用的同一台机器,在另外一个端口上创建了另一个节点,并将其注册到当前节点。 因此,我有两个节点:http:// localhost:5000 和 http:// localhost:5001。

之后,我重新创建了另一个文件blockchain2.py。此文件与blockchain.py不同的是,端口号改为5001。如下所示:

接下来,我们运行这两个文件。然后,我在第二个节点上挖出了一些新的区块,以确保第二个节点的链条比第一个节点的链条更长。 之后,我在第一个节点上调用 GET / nodes / resolve,使其中链通过共识算法被第二个节点的链条取代:

好了,你的第一个区块链程序就已经创建成功啦!

走进区块链(一):用Python实现第一个区块链小程序相关推荐

  1. 用python做一个数据查询软件_使用Python实现NBA球员数据查询小程序功能

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. 以下文章来源于早起Python ,作者投稿君 一.前言 有时将代码转成带有界面的程序,会极大地方便 ...

  2. python进行数据查询_使用Python实现NBA球员数据查询小程序功能

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. 以下文章来源于早起Python ,作者投稿君 一.前言 有时将代码转成带有界面的程序,会极大地方便 ...

  3. 计算机毕业设计Python+uniapp快递寄取微信小程序(小程序+源码+LW)

    计算机毕业设计Python+uniapp快递寄取微信小程序(小程序+源码+LW) 该项目含有源码.文档.程序.数据库.配套开发软件.软件安装教程 项目运行 环境配置: Pychram社区版+ pyth ...

  4. python实现音乐播放和下载小程序功能

    更多编程教程请到:菜鸟教程 https://www.piaodoo.com/ 友情链接: 高州阳光论坛https://www.hnthzk.com/ 人人影视http://www.sfkyty.com ...

  5. python软件代码示例-用Python写一个模拟qq聊天小程序的代码实例

    Python 超简单的聊天程序 客户端: import socket, sys host = '10.248.27.23' # host = raw_input("Plz imput des ...

  6. Python 练习册,每天一个小程序

    Python 练习册,每天一个小程序 说明: Python 练习册,每天一个小程序.注:将 Python 换成其他语言,大多数题目也适用 不会出现诸如「打印九九乘法表」.「打印水仙花」之类的题目 点此 ...

  7. 能不能用python开发qq_用Python写一个模拟qq聊天小程序的代码实例

    用Python写一个模拟qq聊天小程序的代码实例 发布时间:2020-09-09 07:49:29

  8. python+requests对app和微信小程序进行接口测试

    对于web端和app端的接口测试来说,他们都是通过请求方法,url和传递的body参数进行接口请求,区别web和app的区别就是header请求的不同.不同的地方在于header中的User-Agen ...

  9. python扫描app接口_[分享】python+requests对app和微信小程序进行接口测试

    对于web端和app端的接口测试来说,他们都是通过请求方法,url和传递的body参数进行接口请求,区别web和app的区别就是header请求的不同.不同的地方在于header中的User-Agen ...

  10. python爱心代码_母亲节快到了,用Python给老妈写个祝福小程序吧~

    导 语 看到好多人留言问我咋好久没更新文章了,于是看了下上篇文章的发布日期,好吧确实挺久的,是该上线更一波文章了.想到母亲节快到了,不如就用Python给老妈写个祝福小程序吧~让我们愉快地开始吧~ 相 ...

最新文章

  1. 解决使用Spring Boot、Multipartfile实现上传提示无法找到文件的问题
  2. window下删除无效的区域通知图标
  3. word2013 blog test
  4. c语言交换a b(运算符),关于编程语言:是否有一个复合赋值运算符用于a = b
  5. 判断直线与线段是否相交,相交则输出交点x轴坐标
  6. Python基础入门知识实例【基础算法】
  7. C++基础——关于模板的技巧性基础知识(typename、成员模板、模板的模板参数)
  8. dva处理_dva中使用store管理数据的异步问题
  9. iif能用到mysql中吗_数据库 iif
  10. html5掷骰子的小demo
  11. 国内外知名的待办事项app有哪些
  12. linux mentohust dhcp,Ubuntu下Mentohust的配置
  13. 【Unity3D进阶4-15】Lua热更新
  14. MyBatis Generator报错:Cannot instantiate object of type
  15. 我的世界java刷雪机_我的世界自动造/刷雪机制作图文教程
  16. 【资料分享】地图基础知识
  17. 《观音心经》领悟后的空寂感,如何与现实世界有机结合
  18. Linux-引导过程与服务控制
  19. 数据库api如何获取实时股票数据?
  20. js 数组去重5个常用算法

热门文章

  1. 【零基础】极星量化入门五:实现自动止盈功能
  2. 爆炸,解体,入侵,你想得到的你想不到的大BUG们
  3. 第4章编辑css样式,第4章 使用CSS样式.ppt
  4. java 视频 合并成一个_Java 合并多个MP4视频文件
  5. 关于NOIP—信息学奥赛,多的是你不知道的事!
  6. 家居:联思智能控制生产和自动报价
  7. Python自动化办公如何给视频添加字幕实战案例6
  8. 360财团为何失手Opera:资金或没能如期获准出境
  9. ie6下png阴影问题的解决方案
  10. 微信公众号开发教程[011]-自定义菜单以及个性化菜单