用 Python 構建一個極小的區塊鏈

Gerald Nash發表於2018-03-17

雖然有些人認為區塊鏈是一個早晚會出現問題的解決方案,但是毫無疑問,這個創新技術是一個計算機技術上的奇蹟。那麼,究竟什麼是區塊鏈呢?

區塊鏈

比特幣Bitcoin或其它加密貨幣按時間順序公開地記錄交易的數字賬本。

更通俗的說,它是一個公開的資料庫,新的資料儲存在被稱之為區塊block的容器中,並被新增到一個不可變的chain中(因此被稱為區塊鏈blockchain),之前新增的資料也在該鏈中。對於比特幣或其它加密貨幣來說,這些資料就是一組組交易,不過,也可以是其它任何型別的資料。

區塊鏈技術帶來了全新的、完全數字化的貨幣,如比特幣和萊特幣Litecoin,它們並不由任何中心機構管理。這給那些認為當今的銀行系統是騙局並將最終走向失敗的人帶來了自由。區塊鏈也革命性地改變了分散式計算的技術形式,如以太坊Ethereum就引入了一種有趣的概念:智慧合約smart contract

在這篇文章中,我將用不到 50 行的 Python 2.x 程式碼實現一個簡單的區塊鏈,我把它叫做 SnakeCoin。

不到 50 行程式碼的區塊鏈

我們首先將從定義我們的區塊是什麼開始。在區塊鏈中,每個區塊隨同時間戳及可選的索引一同儲存。在 SnakeCoin 中,我們會儲存這兩者。為了確保整個區塊鏈的完整性,每個區塊都會有一個自識別的雜湊值。如在比特幣中,每個區塊的雜湊是該塊的索引、時間戳、資料和前一個區塊的雜湊值等資料的加密雜湊值。這裡提及的“資料”可以是任何你想要的資料。

import hashlib as hasher

class Block:
  def __init__(self, index, timestamp, data, previous_hash):
    self.index = index
    self.timestamp = timestamp
    self.data = data
    self.previous_hash = previous_hash
    self.hash = self.hash_block()
  
  def hash_block(self):
    sha = hasher.sha256()
    sha.update(str(self.index) + 
               str(self.timestamp) + 
               str(self.data) + 
               str(self.previous_hash))
    return sha.hexdigest()

真棒,現在我們有了區塊的結構了,不過我們需要建立的是一個區塊鏈。我們需要把區塊新增到一個實際的鏈中。如我們之前提到過的,每個區塊都需要前一個區塊的資訊。但問題是,該區塊鏈中的第一個區塊在哪裡?好吧,這個第一個區塊,也稱之為創世區塊,是一個特別的區塊。在很多情況下,它是手工新增的,或透過獨特的邏輯新增的。

我們將建立一個函式來簡單地返回一個創世區塊解決這個問題。這個區塊的索引為 0 ,其包含一些任意的資料值,其“前一雜湊值”引數也是任意值。

import datetime as date

def create_genesis_block():
  # Manually construct a block with
  # index zero and arbitrary previous hash
  return Block(0, date.datetime.now(), "Genesis Block", "0")

現在我們可以建立創世區塊了,我們需要一個函式來生成該區塊鏈中的後繼區塊。該函式將獲取鏈中的前一個區塊作為引數,為要生成的區塊建立資料,並用相應的資料返回新的區塊。新的區塊的雜湊值來自於之前的區塊,這樣每個新的區塊都提升了該區塊鏈的完整性。如果我們不這樣做,外部參與者就很容易“改變過去”,把我們的鏈替換為他們的新鏈了。這個雜湊鏈起到了加密的證明作用,並有助於確保一旦一個區塊被新增到鏈中,就不能被替換或移除。

def next_block(last_block):
  this_index = last_block.index + 1
  this_timestamp = date.datetime.now()
  this_data = "Hey! I'm block " + str(this_index)
  this_hash = last_block.hash
  return Block(this_index, this_timestamp, this_data, this_hash)

