使用 Python 從零開始開發區塊鏈應用程序

tming888 6年前發布 | 34K 次閱讀 Python 區塊鏈開發 區塊鏈

本教程將向具有任何編程技能水平的 Python 開發人員介紹區塊鏈。通過從零開始實現一個 公有區塊鏈 并構建一個簡單應用程序來利用它,您將了解區塊鏈到底是什么。

您將能夠使用 Flask 微框架為區塊鏈的不同功能創建端點,比如添加事務,然后在多個機器上運行腳本來創建一個去中心化網絡。您還將了解如何構建一個簡單的用戶界面,以便與區塊鏈進行交互,并存儲任何用例的信息,比如對等支付、聊天或電子商務。

Python 是一種容易理解的編程語言,這是我在本教程中選擇它的原因。通過學習本教程,您將實現一個公有區塊鏈并了解它的實際應用。GitHub 上提供了一個完整的樣本應用程序代碼,該應用程序完全是用 Python 編寫的。

獲取代碼

主要邏輯包含在 views.py 文件中。讓我們一起分析一下該邏輯,以便真正全面了解區塊鏈。

前提條件

  • Python 的基礎編程知識
  • Flask 微框架(用于創建區塊鏈服務器的端點)

背景

公有區塊鏈與私有區塊鏈

公有區塊鏈網絡 (比如比特幣網絡)完全對公眾開放,任何人都能加入和參與其中。

另一方面,如果企業對事務的隱私性、安全性和速度有更高的要求,可以選擇采用 私有區塊鏈網絡 ,參與者需要邀請才能加入該網絡。了解更多信息。

2008 年,一個名叫 Satoshi Nakamoto 的人(或者可能是一個小組)發表了一篇名為《比特幣:一種對等電子現金系統》的白皮書。該文章結合了密碼學技術和對等網絡,不需要依靠中心化權威機構(比如銀行)就能在人們之間實現付款。比特幣應運而生。除了比特幣之外,這篇文章還介紹了一種存儲數據的分布式系統(即現在廣為人知的“區塊鏈”),該系統的適用范圍遠不只是付款或加密貨幣。

從那時起,幾乎每個行業都對區塊鏈產生了濃厚的興趣。無論是像比特幣這樣的完全數字化的貨幣、像以太坊這樣的分布式計算技術,還是像 IBM Blockchain Platform 所基于的Hyperledger Fabric 這樣的開源框架,現在都以區塊鏈作為其背后的基礎技術。

進一步了解 IBM Blockchain Platform Starter Plan(免費使用測試版!)

“區塊鏈”是什么?

區塊鏈是一種存儲數字數據的方式。數據可以是任何內容。對于比特幣,它是事務(在帳戶之間轉移比特幣),它甚至可以是文件;這都無關緊要。數據是以區塊形式進行存儲的,區塊使用哈希值鏈接在一起。因此得名“區塊鏈”。

區塊鏈的神奇之處是在其中添加和存儲此類數據的方式,該方式造就了一些非常理想的特征:

  • 歷史記錄無法更改
  • 系統無法攻破
  • 數據的持久保存
  • 沒有單點故障

那么區塊鏈如何能夠實現這些特征呢?我們將通過實現一個區塊鏈來深入剖析它。讓我們開始吧。

關于該應用程序

首先定義一下我們將要構建的應用程序的用途。我們的目的是構建一個允許用戶共享信息的簡單網站。因為內容將存儲在區塊鏈中,所以它無法更改且會永遠存在。

我們將采用自下而上的實現方式。首先定義我們將存儲在區塊鏈中的數據的結構。 一篇帖子(任何用戶在我們的應用程序上發布的一條消息)將由 3 個基本要素來標識:

  1. 內容
  2. 作者
  3. 時間戳

1

將事務存儲到區塊中

我們將采用一種廣泛使用的格式來將數據存儲在區塊鏈中:JSON。以下是一篇存儲在區塊鏈中的帖子的格式:

{
    "author": "some_author_name", 
    "content": "Some thoughts that author wants to share", 
    "timestamp": "The time at which the content was created"
}

術語“數據”通常在互聯網上被“事務”一詞所取代。所以,為了避免混淆并保持一致,我們將使用術語“事務”來表示在我們的示例應用程序中發布的數據。

事務被打包到區塊中。一個區塊可以包含一個或許多個事務。包含事務的區塊頻繁地生成并添加到區塊鏈中。因為可能有多個區塊,所以每個區塊都應有一個唯一 ID:

