原文鏈接
https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
本文爲節選翻譯。
你想了解區塊鏈是如何工作的,區塊鏈背後的根本技術是什麼?
不過理解區塊鏈並不容易,至少對我來說。在大量的視頻,各種教程以及缺乏實例的挫折中我歷盡艱辛。
我喜歡通過動手編碼來學習。這迫使我在代碼級別上來處理這個事情,也就是說粘在一起。如果你也這樣做的話,在本指導結束的時候,你會有一個正常運轉的區塊鏈,以及深刻理解他們是如何工作的。
開始之前:
記住,區塊鏈是一個不可更改的,順序的,由稱爲塊的記錄組成的鏈。可以包括交易,文件,甚至任何數據。 最重要的是他們通過HASHES鏈接在一起。
如果你要了解hash是什麼,可以參考以下鏈接或自己搜索:
https://learncryptography.com/hash-functions/what-are-hash-functions
本指導面向誰? 你應該掌握基本的Python, 同時也理解HTTP,因爲我們將通過HTTP來講述區塊鏈。
你需要什麼? 安裝Python 3.6+ (以及PIP) , Flask, Requests library:
pip install Flask==0.12.2 requests==2.18.4
還需要一個HTTP Client, 譬如 Postman 或 cURL.
最終代碼? 源碼鏈接 https://github.com/dvf/blockchain
Step 1: Building a Blockchain 建造一個區塊鏈
打開你的編輯器或 IDE,我個人 ❤️ PyCharm. 創建一個文件,命名如下
blockchain.py
. 我們將使用一個文件。如果中途有麻煩,你可以參考源碼 https://github.com/dvf/blockchain
代表一個區塊鏈
我們將創建一個
Blockchain
類,它的構造器 constructor 創建一個初始空列表(用來儲存區塊鏈),另外一個來存儲交易。以下是類的藍本blueprint
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) , 交易列表(list of transactions), 證據( proof)和前一個塊的哈希( hash of the previous Block).
單個塊看起來如下:
block = {
'index': 1,
'timestamp': 1506057125.900785,
'transactions': [
{
'sender': "8527147fe1f5426f9dd545de4b27ee00",
'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
'amount': 5,
}
],
'proof': 324984774000,
'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
此時,鏈的概念應該很明顯—每一個新塊,它自身都保護前一個塊的hash. 這至關重要,因爲此區塊鏈才具有不可更改性:
如果攻擊者鏈中早期的塊,那麼此後的所有塊都包含了不正確的hash。
這合理嗎?如果還不理解,花點時間消化--這是區塊鏈的核心概念。
將交易添加到塊 Adding Transactions to a 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()將交易添加到列表後,將返回交易所添加到列表的索引值 --也就是下一個將被挖的索引值。這以後有用,尤其對提交交易的用戶。
創建新的塊 Creating new Blocks
當Blockchain被實例化的時候我們需要給它種一個創世塊(genesis block)—在它前沒有其它塊.我們還需要對創世塊添加證據,以證明創世塊是通過挖礦得到的(p.o.W)稍後將進一步講解挖礦。
除了在構造器創建創世塊,我們還將進一步完善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()
以上代碼很直觀。有備註及文檔。區塊鏈類幾乎完成了。不過此時,你會奇怪新塊是如何被創建,製造或挖礦的。
理解工作證據 Understanding Proof of Work
工作證據算法Proof of Work algorithm (PoW)是關於如何砸區塊鏈上創建或挖礦新塊的 。PoW的目的就是尋找一個解決問題的數字。這個數字必須難以發現但易於驗證--對於網絡上的任何人,就計算難度而言。這是Proof of Work 背後的核心。
我們看一個非常簡單的例子來幫助理解。
確定x*y的hash以0結束。
hash(x * y) = ac23dc...0
. 對此簡單例子,讓我們固定x
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以0結束:
hash(5 * 21) = 1253e9373e...5e3600155e860
比特幣中Bitcoin,工作量證據算法稱爲Hashcash.它與我們以上的簡單例子並沒有多大不同。礦工就是用以上算法競賽去尋找以便能夠創建新塊。總之,難度取決於要尋找字符串中的字符個數。礦工通過他們的挖掘答案在交易中獲取貨幣得以獎勵。
網絡可以很容易驗證他們的結果。
實現基本的PoW Implementing basic Proof of Work
爲本例的區塊鏈實現一個類似的算法。規則和上面的例子類似:
尋找一個數字 p ,當它和前一個塊的答案hash的結果4個0開頭0000
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 requests 來交互。
Step 2: Our Blockchain as an API 將區塊鏈當做API
我們用 Python Flask Framework. Flask 是一個微框架,很容易將結束點endpoints映射到Python 函數。這樣我們可以使用HTTP requests通過web訪問區塊鏈。
創建三個方法:
-
/transactions/new
-
/mine
-
/chain
Setting up Flask 設置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)
簡單解釋以上代碼:
- Line 15: 實例化節點
- Line 18: 爲節點創建隨機名字
- Line 21: 實例化Blockchain類 class.
- Line 24–26: 創建/mine endpoint ,是一個GET request
- Line 28–30: 創建 /transactions/new endpoint, POST request,因爲我們將給它發送數據。
- Line 32–38: 創建/mine endpoint , 返回整個區塊鏈
- Line 40–41: 在5000端口運行服務器。
The Transactions Endpoint 交易結束點
以下是交易請求的數據樣式。 它是用戶發送給服務器的。
{
"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
The Mining Endpoint 挖礦結束點
挖礦端點是有魔力的地方,並且很容易。要做三樣事情:
- Calculate the Proof of Work 計算PoW
- Reward the miner (us) by adding a transaction granting us 1 coin 給添加交易的礦工一個幣作爲獎勵
- Forge the new Block by adding it to the chain 將新塊添加到鏈來製造新塊
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
注意,挖礦得到的塊的接收者是節點地址。我們在此所做的大部分是與區塊鏈類的方法交互。到此,結束了,可以去區塊鏈交互了。
Step 3: Interacting with our Blockchain 與區塊鏈交互
可以使用老式的cURL或Postman工具通過網絡與我們的API交互。
啓動服務器:
$ python blockchain.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
通過向http://localhost:5000/mine 發送 Get 請求來挖礦一個區塊
下面通過向 http://localhost:5000/transactions/new 發送POST request 來創建一個新的交易, 其中 body包含以下交易結構:
{
"sender": "d4ee26eee15148ee92c6cd394edd974e",
"recipient": "someone-other-address",
"amount": 5
}
如果你沒有使用Postman,可以使用cURL, 命令如下:
$ curl -X POST -H "Content-Type: application/json" -d '{
"sender": "d4ee26eee15148ee92c6cd394edd974e",
"recipient": "someone-other-address",
"amount": 5
}' "http://localhost:5000/transactions/new"
你可以向http://localhost:5000/mine多發送幾次請求,多挖幾個區塊。然後向 http://localhost:500/chain發送請求,可以查看整個鏈。
Step 4: Consensus 共識
這很棒。我們得到了一個基本的區塊鏈,它接受交易,並且允許我們挖掘新的區塊。但是區塊鏈的本意是去中心化。如果是去中心化,我們如何保證他們反映的是同一個鏈?這就是被稱爲共識Consensus的問題。如果我們想要網絡中有多個節點,我們就需要實現共識算法。
Registering new Nodes 註冊新的節點
在我們實現共識算法 Consensus Algorithm前,我們一個讓節點知道網上鄰居節點的方法。網絡上的每個節點應該保證網上其它節點的登記。 因此,我們需要更多的端點endpoints:
-
/nodes/register
-
/nodes/resolve
我們需要修改我們的區塊鏈構造器,提供註冊登記節點的方式:
...
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()來容納節點列表。這是一種簡便的方法來確保新添加的節點是一次idempotent,就是無論添加某個節點多市場,它只出現一次。
Implementing the Consensus Algorithm 實現共識算法
正如所述,衝突是當一個節點與其它節點有不同的鏈。 要解決衝突,我們需要制定規則,最長的有效鏈是授權的。也就是說,網上的最長鏈是事實授權的。使用此算法,網絡中的節點達成共識。
...
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()方法負責檢查一個鏈是否有效,通過遍歷每一個塊,驗證hash和proof。
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
在節點2(http://127.0.0.1:5001)挖礦,多挖數次,確保它的鏈比節點1(http://localhost:5000)的鏈長。然後在節點1調用
GET /nodes/resolve, 節點1上的鏈,通過和網上的其它節點,節點2上的鏈對比,根據共識算法進行更新,因爲節點2的鏈長。
完工了。。。。。。 邀請幾個朋友幫着測試一下你的區塊鏈。
希望這可以激發你創建新東西。我對加密貨幣很狂愛,因爲我堅信區塊鏈將快速改變我們對於經濟,政府和保持記錄的看法。I
Update: part 2 將後續開發。