這就是主要的部分。

現在我們能建立自己的區塊鏈了!在這裡,這個區塊鏈是一個簡單的 Python 列表。其第一個的元素是我們的創世區塊,我們會新增後繼區塊。因為 SnakeCoin 是一個極小的區塊鏈,我們僅僅新增了 20 個區塊。我們透過迴圈來完成它。

# Create the blockchain and add the genesis block
blockchain = [create_genesis_block()]
previous_block = blockchain[0]

# How many blocks should we add to the chain
# after the genesis block
num_of_blocks_to_add = 20

# Add blocks to the chain
for i in range(0, num_of_blocks_to_add):
  block_to_add = next_block(previous_block)
  blockchain.append(block_to_add)
  previous_block = block_to_add
  # Tell everyone about it!
  print "Block #{} has been added to the blockchain!".format(block_to_add.index)
  print "Hash: {}\n".format(block_to_add.hash) 

讓我們看看我們的成果:

用 Python 構建一個極小的區塊鏈

別擔心,它將一直新增到 20 個區塊

很好,我們的區塊鏈可以工作了。如果你想要在主控臺檢視更多的資訊,你可以編輯其完整的原始碼並輸出每個區塊的時間戳或資料。

這就是 SnakeCoin 所具有的功能。要使 SnakeCoin 達到現今的產品級的區塊鏈的高度,我們需要新增更多的功能,如伺服器層,以在多臺機器上跟蹤鏈的改變,並透過工作量證明演算法(POW)來限制給定時間週期內可以新增的區塊數量。

如果你想了解更多技術細節,你可以在這裡檢視最初的比特幣白皮書

讓這個極小區塊鏈稍微變大些

這個極小的區塊鏈及其簡單,自然也相對容易完成。但是因其簡單也帶來了一些缺陷。首先,SnakeCoin 僅能執行在單一的一臺機器上,所以它相距分散式甚遠,更別提去中心化了。其次,區塊新增到區塊鏈中的速度同在主機上建立一個 Python 物件並新增到列表中一樣快。在我們的這個簡單的區塊鏈中,這不是問題,但是如果我們想讓 SnakeCoin 成為一個實際的加密貨幣,我們就需要控制在給定時間內能建立的區塊(和幣)的數量。

從現在開始,SnakeCoin 中的“資料”將是交易資料,每個區塊的“資料”欄位都將是一些交易資訊的列表。接著我們來定義“交易”。每個“交易”是一個 JSON 物件,其記錄了幣的傳送者、接收者和轉移的 SnakeCoin 數量。注:交易資訊是 JSON 格式,原因我很快就會說明。

{
  "from": "71238uqirbfh894-random-public-key-a-alkjdflakjfewn204ij",
  "to": "93j4ivnqiopvh43-random-public-key-b-qjrgvnoeirbnferinfo",
  "amount": 3
}

現在我們知道了交易資訊看起來的樣子了,我們需要一個辦法來將其加到我們的區塊鏈網路中的一臺計算機(稱之為節點)中。要做這個事情,我們會建立一個簡單的 HTTP 伺服器,以便每個使用者都可以讓我們的節點知道發生了新的交易。節點可以接受 POST 請求,請求資料為如上的交易資訊。這就是為什麼交易資訊是 JSON 格式的:我們需要它們可以放在請求資訊中傳遞給伺服器。

$ pip install flask # 首先安裝 Web 伺服器框架
from flask import Flask
from flask import request
node = Flask(__name__)

# Store the transactions that
# this node has in a list
this_nodes_transactions = []

@node.route('/txion', methods=['POST'])
def transaction():
  if request.method == 'POST':
    # On each new POST request,
    # we extract the transaction data
    new_txion = request.get_json()
    # Then we add the transaction to our list
    this_nodes_transactions.append(new_txion)
    # Because the transaction was successfully
    # submitted, we log it to our console
    print "New transaction"
    print "FROM: {}".format(new_txion['from'])
    print "TO: {}".format(new_txion['to'])
    print "AMOUNT: {}\n".format(new_txion['amount'])
    # Then we let the client know it worked out
    return "Transaction submission successful\n"

