使用python構(gòu)建完整底層區(qū)塊鏈

區(qū)塊鏈的文章看了很多是不是還是有些云里霧里的感覺 坡垫,在對區(qū)塊鏈概念有了基本了解之后,筆者建議有基礎(chǔ)的同學(xué)可以動手寫一個區(qū)塊鏈阴汇,這樣才能更深刻的理解區(qū)塊鏈的本質(zhì)。本文是寫給有相應(yīng)python和區(qū)塊鏈基礎(chǔ)的同學(xué)教給大家如何通過python從零開始構(gòu)建區(qū)塊鏈节槐。

前導(dǎo)知識儲備

1. Python

2.?區(qū)塊鏈基礎(chǔ)

測試環(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)

開始挖礦:

1. mine交易

啟動第二個節(jié)點:

通過在一臺機器上開啟不同的網(wǎng)絡(luò)端口模擬多節(jié)點網(wǎng)絡(luò)

python blockchain.py 5001


2. second node

啟動了第二個節(jié)點,并且目前第二個節(jié)點挖了兩次框孕讳,區(qū)塊鏈的長度為3,兩個節(jié)點產(chǎn)生沖突巍膘。這時可以通過共識機制去處理沖突

解決沖突:

1. 通過接口/nodes/register進(jìn)行注冊

2. 通過接口/nodes/resolve解決節(jié)點沖突


3. register


4. resolve

可以看到目前節(jié)點(5000端口)的區(qū)塊鏈已被替換為節(jié)點(5001端口)的數(shù)據(jù)

至此一個簡單的完整的區(qū)塊鏈就構(gòu)建完成厂财,需要了解其他更深入的信息,可以參考我的另外一篇區(qū)塊鏈系列文章峡懈,里面收集了一些我認(rèn)為比較不錯的資料璃饱。

參考

Learn Blockchain by Building One,?Daniel van Flymen

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肪康,隨后出現(xiàn)的幾起案子荚恶,更是在濱河造成了極大的恐慌,老刑警劉巖磷支,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谒撼,死亡現(xiàn)場離奇詭異,居然都是意外死亡雾狈,警方通過查閱死者的電腦和手機廓潜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辩蛋,你說我怎么就攤上這事呻畸。” “怎么了悼院?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵伤为,是天一觀的道長。 經(jīng)常有香客問我据途,道長绞愚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任昨凡,我火速辦了婚禮爽醋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘便脊。我一直安慰自己蚂四,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布哪痰。 她就那樣靜靜地躺著遂赠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晌杰。 梳的紋絲不亂的頭發(fā)上跷睦,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音肋演,去河邊找鬼抑诸。 笑死,一個胖子當(dāng)著我的面吹牛爹殊,可吹牛的內(nèi)容都是我干的蜕乡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼梗夸,長吁一口氣:“原來是場噩夢啊……” “哼层玲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起反症,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤辛块,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铅碍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體润绵,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年胞谈,在試婚紗的時候發(fā)現(xiàn)自己被綠了授药。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片士嚎。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖悔叽,靈堂內(nèi)的尸體忽然破棺而出莱衩,到底是詐尸還是另有隱情,我是刑警寧澤娇澎,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布笨蚁,位于F島的核電站,受9級特大地震影響趟庄,放射性物質(zhì)發(fā)生泄漏括细。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一戚啥、第九天 我趴在偏房一處隱蔽的房頂上張望奋单。 院中可真熱鬧,春花似錦猫十、人聲如沸览濒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贷笛。三九已至,卻和暖如春宙项,著一層夾襖步出監(jiān)牢的瞬間乏苦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工尤筐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留汇荐,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓盆繁,卻偏偏與公主長得像掀淘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子改基,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 小時候不知天高地厚 免不了父親的鞭子 鞭子還不曾落到身上 眼淚就伴著哀嚎滾落下來 本以為能逃過一劫 卻不曾想結(jié)果更...
    喪少女的勵志生活閱讀 247評論 0 7
  • 自從上了大學(xué)才知道什么是霧霾秕狰,原來天也可以這樣晦暗,所以我格外珍惜好天氣躁染,每次藍(lán)天就想出去郊游鸣哀,可是要學(xué)習(xí)一扎進(jìn)圖...
    梓田小可愛閱讀 409評論 0 1
  • 我總是想象一切都是美好的,然而美好卻總是與我無緣吞彤,是我太過刻意的追逐而迷失了方向呢我衬?還是所有美好都虛假的害怕我審視...
    獨孤琳閱讀 154評論 0 0