親自動手學習區塊鏈 Learn Blockchains by Building One

原文鏈接

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
     接受URLs格式的節點列表 
  • /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:5000http://localhost:5001

  

 

在節點2(http://127.0.0.1:5001)挖礦,多挖數次,確保它的鏈比節點1(http://localhost:5000)的鏈長。然後在節點1調用

GET  /nodes/resolve, 節點1上的鏈,通過和網上的其它節點,節點2上的鏈對比,根據共識算法進行更新,因爲節點2的鏈長。

 

 

 

完工了。。。。。。 邀請幾個朋友幫着測試一下你的區塊鏈。 

希望這可以激發你創建新東西。我對加密貨幣很狂愛,因爲我堅信區塊鏈將快速改變我們對於經濟,政府和保持記錄的看法。I  

Update: part 2 將後續開發。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章