27

从0到1 手把手教你建一个区块链

 4 years ago
source link: http://blockchain.51cto.com/art/201910/605082.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从0到1 手把手教你建一个区块链

近期的区块链重回热点,如果你想深入了解区块链,那就来看一下本文,手把手教你构建一个自己的区块链。

作者:Captain编译来源:51CTO|2019-10-29 15:46

【51CTO.com快译】近期的区块链重回热点,如果你想深入了解区块链,那就来看一下本文,手把手教你构建一个自己的区块链。

弄懂区块链的最快方法-亲自构建一个

看到这篇文章,说明您也是对加密货币的兴起感兴趣,想知道区块链是如何工作的和其背后运行的技术原理。

但是想要搞懂区块链并不容易。我在众多的视频中苦苦钻研,跟随着漏洞百出的教程,经历着因区块链相关案例太少而产生的挫败感。

我喜欢从行动中学习。它迫使我从代码层面处理问题,从而解决问题。如果您和我一样做,那么在本指南的最后,您将拥有一个运行正常的区块链,并对它们的工作原理有深入的了解。

上手准备

请记住,区块链是一个不可变的、连续的记录链,称为块。它们可以包含事务、文件或您喜欢的任何数据。但是重要的是,它们通过使用哈希而被链接在一起。

如果您不确定什么是哈希值,请参考这里。

教程面向的人群?

可以轻松地阅读和编写一些基本的Python,并且对HTTP请求的工作方式有所了解,因为本文将通过HTTP与区块链进行交流。

需要准备什么?

确保已安装 Python 3.6 +(以及pip)。您还需要安装Flask和很棒的Requests库:

  1. pip install Flask==0.12.2 requests==2.18.4 

您还需要HTTP客户端,例如Postman或cURL。

源代码可在此处获得。

步骤1:构建一个区块链

打开你最喜欢的文本编辑器或IDE,我个人喜欢 PyCharm。创建一个名为blockchain.py的新文件。我们将只使用一个文件,但是如果您有困惑了,可以随时参考源代码。

展示区块链

我们将创建一个Blockchain class,它的构造函数会创建一个初始的空列表(用于存储我们的区块链),另一个用于存储事务。这是脚本:

  1. class Blockchain(object):  
  2. def __init__(self):  
  3. self.chain = []  
  4. self.current_transactions = []  
  5. def new_block(self):  
  6. # Creates a new Block and adds it to the chain  
  7. def new_transaction(self):  
  8. # Adds a new transaction to the list of transactions  
  9. @staticmethod  
  10. def hash(block):  
  11. # Hashes a Block  
  12. @property  
  13. def last_block(self):  
  14. # Returns the last Block in the chain  

Blockchain class 的demo

我们的Blockchain class负责管理链。它将存储事物,并具有一些将新块添加到链中的辅助方法。让我们来尝试一些新的方法吧。

Block像什么?

每个Block都有以下的内容:

  • 一个索引,
  • 一个时间戳(Unix时间),
  • 一个交易列表,
  • 一个证明(稍后会有更多说明)
  • 前一个区块的哈希值。

