【譯】本身動手寫區塊鏈

可以點進這篇文章,說明你也像我同樣對加密貨幣的興起十分激動,並想了解加密貨幣的支撐技術---區塊鏈是如何工做的。 但理解區塊鏈並不那麼輕鬆,至少對我來講如此。我看了不少相關的視頻和教程,卻沮喪地發現實例真是太少了。 我喜歡經過實踐學習。這種方式使我在代碼層面思考問題,並發現關鍵所在。若是你和我同樣,那麼在本文結尾你將構建一個功能完備的區塊鏈並對其工做機制有深入的理解。node

寫在開始以前。。。

首先,區塊鏈是一系列稱做區塊(Block)的結構順序連接而成的不可改變的記錄。塊中能夠包含交易記錄、文件或者其餘任何你想存儲的數據。須要注意的是塊與塊之間經過hash值連接。若是你不清楚hash是什麼,請參考What Are Hash Functionspython

**本文適合哪些人看?**你應該懂得一些基本的Python知識,同時也應該對HTTP請求有所理解,由於咱們的區塊鏈是運行在HTTP協議之上的。git

**我須要準備什麼?**請確保Python3.6及以上版本和pip工具已經安裝。還須要安裝Flask和requests庫。github

pip install Flask==0.12.2 requests==2.18.4

對了,你還須要一個HTTP客戶端,好比Postman或者cURL,固然,其餘的也能夠。算法

最終的代碼哪裏能夠獲取?點擊這裏json

第一步:構建區塊鏈

建立一個新的Python文件,名爲blockchain.py,咱們全部的邏輯都在一個文件完成。flask

表示一個區塊鏈

咱們建立一個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類負責管理整個區塊鏈,它會存儲交易併爲新增區塊等操做提供輔助方法。下面,咱們來實現這些方法。網絡

區塊是什麼

每一個區塊都有一個索引(index),一個時間戳(timestamp),一系列交易,一個工做量證實(稍後詳述)和前置區塊的哈希值。下面是單個區塊的一個簡單實例:併發

block = {
    'index': 1,
    'timestamp': 1506057125.900785,
    'transactions': [
        {
            'sender': "8527147fe1f5426f9dd545de4b27ee00",
            'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
            'amount': 5,
        }
    ],
    'proof': 324984774000,
    'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}

顯而易見,全部的區塊會構成一條鏈---由於每一個區塊都保存了前一區塊的hash值。這就是區塊鏈不可篡改的重要緣由:若是攻擊者損壞了某一區塊,那麼後面全部的區塊都會做廢。 若是你不明白上面的話,請花一些時間理解,由於這是區塊鏈的核心思想。

向區塊添加交易

咱們須要一個方法來向區塊中添加交易記錄,這裏命名爲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類初始化的時候,咱們須要產生一個創世區塊(genesis block,即沒有前置區塊的區塊)做爲區塊鏈的第一個區塊。咱們還須要添加一個proof字段在創世區塊中做爲挖礦的結果(或者說本次工做量的證實),咱們將在後文繼續討論挖礦。 除了產生創世區塊,咱們還須要完成一些其餘輔助方法(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()

上面的代碼十分直白,我還添加了一些註釋幫助理解。咱們幾乎完成了表示一個區塊鏈的工做。但此時,你應該思考下一個區塊是如何產生或者說被開採出來的。

理解工做量證實機制(Proof of Work)

工做量證實(PoW)算法是用來產生或開採區塊的一種機制,PoW的目標是找到一個符合要求的數字,從算力的角度來講這個數字對任何人來講都很難找到卻十分容易驗證(是否符合要求)。這就是PoW算法的核心思想。 咱們舉一個很是簡單的例子來幫助理解: 假定咱們須要找到一個整數y,使得他和整數x的乘積的哈希值以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,由於:

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

在比特幣世界中,PoW算法被稱爲哈希現金(Hashcash),它和咱們上面的例子沒有本質區別。在這算法中,礦工們開始瞭解決問題的競賽,優勝者能夠產生一個新的區塊。一般來講,難度取決於限制字符的數量。礦工將會由於找到一個合法的解答收到一些比特幣做爲獎勵,整個比特幣網絡可以很容易驗證礦工挖掘的區塊是否合法有效。

實現基本的PoW算法

下面來爲咱們的區塊鏈實現一個相似的算法,咱們的規則將會和上面的例子十分接近:找到一個數p,使得它與前置區塊的哈希值由4個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請求與區塊鏈進行交互。

第二步:將區塊鏈做爲API

咱們將使用Flask,它是一個輕量級的框架,能夠很容易將一個網絡節點映射爲Python函數,這讓咱們能夠經過HTTP請求與區塊鏈交互。咱們將建立如下方法:

  • /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)

下面是簡單的解釋:

  • 15行:實例化節點,關於Flask點擊Quick Start
  • 18行:爲節點建立一個隨機名字
  • 21行:實例化BlockChain
  • 24-26行:建立/mine節點,這是一個GET請求。
  • 28-30行:建立/transactions/new節點,由於須要發送數據,因此是POST請求。
  • 32-38行:建立/chain節點,返回整個區塊鏈
  • 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. 計算執行PoW算法
  2. 經過添加一筆交易獎勵礦工1比特幣
  3. 產生新的區塊並添加入鏈
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類的方法交互。咱們已經完成了這部分,如今能夠開始和區塊鏈交互了。

第三步:和區塊鏈交互

你可使用簡單古老的cURL或者Postman來和這些網絡中的API交互,首先啓動服務器:

$ python blockchain.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

讓咱們發送一個GET請求來開採一個區塊:

http://localhost:5000/mine

Using Postman to make a GET request

再向http://localhost:5000/transactions/new發送一個POST請求,參數是JSON格式的交易數據:

Using Postman to make a POST request

若是不想用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
}

第四步:共識機制

咱們已經擁有了一個能接收交易的初級區塊鏈,而且可以開採出新的區塊。但整個區塊鏈最核心的是去中心化,若是去中心了,咱們又如何保證全部節點對應的是統一區塊鏈呢?這就是共識問題,若是咱們但願網絡中有不止一個節點,就必須實現共識算法。

註冊新節點

在實現共識算法以前,咱們須要讓節點知道有其餘節點加入了網絡。網絡中的每個節點應該存留其餘所有節點的註冊表,所以咱們須要一些其餘的服務器節點:

  1. /nodes/register用來從URL中接收一系列節點
  2. /nodes/resolve實現共識算法,並解決衝突以保證節點擁有正確的鏈 咱們須要修改BlockChain類的構造器並提供一個方法來註冊節點:
...
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()經過遍歷每一區塊並檢查proof和hash的正確與否判斷鏈的有效性。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

如今你能夠用不一樣的計算機來構建網絡中的這些節點,固然也能夠用同一機器的不一樣端口。例如,將5001端口也註冊進區塊鏈網絡:

Registering a new Node

如今,若是我在第二個節點開採一個新的區塊,當我在節點1調用GET /nodes/resolve的時候,共識算法會保證鏈被更新到現有網絡中的最長鏈:

Consensus Algorithm at Work

如今你能夠找一些朋友來和你一塊兒測試這個區塊鏈了。

後記

我但願這篇文章可以激發你的靈感,畢竟我對加密貨幣十分狂熱,我相信他會改變咱們對金融、政府和記錄存儲的思考方式。

Update:我計劃寫這個話題的第二部分,我將進一步拓展這個區塊鏈並支持交易驗證( Transaction Validation Mechanism),同時也將討論如何將你的區塊鏈產業化。

相關文章
相關標籤/搜索