class Block: 
    def __init__(self, index, transactions, timestamp): 
        self.index = [] 
        self.transactions = transactions 
        self.timestamp = timestamp

2

讓區塊不可更改

我們希望檢測出對區塊內存儲的數據的任何篡改。在區塊鏈中,這是使用一個哈希函數來實現的。

哈希函數 接受任何大小的數據并生成固定大小的數據,該結果通常用于識別輸入。下面是 Python 中的一個使用 sha256 哈希函數的示例:

>>> from hashlib import sha256
>>> data = "Some variable length data"
>>> sha256(data).hexdigest()
'b919fbbcae38e2bdaebb6c04ed4098e5c70563d2dc51e085f784c058ff208516'
>>> sha256(data).hexdigest() # no matter how many times you run it, the
 result is going to be the same 256 character string
'b919fbbcae38e2bdaebb6c04ed4098e5c70563d2dc51e085f784c058ff208516'

一個理想的哈希函數包括以下特征:

  • 它應該很容易計算。
  • 哪怕只更改數據中的一個位,哈希值也應該完全發生變化。
  • 應該無法根據輸出哈希值猜出輸入。

您現在知道哈希函數是什么了吧。我們將每個區塊的哈希值都存儲在 Block 對象內的一個字段中,其作用類似于它所包含的數據的數字指紋:

from hashlib import sha256
import json

def compute_hash(block):  
     """ 
    A function that creates the hash of the block. 
    """ 
    block_string = json.dumps(self.__dict__, sort_keys=True) 
    return sha256(block_string.encode()).hexdigest()

備注:在大多數加密貨幣中,甚至對區塊中的各個事務也進行了哈希運算,從而形成一棵哈希樹(也稱為 二進制哈希樹 ),這棵樹的根可以用作區塊的哈希值。它不是區塊鏈正常運作的必要條件,所以我們將省略它,以保持代碼簡潔。

3

鏈接區塊

我們已設置了區塊。區塊鏈應該是一個區塊集合。我們可以將所有區塊都存儲在 Python 列表中(等效于數組)。但這還不夠,因為如果有人故意替換了集合中的一個區塊該怎么辦?用修改過的事務創建一個新的區塊,計算哈希值,然后替換任何舊區塊,這在我們的當前實現中并不是什么難事,因為我們會保持區塊的不可更改性和順序。

我們需要采用某種途徑來確保對過去的區塊的任何更改都會造成整個鏈的失效。一種方法是通過哈希值將區塊鏈接起來。談到鏈接,我們指的是將前一個區塊的哈希值包含在當前區塊中。所以,如果任何以前的區塊的內容發生更改,該區塊的哈希值也會發生更改,導致與下一個區塊中的 previous_hash 字段不匹配。

每個區塊都通過 previous_hash 字段鏈接到前一個區塊,但是第一個區塊該如何處理?第一個區塊稱為 創始區塊 ,大多數情況下,它是手動生成或通過某種獨特邏輯生成的。讓我們將 previous_hash 字段添加到 Block 類中,并實現我們的 Blockchain 類的初始結構(參見清單 1)。

清單 1. 我們的 Blockchain 類的初始結構

from hashlib import sha256
import json
import time
class Block:  
    def__init__(self, index, transactions, timestamp, previous_hash):  
        self.index = index 
        self.transactions = transactions 
        self.timestamp = timestamp
        self.previous_hash = previous_hash 

    def compute_hash(self):  
        block_string = json.dumps(self.__dict__, sort_keys=True) 
        return sha256(block_string.encode()).hexdigest()

這是我們的 Blockchain 類:

class Blockchain: 

    def __init__(self): 
        self.unconfirmed_transactions = [] # data yet to get into blockchain
        self.chain = []
        self.create_genesis_block()

    def create_genesis_block(self): 
        """
        A function to generate genesis block and appends it to
        the chain.The block has index 0, previous_hash as 0, and
        a valid hash.
        """
        genesis_block = Block(0, [], time.time(), "0")
        genesis_block.hash = genesis_block.compute_hash()
        self.chain.append(genesis_block)

    @property
    def last_block(self): 
        return self.chain[-1]

4

實現工作量證明算法

選擇性背書與工作量證明

IBM Blockchain Platform 支持的業務區塊鏈中的共識性不是通過挖礦實現的,而是通過一個稱為 選擇性背書 的流程來實現的。網絡成員準確控制由誰來驗證事務,與目前的業務實現方式大致相同。進一步了解業務區塊鏈。