单个区块示例:

  1. block = {  
  2. 'index': 1,  
  3. 'timestamp': 1506057125.900785,  
  4. 'transactions': [  
  5. 'sender': "8527147fe1f5426f9dd545de4b27ee00",  
  6. 'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",  
  7. 'amount': 5,  
  8. 'proof': 324984774000,  
  9. 'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"  

在这一点上,链的概念应该很明显--每个新区块本身都包含前一个区块的哈希值。这是至关重要的,因为这给予了区块链的不可篡改性:如果攻击者破坏了链中较早的区块,则后续所有的区块都将包含不正确的哈希值。

不知道你能理解嘛,请多花些时间去理解它—这是区块链的核心思想。

添加事物到区块中

我们需要一种将事物添加到区块的方法。我们的new_transaction()方法有效,而且很简单:

  1. class Blockchain(object):  
  2. def new_transaction(self, sender, recipient, amount):  
  3. Creates a new transaction to go into the next mined Block  
  4. :param sender: Address of the Sender  
  5. :param recipient: Address of the Recipient  
  6. :param amount: Amount  
  7. :return: The index of the Block that will hold this transaction  
  8. self.current_transactions.append({  
  9. 'sender': sender,  
  10. 'recipient': recipient,  
  11. 'amount': amount,  
  12. return self.last_block['index'] + 1  

在new_transaction()添加一个新的交易到列表中,它将返回到交易将被添加进去、即将被开采的区块的索引。这对之后提交交易的用户是很有用处的。

创建新区块

当我们Blockchain被实例化,我们需要用一个genesis块来播种它——一个没有前处理的块。还需要向我们的创世区块添加一个“证明”,这是挖掘(或工作证明)的结果。稍后我们将详细讨论挖矿。

除了在构造函数中创建genesis块,我们还将充实new_block()、new_transaction()和hash()的方法:

  1. import hashlib  
  2. import json  
  3. from time import time  
  4. class Blockchain(object):  
  5. def __init__(self):  
  6. self.current_transactions = []  
  7. self.chain = []  
  8. # Create the genesis block  
  9. self.new_block(previous_hash=1, proof=100)  
  10. def new_block(self, proof, previous_hash=None):  
  11. Create a new Block in the Blockchain  
  12. :param proof: The proof given by the Proof of Work algorithm  
  13. :param previous_hash: (Optional) Hash of previous Block  
  14. :return: New Block  
  15. block = {  
  16. 'index': len(self.chain) + 1,  
  17. 'timestamp': time(),  
  18. 'transactions': self.current_transactions,  
  19. 'proof': proof,  
  20. 'previous_hash': previous_hash or self.hash(self.chain[-1]),  
  21. # Reset the current list of transactions  
  22. self.current_transactions = []  
  23. self.chain.append(block)  
  24. return block  
  25. def new_transaction(self, sender, recipient, amount): 
  26. Creates a new transaction to go into the next mined Block  
  27. :param sender: Address of the Sender  
  28. :param recipient: Address of the Recipient  
  29. :param amount: Amount  
  30. :return: The index of the Block that will hold this transaction  
  31. self.current_transactions.append({  
  32. 'sender': sender,  
  33. 'recipient': recipient,  
  34. 'amount': amount,  
  35. return self.last_block['index'] + 1  
  36. @property  
  37. def last_block(self):  
  38. return self.chain[-1]  
  39. @staticmethod  
  40. def hash(block):  
  41. Creates a SHA-256 hash of a Block  
  42. :param block: Block  
  43. :return:  
  44. # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes  
  45. block_string = json.dumps(block, sort_keys=True).encode()  
  46. return hashlib.sha256(block_string).hexdigest()  

上面的内容应该很简单—我添加了一些注释和文档字符串来帮助保持清楚明了。对区块链的表示几乎完成了。但此时,您一定想知道如何创建、锻造或挖掘新的区块。

了解工作量证明

工作算法(PoW)是在区块链上创建或挖掘新区块的方式。PoW的目标是发现可以解决问题的数字。从数字上来说,很难找到该数字,但是很容易被网络上的任何人进行验证。这是工作证明的核心思想。

我们将看一个非常简单的示例来帮助理解。

让我们决定某个整数X乘以另一个Y的哈希必须以0结尾。因此,对于这个简化的示例,让我们修复。在Python中实现:xy0hash(x * y) = ac23dc...0x = 5

  1. from hashlib import sha256  
  2. y = 0 # We don't know what y should be yet...  
  3. while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":  
  4. print(f'The solution is y = {y}')  

解是y = 21。因为,产生的哈希值以0结尾:

  1. hash(5 * 21)= 1253e9373e ... 5e3600155e860 

在比特币中,工作证明算法称为Hashcash。而且与我们上面的示例并没有太大不同。这是矿工为了创建一个新的块而竞相求解的算法。通常,难度由字符串中搜索的字符数决定。然后,通过在交易中获得硬币,矿工将借此获得奖励。

网络能够轻松验证他们的解决方案。

实施基本的工作证明

让我们为区块链实现类似的算法。我们的规则将类似于上面的示例:

找出一个数字 p ,当该数字与上一个块的解决方案进行哈希运算时,将产生一个带有4个前导4个0的哈希。

  1. import hashlib  
  2. import json  
  3. from time import time  
  4. from uuid import uuid4  
  5. class Blockchain(object):  
  6. def proof_of_work(self, last_proof):  
  7. Simple Proof of Work Algorithm:  
  8. - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'  
  9. - p is the previous proof, and p' is the new proof  
  10. :param last_proof:  
  11. :return:  
  12. proof = 0  
  13. while self.valid_proof(last_proof, proof) is False:  
  14. proof += 1  
  15. return proof  
  16. @staticmethod  
  17. def valid_proof(last_proof, proof):  
  18. Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes?  
  19. :param last_proof: Previous Proof  
  20. :param proof: Current Proof  
  21. :return: True if correct, False if not.  
  22. guess = f'{last_proof}{proof}'.encode()  
  23. guess_hash = hashlib.sha256(guess).hexdigest()  
  24. return guess_hash[:4] == "0000"  

要调整算法的难度,我们可以修改前导零的数量。但是4就足够了。您会发现,添加单个前导零会极大地缩短寻找解决方案所需的时间。

我们的类快要完成了,我们已经准备好开始使用HTTP请求与其进行交互。

步骤2:我们的区块链作为API

我们将使用Python Flask框架。这是一个微框架,可轻松将端点映射到Python函数。这使我们可以使用HTTP请求通过web与区块链进行通信。

我们将创建三种方法:

  • /transactions/new 创建一个新的交易块。
  • /mine 告诉我们的服务器挖掘一个新块。
  • /chain 返回完整的区块链。

设置Flask

我们的“服务器”将在我们的区块链网络中形成单个节点。让我们创建一个demo:

  1. import hashlib  
  2. import json  
  3. from textwrap import dedent  
  4. from time import time  
  5. from uuid import uuid4  
  6. from flask import Flask  
  7. class Blockchain(object):  
  8. # Instantiate our Node  
  9. app = Flask(__name__)  
  10. # Generate a globally unique address for this node  
  11. node_identifier = str(uuid4()).replace('-', '')  
  12. # Instantiate the Blockchain  
  13. blockchain = Blockchain()  
  14. @app.route('/mine', methods=['GET'])  
  15. def mine():  
  16. return "We'll mine a new Block"  
  17. @app.route('/transactions/new', methods=['POST'])  
  18. def new_transaction():  
  19. return "We'll add a new transaction"  
  20. @app.route('/chain', methods=['GET'])  
  21. def full_chain():  
  22. response = {  
  23. 'chain': blockchain.chain,  
  24. 'length': len(blockchain.chain),  
  25. return jsonify(response), 200  
  26. if __name__ == '__main__':  
  27. app.run(host='0.0.0.0', port=5000)  

简要说明:

  • 第15行:实例化我们的节点;Flask更多信息
  • 第18行:为节点创建一个随机名称。
  • 第21行:实例化Blockchain类。
  • 第24–26行:创建/mine端点,这是一个GET请求。
  • 第28–30行:创建/transactions/new端点,这是一个POST请求,因为我们将向它发送数据。
  • 第32–38行:创建/chain端点,该端点返回完整的区块链。
  • 40-41行:在端口5000上运行服务器。

这就是交易请求的样子。这是用户发送到服务器的内容:

  1. "sender": "my address",  
  2. "recipient": "someone else's address",  
  3. "amount": 5  

由于我们已经有了用于将事务添加到块中的类方法,因此其余操作很简单。让我们编写添加事务的函数:

  1. import hashlib  
  2. import json  
  3. from textwrap import dedent  
  4. from time import time  
  5. from uuid import uuid4  
  6. from flask import Flask, jsonify, request  
  7. @app.route('/transactions/new', methods=['POST'])  
  8. def new_transaction():  
  9. values = request.get_json()  
  10. # Check that the required fields are in the POST'ed data  
  11. required = ['sender', 'recipient', 'amount']  
  12. if not all(k in values for k in required):  
  13. return 'Missing values', 400  
  14. # Create a new Transaction  
  15. index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])  
  16. response = {'message': f'Transaction will be added to Block {index}'}  
  17. return jsonify(response), 201  

创建交易的方法

挖矿端点

我们的挖矿终点是奇迹发生的地方,这很容易上手。

它必须做三件事:

  1. 计算工作量证明
  2. 奖励矿商(我们)通过增加一个交易给予我们1枚硬币
  3. 将新的区块上链 
  1. import hashlib  
  2. import json  
  3. from time import time  
  4. from uuid import uuid4  
  5. from flask import Flask, jsonify, request  
  6. @app.route('/mine', methods=['GET'])  
  7. def mine():  
  8. # We run the proof of work algorithm to get the next proof...  
  9. last_block = blockchain.last_block  
  10. last_proof = last_block['proof']  
  11. proof = blockchain.proof_of_work(last_proof)  
  12. # We must receive a reward for finding the proof.  
  13. # The sender is "0" to signify that this node has mined a new coin.  
  14. blockchain.new_transaction(  
  15. sender="0",  
  16. recipient=node_identifier,  
  17. amount=1,  
  18. # Forge the new Block by adding it to the chain  
  19. previous_hash = blockchain.hash(last_block)  
  20. block = blockchain.new_block(proof, previous_hash)  
  21. response = {  
  22. 'message': "New Block Forged",  
  23. 'index': block['index'],  
  24. 'transactions': block['transactions'],  
  25. 'proof': block['proof'],  
  26. 'previous_hash': block['previous_hash'],  
  27. return jsonify(response), 200  

注意,已开采区块的接收者是我们节点的地址。而且,我们在这里所做的大部分工作只是与Blockchain类上的方法进行交互。至此,我们已经完成,可以开始与区块链进行交互了。

步骤3:与区块链交互

您可以使用普通的旧cURL或Postman通过网络与我们的API进行交互。

启动服务器:

  1. $ python blockchain.py 
  2. * Running on http://127.0.0.1:5000/ (按CTRL + C退出)  

让我们尝试通过向http://localhost:5000/mine:发出GET请求来挖掘一个块:

f263c0d678477772e27c800361073023.png-wh_651x-s_726426998.png

使用邮递员发出GET请求让我们创建一个新的事务,通过发送POST请求到http://localhost:5000/transactions/new,其主体包含我们的事务结构:

cf9debd61bb64f6e34072e30bd42cb53.png

使用邮递员发出POST请求

如果您不使用Postman,可以使用cURL发出等效请求:http://localhost:5000/chain:

  1. "chain": [  
  2. "index": 1,  
  3. "previous_hash": 1,  
  4. "proof": 100,  
  5. "timestamp": 1506280650.770839,  
  6. "transactions": []  
  7. "index": 2,  
  8. "previous_hash": "c099bc...bfb7",  
  9. "proof": 35293,  
  10. "timestamp": 1506280664.717925,  
  11. "transactions": [  
  12. "amount": 1,  
  13. "recipient": "8bbcb347e0634905b0cac7955bae152b",  
  14. "sender": "0"  
  15. "index": 3,  
  16. "previous_hash": "eff91a...10f2",  
  17. "proof": 35089,  
  18. "timestamp": 1506280666.1086972,  
  19. "transactions": [  
  20. "amount": 1,  
  21. "recipient": "8bbcb347e0634905b0cac7955bae152b",  
  22. "sender": "0"  
  23. "length": 3  

步骤4:共识

我们目前已经拥有一个基本的区块链,可以接受交易并允许我们挖掘新的区块。但是区块链的重点在于它们应该去中心化。而且,如果它们是去中心,我们如何确保它们都反映相同的链?这叫做共识问题,如果我们要在网络中拥有多个节点,就必须实现共识算法。

注册新节点

在实现共识算法之前,我们需要一种让节点知道网络上相邻节点的方法。我们网络上的每个节点都应保留网络上其他节点的注册表。

因此,我们将需要更多的端点:

  1. /nodes/register 接受URL形式的新节点列表。
  2. /nodes/resolve 实现我们的共识算法,该算法可以解决所有冲突-确保节点具有正确的链。

我们需要修改区块链的构造函数,并提供一种注册节点的方法:

  1. from urllib.parse import urlparse  
  2. class Blockchain(object):  
  3. def __init__(self):  
  4. self.nodes = set()  
  5. def register_node(self, address): 
  6. Add a new node to the list of nodes  
  7. :param address: Address of node. Eg. 'http://192.168.0.5:5000'  
  8. :return: None  
  9. parsed_url = urlparse(address)  
  10. self.nodes.add(parsed_url.netloc)  

一种将相邻节点添加到网络的方法

请注意,我们使用了a set()来保存节点列表。这是一种廉价方法,它确保添加新节点是幂等,这意味着无论我们添加特定节点多少次,它都将只出现一次。

实施共识算法

如上所述,当一个节点与另一节点具有不同的链时会发生冲突。为了解决这个问题,我们规定最长的有效链是具有最高权威的。换句话说,网络上最长的链是事实链。使用此算法,我们可以在网络中的节点之间达成共识。

  1. import requests  
  2. class Blockchain(object)  
  3. def valid_chain(self, chain):  
  4. Determine if a given blockchain is valid  
  5. :param chain: A blockchain  
  6. :return: True if valid, False if not  
  7. last_block = chain[0]  
  8. current_index = 1  
  9. while current_index < len(chain):  
  10. block = chain[current_index]  
  11. print(f'{last_block}')  
  12. print(f'{block}')  
  13. print("\n-----------\n")  
  14. # Check that the hash of the block is correct  
  15. if block['previous_hash'] != self.hash(last_block):  
  16. return False  
  17. # Check that the Proof of Work is correct  
  18. if not self.valid_proof(last_block['proof'], block['proof']):  
  19. return False  
  20. last_block = block  
  21. current_index += 1  
  22. return True  
  23. def resolve_conflicts(self):  
  24. This is our Consensus Algorithm, it resolves conflicts  
  25. by replacing our chain with the longest one in the network.  
  26. :return: True if our chain was replaced, False if not  
  27. neighbours = self.nodes  
  28. new_chain = None  
  29. # We're only looking for chains longer than ours  
  30. max_length = len(self.chain)  
  31. # Grab and verify the chains from all the nodes in our network  
  32. for node in neighbours:  
  33. response = requests.get(f'http://{node}/chain')  
  34. if response.status_code == 200:  
  35. length = response.json()['length']  
  36. chain = response.json()['chain']  
  37. # Check if the length is longer and the chain is valid  
  38. if length > max_length and self.valid_chain(chain):  
  39. max_length = length  
  40. new_chain = chain  
  41. # Replace our chain if we discovered a new, valid chain longer than ours  
  42. if new_chain:  
  43. self.chain = new_chain  
  44. return True  
  45. return False  

第一种方法valid_chain()负责通过检查每个块并验证哈希和检验链是否有效。

resolve_conflicts()是一种方法,它会检查我们所有的相邻节点,下载它们的链并使用上述方法验证它们。如果找到有效链,其长度大于我们的长度,我们将替换它。

让我们将两个端点注册到我们的API,一个端点用于添加相邻节点,另一个端点用于解决冲突:

  1. @app.route('/nodes/register', methods=['POST'])  
  2. def register_nodes():  
  3. values = request.get_json()  
  4. nodes = values.get('nodes')  
  5. if nodes is None:  
  6. return "Error: Please supply a valid list of nodes", 400  
  7. for node in nodes:  
  8. blockchain.register_node(node)  
  9. response = {  
  10. 'message': 'New nodes have been added',  
  11. 'total_nodes': list(blockchain.nodes),  
  12. return jsonify(response), 201  
  13. @app.route('/nodes/resolve', methods=['GET'])  
  14. def consensus():  
  15. replaced = blockchain.resolve_conflicts()  
  16. if replaced:  
  17. response = {  
  18. 'message': 'Our chain was replaced',  
  19. 'new_chain': blockchain.chain  
  20. else:  
  21. response = {  
  22. 'message': 'Our chain is authoritative',  
  23. 'chain': blockchain.chain  
  24. return jsonify(response), 200  

此时,您可以根据需要使用其他计算机,并在网络上启动不同的节点。或使用同一台计算机上的不同端口启动进程。我在机器上的另一个端口上旋转了另一个节点,并将其注册到当前节点。因此,我有两个节点:http://localhost:5000和http://localhost:5001。

2a568517be1f92868c088fd9ea0c0d03.png

注册新节点

然后,我在节点2上挖到了一些新区块,以确保链更长。之后,对GET /nodes/resolve在节点1上进行了调用,在该节点上,该链被共识算法替换:

c0cba01a62bbe68483d63de80d9f3b7f.png

工作中的共识算法

目前为止已经接近成功;可以去找一些朋友一起帮助测试您的区块链。

原文标题:Learn Blockchains by Building One,作者:Daniel van Flymen

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】

【编辑推荐】

【责任编辑:庞桂玉 TEL:(010)68476606】
点赞 0
大家都在看猜你喜欢

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK