如何使用Python创建一个简单的区块链

165 阅读13分钟

如何使用Python创建一个简单的区块链

本文提供了一个关于如何使用Python创建一个简单的区块链的分步指南

先决条件

要想跟着学习,你应该具备。

  • 具有Python面向对象编程语言的一些知识
  • 对使用Postman Desktop有基本了解

工具要求

  • Pycharm、Visual Studio Code或Anaconda
  • [Postman Desktop]
  • 一个虚拟环境
  • [Flask]

我们利用Flask来创建一个包含区块链技术的网络应用。

我们还需要Postman来发出请求,与我们的区块链进行互动。GET请求允许我们检索我们区块链的实际状态或挖掘一个区块。

构建架构

让我们继续构建我们的第一个区块链。

我们首先启动我们的IDE并在我们的虚拟环境中安装Flask。

设置好环境后,我们为我们的代码创建一个新文件,并将其命名为blockchain.py

初始化包

blockchain.py 文件中,我们导入以下包,因为它们在构建我们的区块链时是必需的。

import datetime
import json
import hashlib
from flask import Flask, jsonify

我们使用DateTime 库来为每个被创建或开采的区块附加一个时间戳。

hahshlib 将被用来对区块进行散列,JSON 将被用来在散列前对区块进行编码。

jsonify 来自Flask库的数据将被用来返回信息给Postman。

创世区块

为了开始构建我们的区块链,我们创建一个Blockchain 类。__init__ 方法将包括一个名为chain的变量,用于存储区块链中所有区块的列表。

create_blockchain() 方法将允许我们在类的实例化中创建我们的Genesis区块。

create_blockchain() 方法将接受两个默认参数,其中proof 的值为1(1),而previous_hash 的值为0(0)。

这方面的代码显示了对区块链的工作原理有一个背景的重要性。

在区块链中,总是有一个叫做创世区块的第一块,这个区块没有任何previous_hash

我们的__init__ 方法应该是这样的。

class Blockchain:
   def __init__(self):
       self.chain = []
       self.create_blockchain(proof=1, previous_hash='0')

创建_区块链函数

接下来,我们定义一个create_blockchain 方法来扩展Genesis区块。这里唯一的区别是,我们将传入三个参数,分别是selfproof ,和previous_hash

所有的参数都没有默认值。

create_blockchain 函数中,我们包括一个类型为dictionary 的区块变量,将用于定义区块链中的每个区块。

该字典将采取以下键值对。

  • Index:一个索引键将存储区块链的长度。它由__init__ 方法中的链变量表示,其附加值为1(1)。我们将使用这个变量来访问链上的每个区块。

  • Timestamp:时间戳键将取值为:current DateTime ,该区块被创建或开采。

  • Proof:这个键将会得到一个proof 的值,在调用时将会传递给函数。注意,这个变量指的是工作证明。

  • Previous hash:最后,前一个哈希键从函数中获取一个previous_hash ,该值相当于前一个区块的哈希值。

通过添加这些键值对,我们再把这个区块追加到上,并返回区块本身。你的create_blockchain() 函数应该类似于下图所示。

def create_blockchain(self, proof, previous_hash):
   block = {
       'index': len(self.chain) + 1,
       'timestamp': str(datetime.datetime.now()),
       'proof': proof,
       'previous_hash': previous_hash
   }

   self.chain.append(block)
   return block

访问前一个区块

这个过程很简单。我们所要做的就是创建一个方法来获取链上的前一个块。

我们创建一个新的变量并将其命名为last_block ,并传入列表中最后一个区块的值。然后我们返回last_block

你这一步的代码应该是这样的。

def get_previous_block(self):
   last_block = self.chain[-1]
   return last_block

工作证明函数

create_blockchain() 函数中,我们有一个叫做proof的变量。这个变量代表开采一个区块所做的工作证明。

作为区块链的程序员,我们需要创建一个矿工将解决的算法来成功挖掘一个区块。

我们首先创建一个名为proof_of_work() 的新方法,然后传递两个参数,即selfprevious_proof

在该方法中,我们创建一个变量来存储矿工提交的证明。我们将其称为new_proof ,并将其值设置为1(1)。

接下来,我们创建一个控制语句来检查工作证明的状态,默认情况下,它将是False

