歡迎光臨
每天分享高質量文章

使用 Python 一步步搭建自己的區塊鏈

作者:Charlie_Jade

來自:https://segmentfault.com/a/1190000014483104

你是否會和我一樣,對加密數字貨幣底層的區塊鏈技術非常感興趣,特別想瞭解他們的執行機制。

但是學習區塊鏈技術並非一帆風順,我看多了大量的影片教程還有各種課程,最終的感覺就是真正可用的實戰課程太少。

我喜歡在實踐中學習,尤其喜歡一程式碼為基礎去瞭解整個工作機制。如果你我一樣喜歡這種學習方式,當你學完本教程時,你將會知道區塊鏈技術是如何工作的。

寫在開始之前

記住,區塊鏈是一個不可變的、有序的被稱為塊的記錄鏈。它們可以包含交易、檔案或任何您喜歡的資料。但重要的是,他們用雜湊一起被連結在一起。

如果你不熟悉雜湊,這裡是一個解釋:https://learncryptography.com/hash-functions/what-are-hash-functions。

該指南的目的是什麼?

你可以舒服地閱讀和編寫基礎的Python,因為我們將透過HTTP與區塊鏈進行討論,所以你也要瞭解HTTP的工作原理。

我需要準備什麼?

確定安裝了 Python 3.6+ (還有 pip) ,你還需要安裝 Flask、 Requests 庫:

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

對了, 你還需要一個支援HTTP的客戶端, 比如 Postman 或者 cURL,其他也可以。

原始碼在哪兒?

可以點選這裡:https://github.com/dvf/blockchain

Step 1: 建立一個區塊鏈

開啟你最喜歡的文字編輯器或者IDE, 我個人比較喜歡 PyCharm. 新建一個名為 blockchain.py的檔案。 我們將只用這一個檔案就可以。但是如果你還是不太清楚, 你也可以參考 原始碼.

描述區塊鏈

我們要建立一個 Blockchain 類 ,他的建構式建立了一個初始化的空串列(要儲存我們的區塊鏈),並且另一個儲存交易。下麵是我們這個類的實體:

blockchain.py

  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.        pass

  8.    def new_transaction(self):

  9.        # Adds a new transaction to the list of transactions

  10.        pass

  11.    @staticmethod

  12.    def hash(block):

  13.        # Hashes a Block

  14.        pass

  15.    @property

  16.    def last_block(self):

  17.        # Returns the last Block in the chain

  18.        pass

我們的 Blockchain 類負責管理鏈式資料,它會儲存交易並且還有新增新的區塊到鏈式資料的Method。讓我們開始擴充更多Method。

塊是什麼樣的 ?

每個塊都有一個 索引,一個 時間戳(Unix時間戳),一個 事務串列, 一個 校驗(稍後詳述) 和 前一個塊的雜湊

下麵是一個Block的例子 :

blockchain.py

  1. block = {

  2.    'index': 1,

  3.    'timestamp': 1506057125.900785,

  4.    'transactions': [

  5.        {

  6.            'sender': "8527147fe1f5426f9dd545de4b27ee00",

  7.            'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",

  8.            'amount': 5,

  9.        }

  10.    ],

  11.    'proof': 324984774000,

  12.    'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"

  13. }

在這一點上,一個 區塊鏈 的概念應該是明顯的 - 每個新塊都包含在其內的前一個塊的 雜湊 。 這是至關重要的,因為這是 區塊鏈 不可改變的原因:如果攻擊者損壞 區塊鏈 中較早的塊,則所有後續塊將包含不正確的雜湊值。

這有道理嗎? 如果你還沒有想通,花點時間仔細思考一下——這是區塊鏈背後的核心理念

新增交易到區塊

我們將需要一個新增交易到區塊的方式。我們的 new_transaction()方法的責任就是這個, 並且它非常的簡單:

blockchain.py

  1. class Blockchain(object):

  2.    ...

  3.    def new_transaction(self, sender, recipient, amount):

  4.        """

  5.        Creates a new transaction to go into the next mined Block

  6.        :param sender: Address of the Sender

  7.        :param recipient: Address of the Recipient

  8.        :param amount: Amount

  9.        :return: The index of the Block that will hold this transaction

  10.        """

  11.        self.current_transactions.append({

  12.            'sender': sender,

  13.            'recipient': recipient,

  14.            'amount': amount,

  15.        })

  16.        return self.last_block['index'] + 1

