想學習區塊鏈?那就用 Python 構建一個
| 2018-07-30 13:04 收藏: 2
瞭解區塊鏈是如何工作的最快的方法是構建一個。
你看到這篇文章是因為和我一樣,對加密貨幣的大熱而感到興奮。並且想知道區塊鏈是如何工作的 —— 它們背後的技術基礎是什麼。
但是理解區塊鏈並不容易 —— 至少對我來說是這樣。我徜徉在各種難懂的影片中,並且因為示例太少而陷入深深的挫敗感中。
我喜歡在實踐中學習。這會使得我在程式碼層面上處理主要問題,從而可以讓我堅持到底。如果你也是這麼做的,在本指南結束的時候,你將擁有一個功能正常的區塊鏈,並且實實在在地理解了它的工作原理。
開始之前 …
記住,區塊鏈是一個 不可更改的、有序的 記錄(被稱為區塊)的鏈。它們可以包括交易、檔案或者任何你希望的真實資料。最重要的是它們是透過使用雜湊連結到一起的。
如果你不知道雜湊是什麼,這裡有解釋。
本指南的目標讀者是誰? 你應該能輕鬆地讀、寫一些基本的 Python 程式碼,並能夠理解 HTTP 請求是如何工作的,因為我們討論的區塊鏈將基於 HTTP。
我需要做什麼? 確保安裝了 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 類的原型
我們的 Blockchain
類負責管理鏈。它將儲存交易並且有一些為鏈中增加新區塊的輔助性質的方法。現在我們開始去充實一些類的方法。
區塊是什麼樣子的?
每個區塊有一個索引、一個時間戳(Unix 時間)、一個交易的列表、一個證明(後面會詳細解釋)、以及前一個區塊的雜湊。
單個區塊的示例應該是下面的樣子:
block = {
'index': 1,
'timestamp': 1506057125.900785,
'transactions': [
{
'sender': "8527147fe1f5426f9dd545de4b27ee00",
'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
'amount': 5,
}
],
'proof': 324984774000,
'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
我們的區塊鏈中的塊示例
此刻,鏈的概念應該非常明顯 —— 每個新區塊包含它自身的資訊和前一個區域的雜湊。這一點非常重要,因為這就是區塊鏈不可更改的原因:如果攻擊者修改了一個早期的區塊,那麼所有的後續區塊將包含錯誤的雜湊。
這樣做有意義嗎?如果沒有,就讓時間來埋葬它吧 —— 這就是區塊鏈背後的核心思想。
新增交易到一個區塊
我們將需要一種區塊中新增交易的方式。我們的 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
被例項化後,我們需要一個創世區塊(一個沒有祖先的區塊)來播種它。我們也需要去新增一些 “證明” 到創世區塊,它是挖礦(工作量證明 PoW)的成果。我們在後面將討論更多挖礦的內容。
除了在我們的建構函式中建立創世區塊之外,我們還需要寫一些方法,如 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 的目標是去撞出一個能夠解決問題的數字。這個數字必須滿足“找到它很困難但是驗證它很容易”的條件 —— 網路上的任何人都可以計算它。這就是 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。與我們上面的例子沒有太大的差別。這就是礦工們進行競賽以決定誰來建立新塊的演算法。一般來說,其難度取決於在一個字串中所查詢的字元數量。然後礦工會因其做出的求解而得到獎勵的幣——在一個交易當中。
網路上的任何人都可以很容易地去核驗它的答案。
實現基本的 PoW
為我們的區塊鏈來實現一個簡單的演算法。我們的規則與上面的示例類似:
找出一個數字
p
,它與前一個區塊的答案進行雜湊運算得到一個雜湊值,這個雜湊值的前四位必須是由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 請求與它互動。
第 2 步:以 API 方式去訪問我們的區塊鏈
我們將使用 Python Flask 框架。它是個微框架,使用它去做端點到 Python 函式的對映很容易。這樣我們可以使用 HTTP 請求基於 web 來與我們的區塊鏈對話。
我們將建立三個方法:
/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)
對上面的程式碼,我們做新增一些詳細的解釋:
- Line 15:例項化我們的節點。更多關於 Flask 的知識讀 這裡。
- Line 18:為我們的節點建立一個隨機的名字。
- Line 21:例項化我們的區塊鏈類。
- Line 24–26:建立
/mine
端點,這是一個 GET 請求。 - Line 28–30:建立
/transactions/new
端點,這是一個 POST 請求,因為我們要傳送資料給它。 - Line 32–38:建立
/chain
端點,它返回全部區塊鏈。 - Line 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
建立交易的方法
挖礦端點
我們的挖礦端點是見證奇蹟的地方,它實現起來很容易。它要做三件事情:
- 計算工作量證明
- 因為礦工(我們)新增一個交易而獲得報酬,獎勵礦工(我們) 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
類的方法進行互動的。到目前為止,我們已經做完了,現在開始與我們的區塊鏈去互動。
第 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 請求
我們透過生成一個 POST
請求到 http://localhost:5000/transactions/new
去建立一個區塊,請求資料包含我們的交易結構:
使用 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
}
第 4 步:共識
這是很酷的一個地方。我們已經有了一個基本的區塊鏈,它可以接收交易並允許我們去挖掘出新區塊。但是區塊鏈的整個重點在於它是去中心化的。而如果它們是去中心化的,那我們如何才能確保它們表示在同一個區塊鏈上?這就是共識問題,如果我們希望在我們的網路上有多於一個的節點執行,那麼我們將必須去實現一個共識演算法。
註冊新節點
在我們能實現一個共識演算法之前,我們需要一個辦法去讓一個節點知道網路上的鄰居節點。我們網路上的每個節點都保留有一個該網路上其它節點的註冊資訊。因此,我們需要更多的端點:
/nodes/register
以 URL 的形式去接受一個新節點列表/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()
去儲存節點列表。這是一個非常合算的方式,它將確保新增的節點是冪等的 —— 這意味著不論你將特定的節點新增多少次,它都是精確地只出現一次。
實現共識演算法
正如前面提到的,當一個節點與另一個節點有不同的鏈時就會產生衝突。為解決衝突,我們制定一個規則,即最長的有效的鏈才是權威的鏈。換句話說就是,網路上最長的鏈就是事實上的區塊鏈。使用這個演算法,可以在我們的網路上節點之間達到共識。
...
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
。
註冊一個新節點
我接著在節點 2 上挖出一些新區塊,以確保這個鏈是最長的。之後我在節點 1 上以 GET
方式呼叫了 /nodes/resolve
,這時,節點 1 上的鏈被共識演算法替換成節點 2 上的鏈了:
工作中的共識演算法
然後將它們封裝起來 … 找一些朋友來幫你一起測試你的區塊鏈。
我希望以上內容能夠鼓舞你去建立一些新的東西。我是加密貨幣的狂熱擁護者,因此我相信區塊鏈將迅速改變我們對經濟、政府和記錄儲存的看法。
更新: 我正計劃繼續它的第二部分,其中我將擴充套件我們的區塊鏈,使它具備交易驗證機制,同時討論一些你可以在其上產生你自己的區塊鏈的方式。(LCTT 譯註:第二篇並沒有~!)
via: https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
作者:Daniel van Flymen 譯者:qhwdw 校對:wxy
相關文章
- 用 Python 構建一個極小的區塊鏈Python區塊鏈
- 【區塊鏈學習】《區塊鏈學習指南》學習筆記區塊鏈筆記
- FISCO BCOS | 構建第一個區塊鏈應用程式區塊鏈
- 用 Go 構建一個區塊鏈 -- Part 7: 網路Go區塊鏈
- 區塊鏈學習區塊鏈
- 如何學習區塊鏈區塊鏈
- 初識區塊鏈 - 用JS構建你自己的區塊鏈區塊鏈JS
- 想問問區塊鏈區塊鏈
- 構建 EOS 區塊鏈瀏覽器區塊鏈瀏覽器
- 區塊鏈學習資料區塊鏈
- 前言-如何學習區塊鏈區塊鏈
- 區塊鏈學習筆記01--區塊鏈常識區塊鏈筆記
- 區塊鏈的幾個小故事.小白學區塊鏈01區塊鏈
- 劍英的區塊鏈學習手記(一)區塊鏈
- 構建 EOS 區塊鏈瀏覽器 API區塊鏈瀏覽器API
- 說區塊鏈-學習筆記區塊鏈筆記
- 區塊鏈技術學習指引區塊鏈
- 打造一個最小區塊鏈區塊鏈
- 基於Java語言構建區塊鏈(一)—— 基本原型Java區塊鏈原型
- 區塊鏈將構建完美的契約世界區塊鏈
- 區塊鏈構建資料可信流通體系區塊鏈
- 用Go構建區塊鏈——6.交易2Go區塊鏈
- 谷歌要構建自己的區塊鏈技術谷歌區塊鏈
- 一文讀懂區塊鏈以及一個區塊鏈的實現區塊鏈
- 區塊鏈學習者終極指南區塊鏈
- 一個簡單的區塊鏈區塊鏈
- 用 JavaScript 寫一個區塊鏈JavaScript區塊鏈
- 用不到 50 行的 Python 程式碼構建最小的區塊鏈Python區塊鏈
- 使用 Swift 和 Vapor 構建區塊鏈伺服器SwiftVapor區塊鏈伺服器
- 區塊鏈應用|人工智慧的落地及區塊鏈應用暢想區塊鏈人工智慧
- 區塊鏈學習筆記與思考-4區塊鏈筆記
- 區塊鏈習題複習區塊鏈
- 一個簡單的區塊鏈貨幣,python實現區塊鏈Python
- 零基礎學區塊鏈(一)區塊鏈
- 區塊鏈2.0架構:以太坊區塊鏈的介紹區塊鏈架構
- 【譯】用JavaScript寫一個區塊鏈JavaScript區塊鏈
- Python 從零開始構建自己的比特幣區塊鏈系統Python比特幣區塊鏈
- 區塊鏈學習-Golang 與智慧合約的互動(一)區塊鏈Golang