可以点进这篇文章,说明你也像我同样对加密货币的兴起十分激动,并想了解加密货币的支撑技术---区块链是如何工做的。 但理解区块链并不那么轻松,至少对我来讲如此。我看了不少相关的视频和教程,却沮丧地发现实例真是太少了。 我喜欢经过实践学习。这种方式使我在代码层面思考问题,并发现关键所在。若是你和我同样,那么在本文结尾你将构建一个功能完备的区块链并对其工做机制有深入的理解。node
写在开始以前。。。
首先,区块链是一系列称做区块(Block)的结构顺序连接而成的不可改变的记录。块中能够包含交易记录、文件或者其余任何你想存储的数据。须要注意的是块与块之间经过hash值连接。若是你不清楚hash是什么,请参考What Are Hash Functions。python
**本文适合哪些人看?**你应该懂得一些基本的Python知识,同时也应该对HTTP请求有所理解,由于咱们的区块链是运行在HTTP协议之上的。git
**我须要准备什么?**请确保Python3.6及以上版本和pip工具已经安装。还须要安装Flask和requests
库。github
pip install Flask==0.12.2 requests==2.18.4
对了,你还须要一个HTTP客户端,好比Postman或者cURL,固然,其余的也能够。算法
最终的代码哪里能够获取?点击这里json
第一步:构建区块链
建立一个新的Python文件,名为blockchain.py
,咱们全部的逻辑都在一个文件完成。flask
表示一个区块链
咱们建立一个BlockChain
类,其构造器会建立两个列表,一个存储区块链,另外一个存储交易。下面是咱们这个类的第一个版本:服务器
class Blockchain(object): def __init__(self): self.chain = [] self.current_transactions = [] def new_block(self): # Creates a new Block and adds it to the chain pass def new_transaction(self): # Adds a new transaction to the list of transactions pass @staticmethod def hash(block): # Hashes a Block pass @property def last_block(self): # Returns the last Block in the chain pass
咱们的BlockChain
类负责管理整个区块链,它会存储交易并为新增区块等操做提供辅助方法。下面,咱们来实现这些方法。网络
区块是什么
每一个区块都有一个索引(index),一个时间戳(timestamp),一系列交易,一个工做量证实(稍后详述)和前置区块的哈希值。下面是单个区块的一个简单实例:并发
block = { 'index': 1, 'timestamp': 1506057125.900785, 'transactions': [ { 'sender': "8527147fe1f5426f9dd545de4b27ee00", 'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f", 'amount': 5, } ], 'proof': 324984774000, 'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" }
显而易见,全部的区块会构成一条链---由于每一个区块都保存了前一区块的hash值。这就是区块链不可篡改的重要缘由:若是攻击者损坏了某一区块,那么后面全部的区块都会做废。 若是你不明白上面的话,请花一些时间理解,由于这是区块链的核心思想。
向区块添加交易
咱们须要一个方法来向区块中添加交易记录,这里命名为new_transaction()
,代码写的十分直白易懂:
class Blockchain(object): ... def new_transaction(self, sender, recipient, amount): """ Creates a new transaction to go into the next mined Block :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
在new_transaction()
方法将交易添加进区块以后,区块索引将会被返回,该区块将可能被开采为链的最新区块,这在以后用户提交交易的时候十分有用。
建立新区块
当BlockChain
类初始化的时候,咱们须要产生一个创世区块(genesis block,即没有前置区块的区块)做为区块链的第一个区块。咱们还须要添加一个proof
字段在创世区块中做为挖矿的结果(或者说本次工做量的证实),咱们将在后文继续讨论挖矿。 除了产生创世区块,咱们还须要完成一些其余辅助方法(new_block()
,new_transaction()
和hash()
):
import hashlib import json from time import time class Blockchain(object): def __init__(self): self.current_transactions = [] self.chain = [] # Create the genesis block self.new_block(previous_hash=1, proof=100) def new_block(self, proof, previous_hash=None): """ Create a new Block in the Blockchain :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]), } # Reset the current list of transactions self.current_transactions = [] self.chain.append(block) return block def new_transaction(self, sender, recipient, amount): """ Creates a new transaction to go into the next mined Block :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 @property def last_block(self): return self.chain[-1] @staticmethod def hash(block): """ Creates a SHA-256 hash of a Block :param block: <dict> Block :return: <str> """ # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes block_string = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_string).hexdigest()
上面的代码十分直白,我还添加了一些注释帮助理解。咱们几乎完成了表示一个区块链的工做。但此时,你应该思考下一个区块是如何产生或者说被开采出来的。
理解工做量证实机制(Proof of Work)
工做量证实(PoW)算法是用来产生或开采区块的一种机制,PoW的目标是找到一个符合要求的数字,从算力的角度来讲这个数字对任何人来讲都很难找到却十分容易验证(是否符合要求)。这就是PoW算法的核心思想。 咱们举一个很是简单的例子来帮助理解: 假定咱们须要找到一个整数y,使得他和整数x的乘积的哈希值以0结尾,即hash(x*y) = ac23dc...0
。若是x=5
那么用Python实现以下:
from hashlib import sha256 x = 5 y = 0 # We don't know what y should be yet... while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0": y += 1 print(f'The solution is y = {y}')
第一个符合要求的数是y=21
,由于:
hash(5 * 21) = 1253e9373e...5e3600155e860
在比特币世界中,PoW算法被称为哈希现金(Hashcash),它和咱们上面的例子没有本质区别。在这算法中,矿工们开始了解决问题的竞赛,优胜者能够产生一个新的区块。一般来讲,难度取决于限制字符的数量。矿工将会由于找到一个合法的解答收到一些比特币做为奖励,整个比特币网络可以很容易验证矿工挖掘的区块是否合法有效。
实现基本的PoW算法
下面来为咱们的区块链实现一个相似的算法,咱们的规则将会和上面的例子十分接近:找到一个数p,使得它与前置区块的哈希值由4个0开头。
import hashlib import json from time import time from uuid import uuid4 class Blockchain(object): ... def proof_of_work(self, last_proof): """ Simple Proof of Work Algorithm: - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p' - p is the previous proof, and p' is the new proof :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): """ Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes? :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请求与区块链进行交互。
第二步:将区块链做为API
咱们将使用Flask,它是一个轻量级的框架,能够很容易将一个网络节点映射为Python函数,这让咱们能够经过HTTP请求与区块链交互。咱们将建立如下方法:
/transactions/new
创建一个新的区块。/mine
告诉服务器开采一个新的区块/chain
返回整个区块链
设置Flask
咱们的每一个服务器将对应区块链网络中的一个单一节点。下面是样板代码:
import hashlib import json from textwrap import dedent from time import time from uuid import uuid4 from flask import Flask class Blockchain(object): ... # Instantiate our Node app = Flask(__name__) # Generate a globally unique address for this node node_identifier = str(uuid4()).replace('-', '') # Instantiate the Blockchain blockchain = Blockchain() @app.route('/mine', methods=['GET']) def mine(): return "We'll mine a new Block" @app.route('/transactions/new', methods=['POST']) def new_transaction(): return "We'll add a new transaction" @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)
下面是简单的解释:
- 15行:实例化节点,关于Flask点击Quick Start
- 18行:为节点建立一个随机名字
- 21行:实例化
BlockChain
类 - 24-26行:建立
/mine
节点,这是一个GET
请求。 - 28-30行:建立
/transactions/new
节点,由于须要发送数据,因此是POST
请求。 - 32-38行:建立
/chain
节点,返回整个区块链 - 40-41行:运行服务器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 uuid4 from flask import Flask, jsonify, request ... @app.route('/transactions/new', methods=['POST']) def new_transaction(): 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.new_transaction(values['sender'], values['recipient'], values['amount']) response = {'message': f'Transaction will be added to Block {index}'} return jsonify(response), 201
挖矿节点
挖矿节点很简单但也很神奇,他须要完成如下任务:
- 计算执行PoW算法
- 经过添加一笔交易奖励矿工1比特币
- 产生新的区块并添加入链
import hashlib import json from time import time from uuid import uuid4 from flask import Flask, jsonify, request ... @app.route('/mine', methods=['GET']) def mine(): # We run the proof of work algorithm to get the next proof... last_block = blockchain.last_block last_proof = last_block['proof'] proof = blockchain.proof_of_work(last_proof) # We must receive a reward for finding the proof. # The sender is "0" to signify that this node has mined a new coin. blockchain.new_transaction( sender="0", recipient=node_identifier, amount=1, ) # Forge the new Block by adding it to the chain 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
类的方法交互。咱们已经完成了这部分,如今能够开始和区块链交互了。
第三步:和区块链交互
你可使用简单古老的cURL或者Postman来和这些网络中的API交互,首先启动服务器:
$ python blockchain.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
让咱们发送一个GET
请求来开采一个区块:
http://localhost:5000/mine
再向http://localhost:5000/transactions/new
发送一个POST
请求,参数是JSON格式的交易数据:
若是不想用Postman,cURL也能够作到:
$ curl -X POST -H "Content-Type: application/json" -d '{ "sender": "d4ee26eee15148ee92c6cd394edd974e", "recipient": "someone-other-address", "amount": 5 }' "http://localhost:5000/transactions/new"
我重启了服务器并开采了两个区块,因此如今总共有3个了,经过http://localhost:5000/chain
节点能够获取整个区块:
{ "chain": [ { "index": 1, "previous_hash": 1, "proof": 100, "timestamp": 1506280650.770839, "transactions": [] }, { "index": 2, "previous_hash": "c099bc...bfb7", "proof": 35293, "timestamp": 1506280664.717925, "transactions": [ { "amount": 1, "recipient": "8bbcb347e0634905b0cac7955bae152b", "sender": "0" } ] }, { "index": 3, "previous_hash": "eff91a...10f2", "proof": 35089, "timestamp": 1506280666.1086972, "transactions": [ { "amount": 1, "recipient": "8bbcb347e0634905b0cac7955bae152b", "sender": "0" } ] } ], "length": 3 }
第四步:共识机制
咱们已经拥有了一个能接收交易的初级区块链,而且可以开采出新的区块。但整个区块链最核心的是去中心化,若是去中心了,咱们又如何保证全部节点对应的是统一区块链呢?这就是共识问题,若是咱们但愿网络中有不止一个节点,就必须实现共识算法。
注册新节点
在实现共识算法以前,咱们须要让节点知道有其余节点加入了网络。网络中的每个节点应该存留其余所有节点的注册表,所以咱们须要一些其余的服务器节点:
/nodes/register
用来从URL中接收一系列节点/nodes/resolve
实现共识算法,并解决冲突以保证节点拥有正确的链 咱们须要修改BlockChain
类的构造器并提供一个方法来注册节点:
... from urllib.parse import urlparse ... class Blockchain(object): def __init__(self): ... self.nodes = set() ... def register_node(self, address): """ Add a new node to the list of nodes :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 requests class Blockchain(object) ... def valid_chain(self, chain): """ Determine if a given blockchain is valid :param chain: <list> A blockchain :return: <bool> True if valid, False if not """ last_block = chain[0] current_index = 1 while current_index < len(chain): block = chain[current_index] print(f'{last_block}') print(f'{block}') print("\n-----------\n") # Check that the hash of the block is correct if block['previous_hash'] != self.hash(last_block): return False # Check that the Proof of Work is correct if not self.valid_proof(last_block['proof'], block['proof']): return False last_block = block current_index += 1 return True def resolve_conflicts(self): """ This is our Consensus Algorithm, it resolves conflicts by replacing our chain with the longest one in the network. :return: <bool> True if our chain was replaced, False if not """ neighbours = self.nodes new_chain = None # We're only looking for chains longer than ours max_length = 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 > max_length and self.valid_chain(chain): max_length = length new_chain = chain # Replace our chain if we discovered a new, valid chain longer than ours if new_chain: self.chain = new_chain return True return False
valid_chain()
经过遍历每一区块并检查proof和hash的正确与否判断链的有效性。resolve_conflicts()
遍历全部节点,并经过上面的方法验证其有效性。若是一个有效链的长度大于当前链,那么当前链将会被替换。如今添加两个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", 400 for 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
如今你能够用不一样的计算机来构建网络中的这些节点,固然也能够用同一机器的不一样端口。例如,将5001
端口也注册进区块链网络:
如今,若是我在第二个节点开采一个新的区块,当我在节点1调用GET /nodes/resolve
的时候,共识算法会保证链被更新到现有网络中的最长链:
如今你能够找一些朋友来和你一块儿测试这个区块链了。
后记
我但愿这篇文章可以激发你的灵感,毕竟我对加密货币十分狂热,我相信他会改变咱们对金融、政府和记录存储的思考方式。
Update:我计划写这个话题的第二部分,我将进一步拓展这个区块链并支持交易验证( Transaction Validation Mechanism),同时也将讨论如何将你的区块链产业化。