區(qū)塊鏈的文章看了很多是不是還是有些云里霧里的感覺 坡垫,在對區(qū)塊鏈概念有了基本了解之后,筆者建議有基礎(chǔ)的同學(xué)可以動手寫一個區(qū)塊鏈阴汇,這樣才能更深刻的理解區(qū)塊鏈的本質(zhì)。本文是寫給有相應(yīng)python和區(qū)塊鏈基礎(chǔ)的同學(xué)教給大家如何通過python從零開始構(gòu)建區(qū)塊鏈节槐。
前導(dǎo)知識儲備
1. Python
測試環(huán)境
#此為本機環(huán)境搀庶,代碼在本機環(huán)境已調(diào)試 運行無誤
1. python3.6.3
2. flask0.12.2
3. requests2.18.4
4. postman
代碼結(jié)構(gòu)
整個區(qū)塊鏈主要包括兩個方面,一塊是區(qū)塊鏈核心代碼铜异,另外一塊是節(jié)點服務(wù)器相關(guān)的接口哥倔,接下來分別就這兩部分進(jìn)行介紹。
1. 區(qū)塊鏈核心代碼
2. 節(jié)點服務(wù)器接口
區(qū)塊鏈核心代碼
首先我們看下最核心的區(qū)塊鏈核心代碼結(jié)構(gòu)揍庄,對整體框架有個感性認(rèn)識咆蒿,之后我會分塊進(jìn)行完善。方便展示我調(diào)整了下格式蚂子。
class BlockChain(object):
????def __init__(self):pass
? ? def new_block(self):pass
????def new_transaction(self):pass
????def proof_of_work(self):pass
????def register_node(self):pass
????def valid_chain(self, chain):pass
????def resolve_conflicts(self):pass
? ? def hash(block):pass
? ? def valid_proof(last_proof, proof):pass
????def last_block(self): pass
__init__: 初始化
def __init__(self):
? ? self.chain = []
? ? self.current_transactions = []
? ? self.nodes = set()
? ? #創(chuàng)世區(qū)塊構(gòu)建
????self.new_block(previous_hash=1, proof=100)
初始化函數(shù)中沃测,定義了兩個列表,chain用來存儲區(qū)塊鏈食茎,current_transactions用來存儲交易信息蒂破。集合nodes用來存儲節(jié)點信息。實例化類的時候别渔,同時初始化產(chǎn)生第一個區(qū)塊附迷,即創(chuàng)世區(qū)塊。
new_block: 生成新的區(qū)塊
def new_block(self, proof, previous_has=None):
? ? block = {
? ? ? ? 'index': len(self.chain),
? ? ? ? 'timestamp': time.time(),
? ? ? ? 'transactions': self.current_transactions,
? ? ? ? 'proof': proof,
? ? ? ? 'previous_hash': previous_hash or self.hash(self.chain[-1]),
? ? }
? ? self.current_transactions = {}
? ? self.chain.append(block)
? ? return block
new_block主要用來生成一個新快哎媚,并添加到區(qū)塊鏈的尾部喇伯,這里我們可以看到一個區(qū)塊的結(jié)構(gòu)被定義為一個字典。區(qū)塊結(jié)構(gòu)包括5類關(guān)鍵信息
1. index:當(dāng)前區(qū)塊的編號拨与,區(qū)塊編號從創(chuàng)世區(qū)塊開始遞增
2. timestamp: 當(dāng)前時間戳稻据,生成該區(qū)塊時候的時間
3. transactions: 未加到區(qū)塊的交易信息列表
4. proof:工作量證明
5. previous_hash: 前一個區(qū)塊的哈希值
這是簡單的模擬一個區(qū)塊的結(jié)構(gòu),與比特幣區(qū)塊鏈有些差別买喧。更詳細(xì)的區(qū)塊結(jié)構(gòu)信息請參考我之前寫過的文章淺出區(qū)塊鏈攀甚,需要 注意的是區(qū)塊哈希值并不在區(qū)塊的數(shù)據(jù)結(jié)構(gòu)里
new_transaction: 產(chǎn)生新的交易
def new_transaction(self, sender, receiver, amount):
? ? self.current_transactions.append({
? ? ? ? 'sender': sender,
? ? ? ? 'receiver': receiver,
? ? ? ? 'amount': amount
? ? })
? ? return self.last_block['index']+1
一個交易的數(shù)據(jù)結(jié)構(gòu)包括三部分
1. sender:發(fā)送發(fā)地址
2. receiver:接收方地址
3. amount:數(shù)量
函數(shù)里將交易數(shù)據(jù)加入到交易列表里,并返回即將通過挖礦產(chǎn)生的記錄當(dāng)前交易的下一個區(qū)塊的index岗喉。
proof_of_work: 工作量證明
def proof_of_work(self, last_proof):
? ? proof = 0
? ? while(self.valid_proof(last_proof,proof) is False):
? ? ? ? proof += 1
? ? return proof
工作量證明算法是為了找出一個符合特定條件的數(shù)字秋度,作為節(jié)點獲取當(dāng)前區(qū)塊記賬權(quán)的工作量證明
valid_proof: 工作量有效證明
@staticmethod
def valid_proof(last_proof, proof):
? ? guess = '{0}{1}'.format(last_proof, proof).encode()
? ? guess_hash = hashlib.sha256(guess).hexdigest()
? ? return guess_hash[:4] == '0000'
當(dāng)前規(guī)則是:基于上一個區(qū)塊工作量和當(dāng)前區(qū)塊工作量所生成的哈希值前四位為'0000'即為有效工作量。通過改變這一條件钱床,可以調(diào)整難度荚斯。在比特幣區(qū)塊鏈中有動態(tài)調(diào)節(jié)機制,保證平均10分鐘左右生成一個區(qū)塊。
register_node: 注冊節(jié)點
def register_node(self, address):
? ? parsed_url = urlparse(address)
? ? return nodes.add(parsed_url.netloc)
urlparse函數(shù)將地址進(jìn)行解析事期,結(jié)果示例如下
In? ? [1]: urlparse('http://192.168.1.1:5000')
Out [1]: ParseResult(scheme='http', netloc='192.168.1.1:5000', path='', params='', query='', fragment='')
valid_chain: 區(qū)塊有效性驗證
def valid_chain(self, chain):
? ? last_block = chain[0]
? ? current_index = 1
? ? while current_index < len(chain):
? ? ? ? block = chain[current_index]
? ? ? ? if(block['previous_hash']) != self.hash(last_block)):
? ? ? ? ? ? return False;
? ? ? ? if(not self.valid_proof(last_block['proof'],block['proof']):
? ? ? ? ? ? return False;
? ? ? ? last_block = block
? ? ? ? current_index += 1
? ? return True
區(qū)塊鏈的檢查主要是針對‘previous_hash' 和 ’proof' 兩個字段進(jìn)行
創(chuàng)世區(qū)塊的index為0滥壕,其previous_hash 為1,不需要 檢查兽泣,針對其后的每個區(qū)塊檢查以下兩點
1. 區(qū)塊所存儲'previous_hash'的字段的值和前一個區(qū)塊hash出來的值是否一致绎橘;
2. 并檢查基于該區(qū)塊'proof'字段的值和上一個區(qū)塊的'proof'值的哈希值是否滿足條件。
resolve_conflicts: 沖突檢查
def resolve_conflicts(self):
? ? neighbours = self.nodes
? ? new_chain = None
? ? max_length = len(self.chain)
? ? for node in neighbours:
? ? ? ? response = requests.get('http://{0}/chain'.format(node))
? ? ? ? if(response.status_code == 200):
? ? ? ? ? ? length = response.json()['length']
? ? ? ? ? ? chain = response.json()['chain']
? ? ? ? ? ? if(length > max_length and self.valid_chain(chain)):
? ? ? ? ? ? ? ? max_length = length
? ? ? ? ? ? ? ? new_chain = chain
? ? ? ? if(new_chain):
? ? ? ? ? ? return True
? ? ? ? return False
此函數(shù)會遍歷其相鄰節(jié)點尋找更長的區(qū)塊鏈唠倦,如果 發(fā)現(xiàn)更長的區(qū)塊鏈則取代當(dāng)前節(jié)點的區(qū)塊鏈
hash:生成區(qū)塊哈希值
@staticmethod
def hash(block):
? ? block_string = json.dumps(block,sort_keys=True).encode()
? ? return hashlib.sha256(block_string).hexdigest()
調(diào)用hashlib相應(yīng)函數(shù)生成區(qū)塊的sha256哈希值
last_block: 返回最后一個區(qū)塊
@property
def last_block(self):
? ? return self.chain[-1]
恭喜你称鳞,看到這里區(qū)塊鏈底層最核心的部分已經(jīng)完成,休息 一會稠鼻,我們繼續(xù)完成后續(xù)關(guān)于節(jié)點服務(wù)器的代碼
節(jié)點服務(wù)器接口
這部分的代碼主要來模擬區(qū)塊鏈挖礦冈止、交易、注冊新節(jié)點候齿、沖突解決等行為熙暴。
節(jié)點實例化:
import uuid,time,hashlib,json,requests
from urllib.parse import urlparse
from flask import Flask,jsonify,request
app = Flask(__name__)
node_identifier = str(uuid.uuid4()).replace('-','')
blockchain = BlockChain()
if __name__ == '__main__':
????import sys
????try:
????????port = int(sys.argv[1])
????except:
????????port = 5000
????app.run(host='0.0.0.0',port=port)
UUID(Universally Unique Identifier)通用唯一標(biāo)識符,對于 所有的UUID可以保證在空間上和時間上的唯一性慌盯。
服務(wù)器接口:
@app.route('/mine',methods=['GET'])
@app.route('/chain', methods=['GET'])
@app.route('/transactions/new', methods=['POST'])
@app.route('/nodes/register', methods=['POST'])
@app.route('/nodes/resolve', methods=['GET'])
后續(xù)我們將分別對接口做進(jìn)一步的介紹
挖礦接口:
@app.route('/mine',methods=['GET'])
def mine():
? ? last_block = blockchain.last_block
? ? last_proof = last_block['proof']
? ? proof = blockchain.proof_of_work(last_proof)
? ? blockchain.new_transaction(
? ? ? ? sender = '0',
? ? ? ? receiver= node_identifier,
? ? ? ? amount = 1,
? ? ? ? )
? ? block = blockchain.new_block(proof)
? ? response = {
? ? ? ? 'message': 'New block created',
? ? ? ? 'index': blick['index'],
? ? ? ? 'transactions': block['transactions'],
? ? ? ? 'proof': block['proof'],
? ? ? ? 'previous_hash': block['previous_hash'],
? ? }
? ? return jsonify(response),200
提供獲取整個區(qū)塊鏈的接口:
@app.route('/chain', methods=['GET'])
def full_chain():
? ? response = {
? ? ? ? 'chain': blockchain.chain,
? ? ? ? 'length': len(blockchain.chain),
? ? }
? ? return jsonify(response),200
發(fā)送交易數(shù)據(jù)接口:
@app.route('/transactions/new', methods=['POST'])
def new_transactions():
? ? values = request.get_json()
? ? required = ['sender', 'receiver', 'amount']
? ? if not all(k in values for k in required):
? ? ? ? return('Missing values',400)
? ? index = blockchain.new_transaction(values['sender'],values['receiver'],values['amount'])
? ? response = {'message':f'Transaction will be added to block {index}'}
? ? return jsonify(response),201
注冊新節(jié)點接口:
@app.route('/nodes/register', methods=['POST'])
def register_nodes():
? ? values = request.get_json()
? ? nodes = values.get('nodes')
? ? if(nodes is None):
? ? ? ? return('Error: pls 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 has been replaced',
? ? ? ? ? ? 'new_chain': blockchain.chain
? ? ? ? }
? ? else:
? ? ? ? response = {
? ? ? ? ? ? 'message': 'Our chain is authoritative',
? ? ? ? ? ? 'chain': blockchain.chain
? ? ? ? }
? ? return jsonify(response),200
到此代碼層面已經(jīng)全部完成周霉,接下來我們將區(qū)塊鏈部署,來實際的模擬挖礦亚皂,交易等操作俱箱。
測試區(qū)塊鏈
啟動server:
python blockchain.py 5000
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
開始挖礦:
啟動第二個節(jié)點:
通過在一臺機器上開啟不同的網(wǎng)絡(luò)端口模擬多節(jié)點網(wǎng)絡(luò)
python blockchain.py 5001
啟動了第二個節(jié)點,并且目前第二個節(jié)點挖了兩次框孕讳,區(qū)塊鏈的長度為3,兩個節(jié)點產(chǎn)生沖突巍膘。這時可以通過共識機制去處理沖突
解決沖突:
1. 通過接口/nodes/register進(jìn)行注冊
2. 通過接口/nodes/resolve解決節(jié)點沖突
可以看到目前節(jié)點(5000端口)的區(qū)塊鏈已被替換為節(jié)點(5001端口)的數(shù)據(jù)
至此一個簡單的完整的區(qū)塊鏈就構(gòu)建完成厂财,需要了解其他更深入的信息,可以參考我的另外一篇區(qū)塊鏈系列文章峡懈,里面收集了一些我認(rèn)為比較不錯的資料璃饱。
參考
Learn Blockchain by Building One,?Daniel van Flymen