但這里存在一個問題。如果我們更改前一個區塊,我們可以非常輕松地重新計算所有后續區塊的哈希值,并創建一個不同的有效區塊鏈。為了預防這種情況,我們必須讓計算哈希值的任務變得困難和隨機化。

以下是我們實現此操作的方式。我們不會接受任何區塊哈希值,而是對它添加某種約束。讓我們來添加一種約束:哈希值應以兩個前導零開始。另外我們知道,除非更改區塊的內容,否則哈希值不會發生更改。

所以我們將在區塊中引入一個稱為 隨機數 的新字段。隨機數會不斷變化,直到我們獲得滿足約束條件的哈希值。前導零的數量(在我們的例子中為值 2)決定了工作量證明算法的“難度”。您可能還注意到,我們的工作量證明很難計算,但在我們確定隨機數后很容易驗證(對于驗證,您只需要再次運行哈希函數即可):

class Blockchain: 
    # difficulty of PoW algorithm
    difficulty = 2

    """
    Previous code contd..
    """

    def proof_of_work(self, block): 
        """
        Function that tries different values of nonce to get a hash
        that satisfies our difficulty criteria.
        """
        block.nonce = 0

        computed_hash = block.compute_hash()
        while not computed_hash.startswith('0' * Blockchain.difficulty): 
            block.nonce += 1
            computed_hash = block.compute_hash()

        return computed_hash

請注意,沒有明確的邏輯來快速確定隨機數;只能通過暴力破解。

5

將區塊添加到鏈中

要將區塊添加到鏈中,首先需要驗證所提供的工作量證明是否正確,以及要添加的區塊的 previous_hash 字段是否指向鏈中的最新區塊的哈希值。

讓我們看看將區塊添加到鏈中的代碼:

class Blockchain: 
    """
    Previous code contd..
    """
    def add_block(self, block, proof): 
        """
        A function that adds the block to the chain after verification.
        """
        previous_hash = self.last_block.hash

        if previous_hash != block.previous_hash: 
            return False

        if not self.is_valid_proof(block, proof): 
            return False

        block.hash = proof
        self.chain.append(block)
        return True

    def is_valid_proof(self, block, block_hash): 
        """
        Check if block_hash is valid hash of block and satisfies
        the difficulty criteria.
        """
        return (block_hash.startswith('0' * Blockchain.difficulty) and
                block_hash == block.compute_hash())

挖礦

事務最初存儲在一個未確認事務池中。將未確認事務放入區塊中并計算工作量證明的過程被稱為區塊 挖礦 。一旦找到滿足我們的約束條件的隨機數,我們就可以說挖到了一個區塊,這個區塊就會放入區塊鏈中。

在大多數加密貨幣(包括比特幣)中,作為對耗費算力來計算工作量證明的獎勵,礦工可以獲得一些加密貨幣。以下是我們的挖礦函數的格式:

class Blockchain: 
    """
    Previous code contd...
    """

    def add_new_transaction(self, transaction): 
            self.unconfirmed_transactions.append(transaction)

    def mine(self): 
        """
        This function serves as an interface to add the pending
        transactions to the blockchain by adding them to the block
        and figuring out Proof of Work.
        """
        if not self.unconfirmed_transactions: 
            return False

        last_block = self.last_block

        new_block = Block(index=last_block.index + 1,
                          transactions=self.unconfirmed_transactions,
                          timestamp=time.time(),
                          previous_hash=last_block.hash)

        proof = self.proof_of_work(new_block)
        self.add_block(new_block, proof)
        self.unconfirmed_transactions = []
        return new_block.index

好了,我們的工作差不多完成了。您可以 在 GitHub 上查看截至目前的合并代碼

6

創建接口

現在為我們的節點創建接口,以便與其他對等節點以及我們將要構建的應用程序進行交互。我們將使用 Flask 創建一個 REST-API 來與我們的節點進行交互。以下是它的代碼:

from flask import Flask, request
import requests

app =  Flask(__name__)

# the node's copy of blockchain
blockchain = Blockchain()

我們的應用程序需要一個端點來提交新事務。我們的應用程序將使用此端點將新數據(帖子)添加到區塊鏈中:

@app.route('/new_transaction', methods=['POST'])
def new_transaction(): 
    tx_data = request.get_json()
    required_fields = ["author", "content"]

    for field in required_fields: 
        if not tx_data.get(field): 
            return "Invlaid transaction data", 404

    tx_data["timestamp"] = time.time()

    blockchain.add_new_transaction(tx_data)

    return "Success", 201

下面是返回節點的鏈副本的端點。我們的應用程序將使用此端點來查詢要顯示的所有帖子:

