Python 模擬簡單區塊鏈
首先這是說明一下這是Tiny熊老師的教程https://www.cnblogs.com/tinyxiong
另外還要說明一下,暑假指導老師讓我們做一些關於區塊鏈的應用。這裡只是涉及極其簡單的模擬,主要是記錄這些天自己學習的知識。
什麼是區塊鏈?
下面簡單說一下區塊鏈是什麼,做個比喻,區塊就像一個人,區塊鏈好比一群人。怎麼才能讓一群人聯絡起來哪,就必須讓他們之間有一定的關係和聯絡。比如,第一個人有100塊錢,存入銀行。銀行給了他一張“憑證”,但是這張憑證被第二個人拿走了,第三個人的憑證被第四個。。。。
如此一來這麼多人就被聯絡起來。咱們這次實現的簡單的區塊鏈就是本區塊帶有上一區塊的雜湊。
先簡單定義一下區塊的內容:
# { # "index": 1, 區塊的塊號 # "timestamp": "", 時間戳 # "transactions": [ 交易內容 # { # "sender": "", # "recipient": "", # "amount": 5, # } # ], # "proof": "", 工作量證明 # "previous_hash":"" 上一個區塊的hash # # }
本次才用的是Python Flask框架,使用雲端MongoDB ,
簡單說一下需要準備的有,PyCharm , pip , Python 3.7。
使用PyCharm 建立一個PyThon虛擬環境 。點選Create New Project 。選擇資料夾,預設選項就是在本資料夾安裝虛擬環境。
然後就是各種包
import hashlib # hash 演算法 import json # josn from time import time # 時間戳 from uuid import uuid4 # uuid為本機生成ID from flask import Flask, jsonify, requestimport pymongo
我們設想一下,資料要儲存在什麼地方才能在下次啟動程式的時候繼續按照上一次結束的資料進行下一次的運算。因此我們需要使用資料庫儲存我們需要儲存的資料。所以我們要先連線資料庫。
# **User**:**password** 這是你建立叢集的使用者和密碼
client = pymongo.MongoClient('mongodb+srv://**User**:**password**@iec-pj8qn.mongodb.net/MainSite') db = client.MainSite # collection = db.blockchain
現在資料庫已經連線上了,但是問題來了。我們怎麼取出最底層的文件哪?下面我們需要一個迴圈遍歷集合的最大值,回想一下我們定義的區塊結構。裡面定義的 index:1 。 每次新增一個區塊,第二個區塊的index = 2 . 一次增加下去。這樣遍歷的最大值,也可以說是遍歷的次數就是我們需要尋找的index:last
,也就是最後一次被插入的資料。MongoDB 在沒有給定特定的_id 欄位時,自己會生成一個類似與時間戳的欄位。這不是我們需要的,我們在取出資料的時候要把他剔除。
class value: # 取出文件的資料再次組合並存儲在current[] 列表裡 def value(self, index1, hash1, proof, transactions1, timestamp) -> list: current = [] json_value = { 'index': index1, 'previous_hash': hash1, 'proof': proof, 'transactions': transactions1, 'timestamp': timestamp } current.append(json_value) return current class counting: # 迴圈遍歷集合最大值 def count(self): last_count = 0 for x in collection.find(): # collection.find() 集合的所有文件 last_count = last_count + 1 return last_count last1 = counting() # 呼叫counting類 last = last1.count() print(last) result = collection.find_one({"index": last}) # 搜尋到最後一個文件 value = value() # 建立value物件 chain0 = value.value(result['index'], result['previous_hash'], result['proof'], result['transactions'], result['timestamp']) # dict 轉 list print(chain0) print(type(chain0)) client.close() # 連線斷開
現在我們已經獲取都最近一次插入的資料。我們現在就可以插入創始區塊了:
{ "index": 1, "previous_hash": 1, "proof": 100, "timestamp": 1541940889.5927348, "transactions": [] }
把這段josn插入MongoDB , 或許你可以在網頁插入或者在本地下載一個 MongoDB Shell 這個雲端MongoDB 有提示怎麼下載,這裡我就不多說了。如果不明白MongoDB 的用法請去 菜鳥教程
把創始區塊插入集合,啟動一下。出現你插入的資料,說明你已經成功的連線資料庫。
下面我們來看一大段程式碼:
class Blockchain: def __init__(self): # 建構函式,初始區塊鏈,當前交易,生成創始區塊 self.chain = [] # 真正的區塊鏈 self.chain.append(chain0[0]) # 每次連線取最後一個集合文件作為本次的啟動創世區塊 self.current_transactions = [] # self.new_block(proof=100, previous_hash=1) # 如果沒有用到資料庫,呼叫建構函式,自行建立一個創世區塊 def new_block(self, proof, previous_hash=None, last_index=None): # 新建區塊 工作量證明,前一個區塊Hash # 定義交易區塊實體 block = { 'index': last_index + 1, 'timestamp': time(), 'transactions': self.current_transactions, # 當前交易 'proof': proof, 'previous_hash': previous_hash or self.hash(self.last_block) } self.current_transactions = [] # 清空當前交易 self.chain.append(block) # 區塊鏈新增新區塊 return block def new_transactions(self, sender, recipient, amount) -> int: # 新的交易 self.current_transactions.append( # 當前交易新增資料 { 'sender': sender, 'recipient': recipient, 'amount': amount } ) return self.last_block['index'] + 1 # 最後一個區塊的 index+1 @staticmethod def hash(block): # 區塊hash演算法 block_string = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_string).hexdigest() @property def last_block(self): # 最後一個區塊# 取出的最後一個區塊型別總是 list long = len(self.chain) print(long) if long > 1: last_block = self.chain[-1] print('++++++++++++++++++++++++') print(last_block) print(type(last_block)) print(last_block['index']) temp_json = { 'index': last_block['index'], 'previous_hash': last_block['previous_hash'], 'proof': last_block['proof'], 'transactions': last_block['transactions'], 'timestamp': last_block['timestamp'] } print(temp_json) self.chain.append(temp_json) print(self.chain) print(type(self.chain[-1])) return self.chain[-1] def proof_of_work(self, last_proof: int) -> int: # 工作量證明 proof = 0 while self.valid_proof(last_proof, proof) is False: # 迴圈檢測合格hash proof += 1 # print(proof) return proof def valid_proof(self, last_proof: int, proof: int) -> bool: # 有效工作量證明 guess = f'{last_proof}{proof}'.encode() guess_hash = hashlib.sha256(guess).hexdigest() # 雜湊後得到摘要 # print(guess_hash) if guess_hash[0:4] == "0000": # 工作量證明條件 return True else: return False
上面的類,基本上都有註釋,並且時我測試時的斷點也有保留,更清晰的瞭解資料的型別和數值。我就不一一口述了。簡單的說一下就是 交易方法def new_transactions, 和 新建塊的打包計算def new_block
計算hash def hash(block): 有效工作量證明def hash(block) 。
本地資料夾建立static 資料夾 加入index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>index</title> </head> <body> <p>Hello BlockChain</p> <form action="transactions" method="post"> sender:<input type="text" name="sender"> recipient:<input type="text" name="recipient"> amount:<input type="text" name="amount"> <input type="submit" value="submit"> </form> </body> </html>
下面開始使用我們的Flask框架
app = Flask(__name__, static_url_path='') # 引數的意思是為靜態html檔案,新增路徑
Flask框架
app = Flask(__name__, static_url_path='') # 引數的意思是為靜態html檔案,新增路徑
blockchain = Blockchain() # 建立物件
node_identifier = str(uuid4()).replace('-', '') # 使用uuid生成本結點ID,replace()替換'-'
@app.route('/', methods=['GET']) def index(): return app.send_static_file('index.html') @app.route('/transactions', methods=['POST']) def new_transaction(): # 新的交易 print(request) sender = request.form['sender'] # 取出 Form 裡的值 print(sender) recipient = request.form['recipient'] print(recipient) amount = request.form['amount'] print(amount) values = [sender, recipient, amount] index = blockchain.new_transactions(values[0], # 呼叫函式 values[1], values[2]) response = {"message": f'Transaction will be added to Block {index}'} return jsonify(response), 201 @app.route('/mine', methods=['GET']) def mine(): # 交易打包,挖礦 last_block = blockchain.last_block print("=======================") print(type(last_block)) print(last_block) last_proof = last_block['proof'] print(last_proof) last_index = last_block['index'] print(last_index) proof = blockchain.proof_of_work(last_proof) # 工作量證明 也就是挖礦 blockchain.new_transactions(sender="0", # 給本結點的獎勵 recipient=node_identifier, amount=1) block = blockchain.new_block(proof, None, last_index) # 打包區塊 client = pymongo.MongoClient('mongodb+srv://**user**:**password**@iec-pj8qn.mongodb.net/MainSite') db = client.MainSite collection = db.blockchain collection.insert_one(block) # 把打包好的區塊新增到資料庫。 client.close() response = { "message": "New Block Forged", "index": block['index'], "transactions": block['transactions'], "proof": block['proof'], "previous_hash": block['previous_hash'] } return jsonify(response), 200 @app.route('/chain', methods=['GET']) def full_chain(): # 返回區塊鏈 response = { 'chain': blockchain.chain, 'length': len(blockchain.chain) } return jsonify(response), 200 if __name__ == '__main__': # 當 block chain.py 作為模組時不執行下面函式 app.run(host='127.0.0.1', port=3000)
把所有的程式碼塊合在一起就是本次區塊鏈模擬器的全部內容了,這只是單節點,單儲存的鏈,只是更好的去理解區塊鏈的結構,還有比如共識機制和選擇鏈我們還沒有去完成。
對了 說一下用法:
提交後: http://127.0.0.1:3000/mine
之後開啟 PowerShell
結束。