new_transaction() 方法添加了交易到串列,它傳回了交易將被新增到的區塊的索引---講開採下一個這對稍後對提交交易的使用者有用。

建立新的區塊

當我們的 Blockchain  被實體化後,我們需要將 創世 區塊(一個沒有前導區塊的區塊)新增進去進去。我們還需要向我們的起源塊新增一個 證明,這是挖礦的結果(或工作證明)。 我們稍後會詳細討論挖礦。

除了在建構式中建立 創世 區塊外,我們還會補全 new_block()new_transaction()  和 hash() 函式:

blockchain.py

  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.        # 建立創世區塊

  9.        self.new_block(previous_hash=1, proof=100)

  10.    def new_block(self, proof, previous_hash=None):

  11.        """

  12.        建立一個新的區塊到區塊鏈中

  13.        :param proof: 由工作證明演演算法生成的證明

  14.        :param previous_hash: (Optional) 前一個區塊的 hash 值

  15.        :return: 新區塊

  16.        """

  17.        block = {

  18.            'index': len(self.chain) + 1,

  19.            'timestamp': time(),

  20.            'transactions': self.current_transactions,

  21.            'proof': proof,

  22.            'previous_hash': previous_hash or self.hash(self.chain[-1]),

  23.        }

  24.        # 重置當前交易記錄

  25.        self.current_transactions = []

  26.        self.chain.append(block)

  27.        return block

  28.    def new_transaction(self, sender, recipient, amount):

  29.        """

  30.        建立一筆新的交易到下一個被挖掘的區塊中

  31.        :param sender: 傳送人的地址

  32.        :param recipient: 接收人的地址

  33.        :param amount: 金額

  34.        :return: 持有本次交易的區塊索引

  35.        """

  36.        self.current_transactions.append({

  37.            'sender': sender,

  38.            'recipient': recipient,

  39.            'amount': amount,

  40.        })

  41.        return self.last_block['index'] + 1

  42.    @property

  43.    def last_block(self):

  44.        return self.chain[-1]

  45.    @staticmethod

  46.    def hash(block):

  47.        """

  48.        給一個區塊生成 SHA-256 值

  49.        :param block: Block

  50.        :return:

  51.        """

  52.        # 我們必須確保這個字典(區塊)是經過排序的,否則我們將會得到不一致的雜湊

  53.        block_string = json.dumps(block, sort_keys=True).encode()

  54.        return hashlib.sha256(block_string).hexdigest()

上面的程式碼應該是直白的 --- 為了讓程式碼清晰,我添加了一些註釋和檔案說明。 我們差不多完成了我們的區塊鏈。 但在這個時候你一定很疑惑新的塊是怎麼被建立、鍛造或挖掘的。

工作量證明演演算法

使用工作量證明(PoW)演演算法,來證明是如何在區塊鏈上建立或挖掘新的區塊。PoW 的標的是計算出一個符合特定條件的數字,這個數字對於所有人而言必須在計算上非常困難,但易於驗證。這是工作證明背後的核心思想。

我們將看到一個簡單的例子幫助你理解:

假設一個整數 x 乘以另一個整數 y 的積的 Hash 值必須以 0 結尾,即 hash(x * y) = ac23dc...0。設 x = 5,求 y ?用 Python 實現:

  1. from hashlib import sha256

  2. x = 5

  3. y = 0  # We don't know what y should be yet...

  4. while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":

  5.    y += 1

  6. print(f'The solution is y = {y}')

結果是: y = 21。因為,生成的 Hash 值結尾必須為 0。

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

在比特幣中,工作量證明演演算法被稱為 Hashcash ,它和上面的問題很相似,只不過計算難度非常大。這就是礦工們為了爭奪建立區塊的權利而爭相計算的問題。 通常,計算難度與標的字串需要滿足的特定字元的數量成正比,礦工算出結果後,就會獲得一定數量的比特幣獎勵(透過交易)。

驗證結果,當然非常容易。

實現工作量證明

讓我們來實現一個相似 PoW 演演算法。規則類似上面的例子:

找到一個數字 P ,使得它與前一個區塊的 proof 拼接成的字串的 Hash 值以 4 個零開頭。