@app.route('/chain', methods=['GET'])
def get_chain(): 
    chain_data = []
    for block in blockchain.chain: 
        chain_data.append(block.__dict__)
    return json.dumps({"length": len(chain_data),
                       "chain": chain_data})

下面是請求節點挖掘未確認事務(如果有)的端點。我們將使用此端點從我們的應用程序自身發起一個挖礦命令:

@app.route('/mine', methods=['GET'])
def mine_unconfirmed_transactions(): 
    result = blockchain.mine()
    if not result: 
        return "No transactions to mine"
    return "Block #{} is mined.".format(result)


# endpoint to query unconfirmed transactions
@app.route('/pending_tx')
def get_pending_tx(): 
    return json.dumps(blockchain.unconfirmed_transactions)


app.run(debug=True, port=8000)

現在,您可以體驗一下我們的區塊鏈,創建一些事務,然后使用諸如 cURL 或 Postman 之類的工具來挖掘它們。

7

建立共識和去中心化

目前為止,我們實現的代碼只能在單個計算機上運行。即使通過哈希值鏈接了區塊,我們仍然不能信任單個實體。我們需要多個節點來維護我們的區塊鏈。 所以讓我們創建一個端點,以便讓一個節點了解網絡中的其他對等節點:

# the address to other participating members of the network
peers = set()

# endpoint to add new peers to the network.
@app.route('/add_nodes', methods=['POST'])
def register_new_peers(): 
    nodes = request.get_json()
    if not nodes: 
        return "Invalid data", 400
    for node in nodes: 
        peers.add(node)

    return "Success", 201

您可能已經認識到,在多節點方面存在一個問題。由于故意操縱或意外的原因,一些節點的鏈副本可能有所不同。在這種情況下,我們需要商定采用鏈的某個版本,以維持整個系統的完整性。我們需要達成共識。

一種簡單的共識算法可能是,在網絡中的不同參與者構成的鏈出現分歧時,商定采用最長的有效鏈。選擇此方法的理由是,最長的鏈是對已完成的最多工作量的有效估算:

def consensus(): 
    """
    Our simple consensus algorithm.如果找到一個更長的有效鏈,則用它替換我們的鏈。
    """
    global blockchain

    longest_chain = None
    current_len = len(blockchain)

    for node in peers: 
        response = requests.get('http://{}/chain'.format(node))
        length = response.json()['length']
        chain = response.json()['chain']
        if length > current_len and blockchain.check_chain_validity(chain): 
            current_len = length
            longest_chain = chain

    if longest_chain: 
        blockchain = longest_chain
        return True

    return False

最后,我們需要開發一種方法,讓任何節點向網絡宣布它已經挖到一個區塊,以便每個人都能更新他們的區塊鏈,并繼續挖掘其他事務。其他節點可以輕松地驗證工作量證明,并將它添加到各自的鏈中:

# endpoint to add a block mined by someone else to the node's chain.
@app.route('/add_block', methods=['POST'])
def validate_and_add_block(): 
    block_data = request.get_json()
    block = Block(block_data["index"], block_data["transactions"],
                  block_data["timestamp", block_data["previous_hash"]])

    proof = block_data['hash']
    added = blockchain.add_block(block, proof)

    if not added: 
        return "The block was discarded by the node", 400

    return "Block added to the chain", 201

    def announce_new_block(block): 
    for peer in peers: 
        url = "http://{}/add_block".format(peer)
        requests.post(url, data=json.dumps(block.__dict__, sort_keys=True))

announce_new_block 方法應在節點挖到每個區塊后調用,以便對等節點能將該區塊添加到自己的鏈中。

8

構建應用程序

好了,后端都設置好了。您可以 在 GitHub 上查看到目前為止的代碼

現在,是時候創建應用程序的接口了。我們使用了 Jinja2 模板來呈現網頁和一些 CSS,讓頁面看起來美觀一些。

我們的應用程序需要連接到區塊鏈網絡中的某個節點,以便抓取數據和提交新數據。也可能存在多個節點:

import datetime
import json

import requests
from flask import render_template, redirect, request

from app import app

.
CONNECTED_NODE_ADDRESS = "http://127.0.0.1:8000"

posts = []

fetch_posts 函數從節點的 / chain 端點獲取數據,解析該數據并將它們存儲在本地。

