通过构建一个区块链学习区块链
你在这里是因为和我一样,你对Cryptocurrencies的兴起感到兴奋。而且你想知道区块链是如何工作的 - 它们背后的基本技术。
但是理解区块链并不容易 - 或者至少不是为了我。我跋涉在密集的视频,跟随多孔的教程,并处理了太少的例子放大的挫折。
我喜欢边干边学。它迫使我在代码级别处理这个主题,这让代码难以实现。如果你这样做,在本指南的最后,你将有一个功能强大的区块链,他们是如何工作的。
在你开始之前...
请记住,区块链是一个名为块的不可变的顺序链记录。它们可以包含事务,文件或任何你喜欢的数据。但重要的是他们用哈希链接在一起。
如果你不确定什么是散列,这是一个解释。
**这个指南是以谁为目标的?**你应该可以轻松地阅读和编写一些基本的Python,并且对HTTP请求的工作有一些了解,因为我们将通过HTTP与我们的Blockchain进行交流。
我需要什么? 确保安装了 Python 3.6 +(以及pip)。您还需要安装Flask和美妙的Requests库:
pip install Flask==0.12.2 requests==2.18.4
哦,你还需要一个HTTP客户端,比如Postman或者cURL。但是什么都可以做
**最终的代码在哪里?**源代码在这里可用。
第1步:构建区块链
打开你最喜欢的文本编辑器或IDE,我个人❤️ PyCharm。创建一个叫做的新文件blockchain.py
。我们只会使用一个文件,但是如果你迷路了,你可以随时引用源代码。
代表区块链
我们将创建一个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
班负责管理连锁店。它将存储交易,并有一些帮助方法来添加新的块到链中。让我们开始充实一些方法。
块是什么样的?
每个块有一个索引,一个时间戳(Unix时间),一个事务列表,一个证明(稍后更多)和前一个块的散列。
下面是一个Block的例子:
block = {
'index': 1,
'timestamp': 1506057125.900785,
'transactions': [
{
'sender': "8527147fe1f5426f9dd545de4b27ee00",
'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
'amount': 5,
}
],
'proof': 324984774000,
'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
Example of a Block in our Blockchain
在这一点上,一个链的想法应该是明显的 - 每个新块都包含在其内部的前一个块的散列。这是至关重要的,因为这是区块链不可变性的原因:如果攻击者损坏链中较早的块,则所有后续块将包含不正确的哈希值。
这有道理吗?如果没有,花点时间让它沉入其中 - 这是区块链背后的核心思想。
将交易添加到块
我们需要一种将交易添加到Block的方法。我们的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
被实例化,我们需要一个种子就发生块,一个没有前人块。我们还需要为我们的成因块添加一个“证明”,这是挖掘(或工作证明)的结果。我们稍后会详细讨论采矿。
除了创建起源于我们的构造块,我们还将充实的方法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()
上面应该是直截了当的 - 我已经添加了一些注释和文档,以帮助保持清晰。我们几乎完成了代表我们的区块链。但在这一点上,你一定想知道如何创建新的块,伪造或开采
理解工作证明
工作证明算法(PoW)是如何在区块链上创建或开采新的区块。PoW的目标是发现一个解决问题的数字。**这个数字一定很难找到, 但是网络上的任何人都可以很容易地通过计算来验证。**这是“工作证明”背后的核心思想。
我们将看一个非常简单的例子来帮助你下沉。
让我们确定散列,一个整数x
乘以另一个整数 y
必须结束与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
。因为产生的散列结束于0:
hash(5 * 21) = 1253e9373e...5e3600155e860
在比特币中,工作证明算法被称为Hashcash。这和我们上面的基本例子没有什么不同。这是矿工为了创建一个新块而竞相解决的算法。通常,难度取决于字符串中搜索的字符数。矿工们通过交易获得一枚硬币来获得解决方案。
网络能够轻松验证他们的解决方案。
实施基本的工作证明
让我们为我们的区块链实现一个类似的算法。我们的规则与上面的例子类似:
找到一个数字 p ,当与前一个块的解决方案进行散列时,产生一个具有4个前导s 的散列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"
为了调整算法的难度,我们可以修改前导零的数量。但是4就足够了。你会发现增加一个单一的前导零与寻找一个解决方案所需的时间差异巨大。
我们的课程已经接近完成,我们已经准备好开始使用HTTP请求进行交互了。
##第2步:我们的区块链作为一个API
我们将使用Python 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这里。
- 第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
采矿端点
我们的挖掘终点是魔法发生的地方,而且很容易。它必须做三件事情:
- 计算工作证明
- 奖励矿工(我们)通过增加一笔交易给予我们一枚硬币
- 通过将其添加到链中来锻造新块
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类的方法进行交互。在这一点上,我们完成了,可以开始与我们的区块链互动。
第3步:与我们的区块链互动
您可以使用普通的cURL或Postman通过网络与我们的API进行交互。
启动服务器:
$ python blockchain.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
让我们尝试通过GET
请求来挖掘一个块http://localhost:5000/mine:
使用Postman进行GET请求
让我们创建一个新的交易,向http://localhost:5000/transactions/new
POST
包含我们交易结构的实体发出请求:
使用Postman进行POST请求
如果您不使用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)
请注意,我们使用a 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()
负责通过遍历每个块来检查链是否有效,并验证散列和证明。
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
在这一点上,如果你喜欢,你可以抓住一个不同的机器,并在你的网络上启动不同的节点。或者使用同一台机器上的不同端口启动进程。我在我的机器上,不同的端口上创建了另一个节点,并将其注册到当前节点。因此,我有两个节点:http://localhost:5000
和http://localhost:5001
。
注册一个新的节点
然后我在node2
上挖掘了一些新的块,以确保链条更长。之后,我通过GET
请求 /nodes/resolve
给node1
,在链路被共识算法取代:
工作中的一致性算法
这是一个包...去找一些朋友一起帮助测试你的区块链。
我希望这能激发你创造新的东西。我对Cryptocurrencies
感到欣喜,因为我相信Blockchains
将会迅速改变我们对经济,政府和记录的看法。
更新: 我打算跟进第二部分,在这里我们将扩展我们的区块链以拥有一个交易验证机制,并讨论一些可以生产您的区块链的方法。