blockchain.py

  1. import hashlib

  2. import json

  3. from time import time

  4. from uuid import uuid4

  5. class Blockchain(object):

  6.    ...

  7.    def proof_of_work(self, last_proof):

  8.        """

  9.        Simple Proof of Work Algorithm:

  10.         - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'

  11.         - p is the previous proof, and p' is the new proof

  12.        :param last_proof:

  13.        :return:

  14.        """

  15.        proof = 0

  16.        while self.valid_proof(last_proof, proof) is False:

  17.            proof += 1

  18.        return proof

  19.    @staticmethod

  20.    def valid_proof(last_proof, proof):

  21.        """

  22.        Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes?

  23.        :param last_proof: Previous Proof

  24.        :param proof: Current Proof

  25.        :return: True if correct, False if not.

  26.        """

  27.        guess = f'{last_proof}{proof}'.encode()

  28.        guess_hash = hashlib.sha256(guess).hexdigest()

  29.        return guess_hash[:4] == "0000"

衡量演演算法複雜度的辦法是修改零開頭的個數。使用 4 個來用於演示,你會發現多一個零都會大大增加計算出結果所需的時間。

現在 Blockchain 類基本已經完成了,接下來使用 HTTP requests 來進行互動。

Step 2: Blockchain 作為 API 介面

我們將使用 Python Flask 框架,這是一個輕量 Web 應用框架,它方便將網路請求對映到 Python 函式,現在我們來讓 Blockchain 執行在基於 Flask web 上。

我們將建立三個介面:

  • /transactions/new 建立一個交易並新增到區塊

  • /mine 告訴伺服器去挖掘新的區塊

  • /chain 傳回整個區塊鏈

建立節點

我們的“Flask 伺服器”將扮演區塊鏈網路中的一個節點。我們先新增一些框架程式碼:

blockchain.py

  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.    ...

  9. # Instantiate our Node

  10. app = Flask(__name__)

  11. # Generate a globally unique address for this node

  12. node_identifier = str(uuid4()).replace('-', '')

  13. # Instantiate the Blockchain

  14. blockchain = Blockchain()

  15. @app.route('/mine', methods=['GET'])

  16. def mine():

  17.    return "We'll mine a new Block"

  18. @app.route('/transactions/new', methods=['POST'])

  19. def new_transaction():

  20.    return "We'll add a new transaction"

  21. @app.route('/chain', methods=['GET'])

  22. def full_chain():

  23.    response = {

  24.        'chain': blockchain.chain,

  25.        'length': len(blockchain.chain),

  26.    }

  27.    return jsonify(response), 200

  28. if __name__ == '__main__':

  29.    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. {

  2. "sender": "my address",

  3. "recipient": "someone else's address",

  4. "amount": 5

  5. }

因為我們已經有了新增交易的方法,所以基於介面來新增交易就很簡單了。讓我們為新增事務寫函式:

blockchain.py

  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. ...

  8. @app.route('/transactions/new', methods=['POST'])

  9. def new_transaction():

  10.    values = request.get_json()

  11.    # Check that the required fields are in the POST'ed data

  12.    required = ['sender', 'recipient', 'amount']

  13.    if not all(k in values for k in required):

  14.        return 'Missing values', 400

  15.    # Create a new Transaction

  16.    index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])

  17.    response = {'message': f'Transaction will be added to Block {index}'}

  18.    return jsonify(response), 201

挖礦

挖礦正是神奇所在,它很簡單,做了一下三件事:

  1. 計算工作量證明 PoW

  2. 透過新增一個交易授予礦工(自己)一個幣

  3. 構造新區塊並將其新增到鏈中