因此,我们创建一个新的变量,称为check_proof ,并给它分配一个False 的值。在这个阶段,你的这个函数的代码应该是这样的。

def proof_of_work(self, previous_proof):
   # miners proof submitted
   new_proof = 1
   # status of proof of work
   check_proof = False

接下来,我们继续进行需要由矿工解决的算法。我们将算法封装在一个while语句中,因为这部分应该重复进行,直到找到证明。

我们首先创建一个while语句,并将check_proof 的条件设置为False。如果程序还没有检查出工作证明,那么它就必须跑完while循环的主体。

接下来,在while循环中,我们定义要解决的问题/算法,该算法将基于之前成功的证明和矿工提交的新证明。

在程序的这个方面,你可以定义你想让这个问题变得多么复杂。在本教程中,我们将使其简单化,以测试我们的代码是否有效。

继续前进,我们创建了一个新的变量,名为hash_operation ,并赋值为hashlib.sha256(str(the algorithm).encode()).hexdigest()

这就是我们利用SHA256哈希库将问题编码为加密的十六进制数字。

现在,我们需要替换挖掘一个区块的算法。

下面是我的算法,需要解决的是挖掘一个区块的问题。

new_proof ** 2 - previous_proof ** 2

该算法将用户提交的新证明提高到2的幂数,然后从提高到2的幂数的前一个证明的指数中减去它。

检查矿工的问题解决方案

接下来,我们通过检查hash_operation 前4个字符来评估矿工对该问题的解决方案。

对于我们的代码,我们检查前4个字符是否等于零。

如果检查的结果是True ,那么我们已经检查了证明,它是有效的。然后我们可以给它赋值为True

如果结果是False ,我们将new_proof递增1,这让矿工有机会再试一次,然后我们返回新的证明。

你的代码库应该是这样的。

def proof_of_work(self, previous_proof):
   # miners proof submitted
   new_proof = 1
   # status of proof of work
   check_proof = False
   while check_proof is False:
       # problem and algorithm based off the previous proof and new proof
       hash_operation = hashlib.sha256(str(new_proof ** 2 - previous_proof ** 2).encode()).hexdigest()
       # check miners solution to problem, by using miners proof in cryptographic encryption
       # if miners proof results in 4 leading zero's in the hash operation, then:
       if hash_operation[:4] == '0000':
           check_proof = True
       else:
           # if miners solution is wrong, give mine another chance until correct
           new_proof += 1
   return new_proof

为什么我们需要四个前导零来进行哈希运算?

现在,为什么是四(4)个前导零?为什么不是五(5)或六()6或更多。

请注意,我们需要的前导零越多,挖掘一个区块就越困难。在本教程中,我们将其定为4,以确保我们能更快地开采出一个区块。

另外,我们传入的hash_operation 算法必须是非对称的,这样如果操作顺序颠倒了,就不会产生相同的值。例如,a+b等于b+a,但a-b不等于b-a。

最后,在我们的算法中,我们需要同时提交新的证明和现有区块的先前证明,以有效地定义一个算法,供矿工解决。因此,这个过程是连续的,并与现有区块相关联。

生成哈希值

在这一步,我们为整个区块本身生成一个哈希值。我们创建了一个hash() 方法,需要两个参数(selfblock)。

在这个函数中,我们使用JSON转储对区块进行编码,并返回整个区块的加密哈希值。

你的代码应该是这样的。

# generate a hash of an entire block
def hash(self, block):
   encoded_block = json.dumps(block, sort_keys=True).encode()
   return hashlib.sha256(encoded_block).hexdigest()

检查区块链的有效性

在这一步,我们创建一个函数来检查整个区块链是否有效。

这一步对于维护我们区块链的完整性至关重要,以确保我们的区块没有一个是损坏的。

我们首先创建一个is_chain_valid 方法,将selfchain 作为参数。

然后我们检索链上的第一个区块,作为当前区块的前一个区块。

previous_block = chain[0]

接下来,我们为链上的区块设置一个索引值1 ,作为迭代的唯一目的。

block_index =1

下面是这个过程的复杂部分。

我们首先创建一个while循环,如果block_index 等于链的长度,则条件语句为真,如果block_index 小于链的长度则为假。

在我们的while循环中,我们从链中检索出当前的块。

while block_index < len(chain):
block = chain[block_index]

接下来,我们检查当前区块的前一个哈希域是否与前一个区块的哈希域不相似。如果是,则返回True ,否则False

if block["previous_hash"] != self.hash(previous_block):
   return False

接下来,我们从上一个区块得到证明,同时也得到当前区块的证明。这两个值都是我们算法中的哈希操作所需要的。

previous_proof = previous_block['proof']
current_proof = block['proof']

我们进一步通过算法运行证明数据,然后我们检查前四个引导字符是否不等于四个零。

如果散列操作无效,那么我们返回False ,否则,我们将previous_block 值设置为刚刚完成检查的当前区块,并将区块索引递增为1 ,然后返回True作为积极的验证检查。

你的完整代码应该是这样的。

# check if the blockchain is valid
def is_chain_valid(self, chain):
   # get the first block in the chain and it serves as the previous block
   previous_block = chain[0]
   # an index of the blocks in the chain for iteration
   block_index = 1
   while block_index < len(chain):
       # get the current block
       block = chain[block_index]
       # check if the current block link to previous block has is the same as the hash of the previous block
       if block["previous_hash"] != self.hash(previous_block):
           return False

       # get the previous proof from the previous block
       previous_proof = previous_block['proof']

       # get the current proof from the current block
       current_proof = block['proof']

       # run the proof data through the algorithm
       hash_operation = hashlib.sha256(str(current_proof ** 2 - previous_proof ** 2).encode()).hexdigest()
       # check if hash operation is invalid
       if hash_operation[:4] != '0000':
           return False
       # set the previous block to the current block after running validation on current block
       previous_block = block
       block_index += 1
   return True

通过遵循上述步骤,我们已经建立了我们的区块链。然而,我们需要与它互动,使用Flask和Postman来挖掘我们的区块并显示一些细节。

挖掘我们的区块链

为了挖掘我们的区块,我们用Flask创建了一个网络应用程序入口点,如下所示。

app = Flask(__name__)

接下来,我们创建一个区块链类的实例。

blockchain = Blockchain()

现在,我们可以开始挖掘区块了。

挖掘一个新的区块

我们首先创建一个Flask路由mine_block ,其中有一个GET 的方法,然后我们定义视图

@app.route('/mine_block', methods=['GET'])
def mine_block():

mine_block() 视图内,我们获取创建区块所需的数据。这包括以下内容。

  • 上一个区块
  • 前一个区块的证明
  • 工作证明
  • 上一个哈希值

以下数据点的方法在区块链类中有定义。我们需要做的就是把它们连接起来,如下图所示。

# get the data we need to create a block
previous_block = blockchain.get_previous_block()
previous_proof = previous_block['proof']
proof = blockchain.proof_of_work(previous_proof)
previous_hash = blockchain.hash(previous_block)

接下来,我们创建区块并将其存储在区块变量中。

block = blockchain.create_blockchain(proof, previous_hash)

然后,我们返回一个jsonify 响应,其中包含一个message ,索引,时间戳,证明,和之前的哈希值。

response = {'message': 'Block mined!',
           'index': block['index'],
           'timestamp': block['timestamp'],
           'proof': block['proof'],
           'previous_hash': block['previous_hash']}
return jsonify(response), 200

现在,我们有了所有的代码来挖掘一个区块。让我们再往前走一步,编写检索链上所有区块的功能。

检索链

我们首先创建一个新的get_chain 路线,其中有一个GET 方法。然后我们返回一个包含区块链长度和链中区块链的响应。

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

设置应用程序的端口

最后,我们需要运行我们的应用程序,所以我们在现有代码下面添加这行代码。

app.run(host='0.0.0.0', port=5000)

在这个阶段,你应该有完整的区块链代码,如下图所示。

import datetime
import json
import hashlib
from flask import Flask, jsonify