node.run()

真棒!現在我們有了一種儲存使用者彼此傳送 SnakeCoin 的記錄的方式。這就是為什麼人們將區塊鏈稱之為公共的、分散式賬本:所有的交易資訊儲存給所有人看,並被儲存在該網路的每個節點上。

但是,有個問題:人們從哪裡得到 SnakeCoin 呢?現在還沒有辦法得到,還沒有一個稱之為 SnakeCoin 這樣的東西,因為我們還沒有建立和分發任何一個幣。要建立新的幣,人們需要“挖”一個新的 SnakeCoin 區塊。當他們成功地挖到了新區塊,就會建立出一個新的 SnakeCoin ,並獎勵給挖出該區塊的人(礦工)。一旦挖礦的礦工將 SnakeCoin 傳送給別人,這個幣就流通起來了。

我們不想讓挖新的 SnakeCoin 區塊太容易,因為這將導致 SnakeCoin 太多了,其價值就變低了;同樣,我們也不想讓它變得太難,因為如果沒有足夠的幣供每個人使用,它們對於我們來說就太昂貴了。為了控制挖新的 SnakeCoin 區塊的難度,我們會實現一個工作量證明Proof-of-Work(PoW)演算法。工作量證明基本上就是一個生成某個專案比較難,但是容易驗證(其正確性)的演算法。這個專案被稱之為“證明”,聽起來就像是它證明了計算機執行了特定的工作量。

在 SnakeCoin 中,我們建立了一個簡單的 PoW 演算法。要建立一個新區塊,礦工的計算機需要遞增一個數字,當該數字能被 9 (“SnakeCoin” 這個單詞的字母數)整除時,這就是最後這個區塊的證明數字,就會挖出一個新的 SnakeCoin 區塊,而該礦工就會得到一個新的 SnakeCoin。

# ...blockchain
# ...Block class definition

miner_address = "q3nf394hjg-random-miner-address-34nf3i4nflkn3oi"

def proof_of_work(last_proof):
  # Create a variable that we will use to find
  # our next proof of work
  incrementor = last_proof + 1
  # Keep incrementing the incrementor until
  # it's equal to a number divisible by 9
  # and the proof of work of the previous
  # block in the chain
  while not (incrementor % 9 == 0 and incrementor % last_proof == 0):
    incrementor += 1
  # Once that number is found,
  # we can return it as a proof
  # of our work
  return incrementor

@node.route('/mine', methods = ['GET'])
def mine():
  # Get the last proof of work
  last_block = blockchain[len(blockchain) - 1]
  last_proof = last_block.data['proof-of-work']
  # Find the proof of work for
  # the current block being mined
  # Note: The program will hang here until a new
  #       proof of work is found
  proof = proof_of_work(last_proof)
  # Once we find a valid proof of work,
  # we know we can mine a block so 
  # we reward the miner by adding a transaction
  this_nodes_transactions.append(
    { "from": "network", "to": miner_address, "amount": 1 }
  )
  # Now we can gather the data needed
  # to create the new block
  new_block_data = {
    "proof-of-work": proof,
    "transactions": list(this_nodes_transactions)
  }
  new_block_index = last_block.index + 1
  new_block_timestamp = this_timestamp = date.datetime.now()
  last_block_hash = last_block.hash
  # Empty transaction list
  this_nodes_transactions[:] = []
  # Now create the
  # new block!
  mined_block = Block(
    new_block_index,
    new_block_timestamp,
    new_block_data,
    last_block_hash
  )
  blockchain.append(mined_block)
  # Let the client know we mined a block
  return json.dumps({
      "index": new_block_index,
      "timestamp": str(new_block_timestamp),
      "data": new_block_data,
      "hash": last_block_hash
  }) + "\n"