blockchain.py

  1. import hashlib

  2. import json

  3. from time import time

  4. from uuid import uuid4

  5. from flask import Flask, jsonify, request

  6. ...

  7. @app.route('/mine', methods=['GET'])

  8. def mine():

  9.    # We run the proof of work algorithm to get the next proof...

  10.    last_block = blockchain.last_block

  11.    last_proof = last_block['proof']

  12.    proof = blockchain.proof_of_work(last_proof)

  13.    # We must receive a reward for finding the proof.

  14.    # The sender is "0" to signify that this node has mined a new coin.

  15.    blockchain.new_transaction(

  16.        sender="0",

  17.        recipient=node_identifier,

  18.        amount=1,

  19.    )

  20.    # Forge the new Block by adding it to the chain

  21.    previous_hash = blockchain.hash(last_block)

  22.    block = blockchain.new_block(proof, previous_hash)

  23.    response = {

  24.        'message': "New Block Forged",

  25.        'index': block['index'],

  26.        'transactions': block['transactions'],

  27.        'proof': block['proof'],

  28.        'previous_hash': block['previous_hash'],

  29.    }

  30.    return jsonify(response), 200

註意交易的接收者是我們自己的伺服器節點,我們做的大部分工作都只是圍繞 Blockchain 類方法進行互動。到此,我們的區塊鏈就算完成了,我們來實際執行下.

Step 3: 執行區塊鏈

你可以使用 cURL 或 Postman 去和 API 進行互動。

啟動 Server:

  1. $ python blockchain.py

  2. * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

讓我們透過請求 http://localhost:5000/mine ( GET )來進行挖礦:

用 Postman 發起一個 GET 請求。

建立一個交易請求,請求 http://localhost:5000/transactions/new (POST),如圖

如果不是使用 Postman,則用一下的 cURL 陳述句也是一樣的:

  1. $ curl -X POST -H "Content-Type: application/json" -d '{

  2. "sender": "d4ee26eee15148ee92c6cd394edd974e",

  3. "recipient": "someone-other-address",

  4. "amount": 5

  5. }' "http://localhost:5000/transactions/new"

在挖了兩次礦之後,就有 3 個塊了,透過請求 http://localhost:5000/chain 可以得到所有的塊資訊:

  1. {

  2.  "chain": [

  3.    {

  4.      "index": 1,

  5.      "previous_hash": 1,

  6.      "proof": 100,

  7.      "timestamp": 1506280650.770839,

  8.      "transactions": []

  9.    },

  10.    {

  11.      "index": 2,

  12.      "previous_hash": "c099bc...bfb7",

  13.      "proof": 35293,

  14.      "timestamp": 1506280664.717925,

  15.      "transactions": [

  16.        {

  17.          "amount": 1,

  18.          "recipient": "8bbcb347e0634905b0cac7955bae152b",

  19.          "sender": "0"

  20.        }

  21.      ]

  22.    },

  23.    {

  24.      "index": 3,

  25.      "previous_hash": "eff91a...10f2",

  26.      "proof": 35089,

  27.      "timestamp": 1506280666.1086972,

  28.      "transactions": [

  29.        {

  30.          "amount": 1,

  31.          "recipient": "8bbcb347e0634905b0cac7955bae152b",

  32.          "sender": "0"

  33.        }

  34.      ]

  35.    }

  36.  ],

  37.  "length": 3

  38. }

Step 4: 一致性(共識)

我們已經有了一個基本的區塊鏈可以接受交易和挖礦。但是區塊鏈系統應該是分散式的。既然是分散式的,那麼我們究竟拿什麼保證所有節點有同樣的鏈呢?這就是一致性問題,我們要想在網路上有多個節點,就必須實現一個一致性的演演算法。

註冊節點

在實現一致性演演算法之前,我們需要找到一種方式讓一個節點知道它相鄰的節點。每個節點都需要儲存一份包含網路中其它節點的記錄。因此讓我們新增幾個介面:

  1. /nodes/register 接收 URL 形式的新節點串列.

  2. /nodes/resolve 執行一致性演演算法,解決任何衝突,確保節點擁有正確的鏈.

我們修改下 Blockchain 的 init 函式並提供一個註冊節點方法:

blockchain.py

  1. ...

  2. from urllib.parse import urlparse

  3. ...

  4. class Blockchain(object):

  5.    def __init__(self):

  6.        ...

  7.        self.nodes = set()

  8.        ...

  9.    def register_node(self, address):

  10.        """

  11.        Add a new node to the list of nodes

  12.        :param address: Address of node. Eg. 'http://192.168.0.5:5000'

  13.        :return: None

  14.        """

  15.        parsed_url = urlparse(address)

  16.        self.nodes.add(parsed_url.netloc)

我們用 set 來儲存節點,這是一種避免重覆新增節點的簡單方法.

實現共識演演算法