def fetch_posts(): 
    get_chain_address = "{}/chain".format(CONNECTED_NODE_ADDRESS)
    response = requests.get(get_chain_address)
    if response.status_code == 200: 
        content = []
        chain = json.loads(response.content)
        for block in chain["chain"]: 
            for tx in block["transactions"]: 
                tx["index"] = block["index"]
                tx["hash"] = block["previous_hash"]
                content.append(tx)

        global posts
        posts = sorted(content, key=lambda k: k['timestamp'],
                       reverse=True)

該應用程序有一個 HTML 表單,用于接受用戶輸入,然后向一個已連接的節點發出一個 POST 請求,以便將該事務添加到未確認事務池中。然后,通過網絡對該事務進行挖掘,最后在我們刷新網站后抓取該事務:

@app.route('/submit', methods=['POST'])
def submit_textarea(): 
    """
    Endpoint to create a new transaction via our application.
    """
    post_content = request.form["content"]
    author = request.form["author"]

    post_object = {
        'author': author,
        'content': post_content,
    }

    # Submit a transaction
    new_tx_address = "{}/new_transaction".format(CONNECTED_NODE_ADDRESS)

    requests.post(new_tx_address,
                  json=post_object,
                  headers={'Content-type': 'application/json'})

    return redirect('/')

9

運行應用程序

大功告成!可以 在 GitHub 上找到最終代碼

要運行該應用程序:

  1. 啟動一個區塊鏈節點服務器:
    python node_server.py
  2. 運行該應用程序:
    python run_app.py

您應該能在 http://localhost:5000 上看到正在運行的應用程序。

  1. 嘗試發布一些數據,您會看到類似下圖的結果:

    使用 Python 從零開始開發區塊鏈應用程序

     
  2. 單擊 Request to mine 按鈕,您會看到類似下圖的結果:

    使用 Python 從零開始開發區塊鏈應用程序

     
  3. 單擊 Resync 按鈕,您會看到應用程序與鏈重新同步:

    使用 Python 從零開始開發區塊鏈應用程序

驗證事務

您可能注意到了應用程序中的一個缺陷:任何人都能更改任何名稱和發布任何內容。解決此問題的一種方法是,使用 公私密鑰加密 來創建帳戶。每個新用戶都需要一個公鑰(類似于用戶名)和私鑰,才能在我們的應用程序中發布內容。這些密鑰可以充當數字簽名。公鑰只能對使用相應私鑰加密的內容進行解密。在將事務添加到任何區塊之前,會使用作者的公鑰對其進行驗證。這樣,我們就知道是誰寫的這條消息。

結束語

本教程介紹了公有區塊鏈的基礎知識。如果您一直在跟隨操作,那么您已經從零開始實現了一個區塊鏈,并構建了一個簡單應用程序來允許用戶在該區塊鏈上共享信息。

后續行動

您可以在云上創建多個節點,并完善您構建的應用程序。您可以 將任何 Flask 應用程序部署到 IBM Cloud

此外,您還可以使用諸如 ngrok 之類的隧道服務為您的 localhost 服務器創建一個公有 URL,然后您就能與多臺機器進行交互。

這一領域還有許多值得探索的地方!您可以通過以下方式繼續增強您的區塊鏈技能:

  • 親自體驗新的 IBM Blockchain Platform Starter Plan(免費測試版),繼續探索區塊鏈技術。您可以快速建立一個區塊鏈預生產網絡,部署樣本應用程序,開發和部署客戶端應用程序。 入門 ! 
  • 訪問 developerWorks 上的 區塊鏈開發人員中心 。可以在這里獲得開發和部署業務區塊鏈解決方案的工具和教程,以及代碼和社區支持。 
  • 學習 面向開發人員的區塊鏈基礎課程 ,了解資產轉移的復雜細節。學完這門持續 2 小時的免費自學課程后,參加測驗,獲取一枚徽章,并開始為您的業務網絡規劃有用的區塊鏈應用程序。 
  • 繼續學習 IBM Blockchain 開發人員基礎課程 ,這門持續 6 小時的免費課程在“區塊鏈基礎知識”的基礎上進行了擴展,更詳細地介紹了區塊鏈業務網絡的組件和架構,以及構建網絡和創建應用程序的經驗。 
  • 查閱許多 區塊鏈 Code Pattern ,它們提供了解決復雜問題的路線圖,包括概述、架構圖、流程、存儲庫指南和其他閱讀材料。

作者簡介

Satwik Kansal 是一位擁有區塊鏈技術和數據科學經驗的軟件開發人員。可以在 推ter、 Linkedin他的網站 上聯系他。

 

來自:http://www.ibm.com/developerworks/cn/cloud/library/cl-develop-blockchain-app-in-python/index.html?ca=drs-

 

 本文由用戶 tming888 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!