現在,我們能控制特定的時間段內挖到的區塊數量,並且我們給了網路中的人新的幣,讓他們彼此傳送。但是如我們說的,我們只是在一臺計算機上做的。如果區塊鏈是去中心化的,我們怎樣才能確保每個節點都有相同的鏈呢?要做到這一點,我們會使每個節點都廣播其(儲存的)鏈的版本,並允許它們接受其它節點的鏈。然後,每個節點會校驗其它節點的鏈,以便網路中每個節點都能夠達成最終的鏈的共識。這稱之為共識演算法consensus algorithm

我們的共識演算法很簡單:如果一個節點的鏈與其它的節點的不同(例如有衝突),那麼最長的鏈保留,更短的鏈會被刪除。如果我們網路上的鏈沒有了衝突,那麼就可以繼續了。

@node.route('/blocks', methods=['GET'])
def get_blocks():
  chain_to_send = blockchain
  # Convert our blocks into dictionaries
  # so we can send them as json objects later
  for block in chain_to_send:
    block_index = str(block.index)
    block_timestamp = str(block.timestamp)
    block_data = str(block.data)
    block_hash = block.hash
    block = {
      "index": block_index,
      "timestamp": block_timestamp,
      "data": block_data,
      "hash": block_hash
    }
  # Send our chain to whomever requested it
  chain_to_send = json.dumps(chain_to_send)
  return chain_to_send

def find_new_chains():
  # Get the blockchains of every
  # other node
  other_chains = []
  for node_url in peer_nodes:
    # Get their chains using a GET request
    block = requests.get(node_url + "/blocks").content
    # Convert the JSON object to a Python dictionary
    block = json.loads(block)
    # Add it to our list
    other_chains.append(block)
  return other_chains

def consensus():
  # Get the blocks from other nodes
  other_chains = find_new_chains()
  # If our chain isn't longest,
  # then we store the longest chain
  longest_chain = blockchain
  for chain in other_chains:
    if len(longest_chain) < len(chain):
      longest_chain = chain
  # If the longest chain wasn't ours,
  # then we set our chain to the longest
  blockchain = longest_chain

我們差不多就要完成了。在執行了完整的 SnakeCoin 伺服器程式碼之後,在你的終端可以執行如下程式碼。(假設你已經安裝了 cCUL)。

1、建立交易

curl "localhost:5000/txion" \
-H "Content-Type: application/json" \
-d '{"from": "akjflw", "to":"fjlakdj", "amount": 3}'

2、挖一個新區塊

curl localhost:5000/mine

3、 檢視結果。從客戶端視窗,我們可以看到。

用 Python 構建一個極小的區塊鏈

對程式碼做下美化處理,我們看到挖礦後我們得到的新區塊的資訊:

{
"index": 2,
"data": {
"transactions": [
{
"to": "fjlakdj",
"amount": 3,
"from": "akjflw"
},
{
"to": "q3nf394hjg-random-miner-address-34nf3i4nflkn3oi",
"amount": 1,
"from": "network"
}
],
"proof-of-work": 36
},
"hash": "151edd3ef6af2e7eb8272245cb8ea91b4ecfc3e60af22d8518ef0bba8b4a6b18",
"timestamp": "2017-07-23 11:23:10.140996"
}

大功告成!現在 SnakeCoin 可以執行在多個機器上,從而建立了一個網路,而且真實的 SnakeCoin 也能被挖到了。

你可以根據你的喜好去修改 SnakeCoin 伺服器程式碼,並問各種問題了。

在下一篇(LCTT 譯註:截止至本文翻譯,作者還沒有寫出下一篇),我們將討論建立一個 SnakeCoin 錢包,這樣使用者就可以傳送、接收和儲存他們的 SnakeCoin 了。

用 Python 構建一個極小的區塊鏈

相關文章