就像先前講的那樣,當一個節點與另一個節點有不同的鏈時,就會產生衝突。 為瞭解決這個問題,我們將制定最長的有效鏈條是最權威的規則。換句話說就是:在這個網路裡最長的鏈就是最權威的。 我們將使用這個演演算法,在網路中的節點之間達成共識。

blockchain.py

  1. ...

  2. import requests

  3. class Blockchain(object)

  4.    ...

  5.    def valid_chain(self, chain):

  6.        """

  7.        Determine if a given blockchain is valid

  8.        :param chain: A blockchain

  9.        :return: True if valid, False if not

  10.        """

  11.        last_block = chain[0]

  12.        current_index = 1

  13.        while current_index < len(chain):

  14.            block = chain[current_index]

  15.            print(f'{last_block}')

  16.            print(f'{block}')

  17.            print("-----------")

  18.            # Check that the hash of the block is correct

  19.            if block['previous_hash'] != self.hash(last_block):

  20.                return False

  21.            # Check that the Proof of Work is correct

  22.            if not self.valid_proof(last_block['proof'], block['proof']):

  23.                return False

  24.            last_block = block

  25.            current_index += 1

  26.        return True

  27.    def resolve_conflicts(self):

  28.        """

  29.        This is our Consensus Algorithm, it resolves conflicts

  30.        by replacing our chain with the longest one in the network.

  31.        :return: True if our chain was replaced, False if not

  32.        """

  33.        neighbours = self.nodes

  34.        new_chain = None

  35.        # We're only looking for chains longer than ours

  36.        max_length = len(self.chain)

  37.        # Grab and verify the chains from all the nodes in our network

  38.        for node in neighbours:

  39.            response = requests.get(f'http://{node}/chain')

  40.            if response.status_code == 200:

  41.                length = response.json()['length']

  42.                chain = response.json()['chain']

  43.                # Check if the length is longer and the chain is valid

  44.                if length > max_length and self.valid_chain(chain):

  45.                    max_length = length

  46.                    new_chain = chain

  47.        # Replace our chain if we discovered a new, valid chain longer than ours

  48.        if new_chain:

  49.            self.chain = new_chain

  50.            return True

  51.        return False

第一個方法 valid_chain() 負責檢查一個鏈是否有效,方法是遍歷每個塊並驗證雜湊和證明。

resolve_conflicts() 是一個遍歷我們所有鄰居節點的方法,下載它們的鏈並使用上面的方法驗證它們。 如果找到一個長度大於我們的有效鏈條,我們就取代我們的鏈條。

我們將兩個端點註冊到我們的API中,一個用於新增相鄰節點,另一個用於解決衝突:

blockchain.py

  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.    }

  13.    return jsonify(response), 201

  14. @app.route('/nodes/resolve', methods=['GET'])

  15. def consensus():

  16.    replaced = blockchain.resolve_conflicts()

  17.    if replaced:

  18.        response = {

  19.            'message': 'Our chain was replaced',

  20.            'new_chain': blockchain.chain

  21.        }

  22.    else:

  23.        response = {

  24.            'message': 'Our chain is authoritative',

  25.            'chain': blockchain.chain

  26.        }

  27.    return jsonify(response), 200

在這一點上,如果你喜歡,你可以使用一臺不同的機器,併在你的網路上啟動不同的節點。 或者使用同一臺機器上的不同埠啟動行程。 我在我的機器上,不同的埠上建立了另一個節點,並將其註冊到當前節點。 因此,我有兩個節點: http://localhost:5000http://localhost:5001。 註冊一個新節點:

然後我在節點 2 上挖掘了一些新的塊,以確保鏈條更長。 之後,我在節點1上呼叫 GET/nodes/resolve,其中鏈由一致性演演算法取代:

這是一個包,去找一些朋友一起,以幫助測試你的區塊鏈。

我希望本文能激勵你創造更多新東西。我之所以對數字貨幣入迷,是因為我相信區塊鏈會很快改變我們看待事物的方式,包括經濟、政府、檔案管理等。

更新:我計劃在接下來的第2部分中繼續討論區塊鏈交易驗證機制,並討論一些可以讓區塊鏈進行生產的方法。


編號404,輸入編號直達本文

●輸入m獲取文章目錄

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