class Blockchain:
   def __init__(self):
       self.chain = []
       self.create_blockchain(proof=1, previous_hash='0')

   def create_blockchain(self, proof, previous_hash):
       block = {
           'index': len(self.chain) + 1,
           'timestamp': str(datetime.datetime.now()),
           'proof': proof,
           'previous_hash': previous_hash
       }

       self.chain.append(block)
       return block

   def get_previous_block(self):
       last_block = self.chain[-1]
       return last_block

   def proof_of_work(self, previous_proof):
       # miners proof submitted
       new_proof = 1
       # status of proof of work
       check_proof = False
       while check_proof is False:
           # problem and algorithm based off the previous proof and new proof
           hash_operation = hashlib.sha256(str(new_proof ** 2 - previous_proof ** 2).encode()).hexdigest()
           # check miners solution to problem, by using miners proof in cryptographic encryption
           # if miners proof results in 4 leading zero's in the hash operation, then:
           if hash_operation[:4] == '0000':
               check_proof = True
           else:
               # if miners solution is wrong, give mine another chance until correct
               new_proof += 1
       return new_proof

   # generate a hash of an entire block
   def hash(self, block):
       encoded_block = json.dumps(block, sort_keys=True).encode()
       return hashlib.sha256(encoded_block).hexdigest()

   # check if the blockchain is valid
   def is_chain_valid(self, chain):
       # get the first block in the chain and it serves as the previous block
       previous_block = chain[0]
       # an index of the blocks in the chain for iteration
       block_index = 1
       while block_index < len(chain):
           # get the current block
           block = chain[block_index]
           # check if the current block link to previous block has is the same as the hash of the previous block
           if block["previous_hash"] != self.hash(previous_block):
               return False

           # get the previous proof from the previous block
           previous_proof = previous_block['proof']

           # get the current proof from the current block
           current_proof = block['proof']

           # run the proof data through the algorithm
           hash_operation = hashlib.sha256(str(current_proof ** 2 - previous_proof ** 2).encode()).hexdigest()
           # check if hash operation is invalid
           if hash_operation[:4] != '0000':
               return False
           # set the previous block to the current block after running validation on current block
           previous_block = block
           block_index += 1
       return True


app = Flask(__name__)

blockchain = Blockchain()


@app.route('/mine_block', methods=['GET'])
def mine_block():
   # get the data we need to create a block
   previous_block = blockchain.get_previous_block()
   previous_proof = previous_block['proof']
   proof = blockchain.proof_of_work(previous_proof)
   previous_hash = blockchain.hash(previous_block)

   block = blockchain.create_blockchain(proof, previous_hash)
   response = {'message': 'Block mined!',
               'index': block['index'],
               'timestamp': block['timestamp'],
               'proof': block['proof'],
               'previous_hash': block['previous_hash']}
   return jsonify(response), 200


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



app.run(host='0.0.0.0', port=5000)

接下来,我们继续运行我们的应用程序。

运行应用程序

有两种方法可以测试运行我们的区块链代码。第一种方法是使用Postman桌面应用程序,而另一种方法是通过网络浏览器。

通过网络浏览器运行

让我们通过网络浏览器运行我们的应用程序。当我们在Pycharm上运行应用程序时,我们会得到一个本地地址,从终端运行我们的应用程序。

Terminal Image

从我的终端来看,URL地址是172.20.10.2:5000。

在浏览器中,我们从挖掘一个区块开始,我们通过调用我们创建的mine_block 路由函数来实现这一点。

Mine Block Image

要调用mine_block 函数,我们在正斜杠后面加上函数名称,它就会运行该区块下的代码指令。

从结果中,我们可以看到该区块的索引,显示该区块被开采的信息,之前的哈希值,工作证明,以及时间戳。

每次我们刷新页面,它都会挖出一个新的区块。现在,让我们通过将mine_block 替换为get_chain 来获得链上的数据。你应该有类似于下面的东西。

Blockchain Image

上述函数按照预期显示了链上的区块和它们的索引。现在,让我们在Postman桌面程序上重复这个过程。

通过Postman运行

为了挖掘一个区块,我们复制mine_block URL并将其粘贴到请求URL栏。在发送请求之前,请确保请求类型是GET

结果应该类似于下面显示的那些。

Postman Mine Block Image

接下来,让我们来获取链上的数据。

Postman BlockChain Image

结论

在这篇文章中,你已经成功建立了你的第一个区块链。你可以挖掘一个区块,也可以查看链上的区块列表。