關於區塊鏈的解讀和簡單Python實現

概念解讀

區塊鏈幾乎是很多天間成爲人盡皆知的名詞,這個名詞也勾起了我強烈的興趣,可是經過在網上搜羅資料,多方閱讀,發現不少介紹區塊鏈的文獻要麼模棱兩可,要麼做者自己的理解也頗有限,致使不少關鍵的問題敘述不清。本人花了一些時間總結概括,但願能夠給讀者一個比較全面清晰的認識。html

 

區塊鏈的官方定義是:一個分佈式帳本,一種經過去中心化、去信任的方式集體維護一個可靠數據庫的技術方案。那麼對於圈外人該如何理解呢?如下我會詳細描述一個區塊鏈的產生過程和實現意義,從而給你們構建出一個清晰的區塊鏈概念。咱們先講原理、再說特色、而後論用途、最後迴歸代碼,這樣你就會有一種恍然大悟的感受。python

 

咱們以btc爲例:「區塊鏈」,顧名思義,就是由一個個區塊依次鏈接起來組成的鏈條,能夠類比爲一條無限長度的直線鐵鏈,每一個鐵環就是一個區塊。那麼區塊的內容應該是什麼呢?區塊狹義上是有兩種的,一個是普通區塊,一個就是創世區塊。創世區塊就是一項區塊鏈項目中的第一個區塊,因爲我的水平有限,對創世區塊沒有作過詳細研究,可是根據個人瞭解,創世區塊應該是具有與普通區塊類似結構的,但會確定會加入一些創始人想說的東西,而且在有些項目中可能會多一條記錄,就是coin的發行量,例如swtc的6000億數目就是寫在創世區塊之中的,一旦發行,沒法修改。git

 

那麼,一個普通區塊中到底有什麼?程序員

1.index:就是從0-n依次產生的數字,能夠稱之爲鏈高度。算法

2.hash:一個長度爲256位隨機數,是這個區塊的惟一編號。數據庫

3.previous hash:上一個區塊的hash,一個普通區塊有且僅有一個previous hash,這就是區塊鏈之因此稱爲鏈的緣由,就是這麼一環套一環連接而成的。json

4.tempstamp:用於記錄該區塊誕生的時間。安全

5.difficulty:直觀體現創造該區塊的難度。網絡

6.nonce:隨機數,用於產生下一個區塊。數據結構

上述的都存在區塊頭中。

7.data:存儲的交易記錄。只有這個存在區塊體中。

 

Ok,上述提到了一個區塊須要具有的最基本的幾條要素,可能你如今還處於一臉懵逼的狀態:這些東西究竟是怎麼工做的呢?下面我將一步步分析區塊鏈的工做過程,不過,這裏要先問兩個問題:爲何要產生新的區塊?怎麼產生新的區塊?

 

爲何產生新區快?

以前說了,一個區塊記錄的就是一份帳單,帳單中存儲着若干條交易記錄,是買賣雙方具體活動的最有力的證實,例如咱們在淘寶上的購買記錄,就是咱們的消費帳單。人們天天的消費記錄是不斷增加的,不可能永遠放在一個區塊裏,那麼跟如今的中心化存儲機制還有什麼區別?因此,隨着買賣記錄的不斷增長,就須要不斷產生新的區塊來存儲這些數據。

 

怎麼產生新的區塊?

我相信,最近除了區塊鏈這個名詞如雷貫耳之外,「挖礦」應該也沒少聽吧。挖礦實際上就是由那些礦工來生成新的區塊的過程。在btc項目中,btc礦工挖礦成功(其實就是成功的建立了一個區塊)就能夠得到必定數量的被btc獎勵,因此btc數量是在必定範圍內慢慢增長的。在通常容許挖礦的區塊鏈項目(也存在限制coin數量不容許挖礦的區塊鏈項目)中,礦工的數量通常會大於6個,通常超過6個礦工承認的新區塊就能夠加入到區塊鏈中。到此爲止,有人會說:哇!btc這麼值錢,挖礦不是很爽?其實否則,若是區塊無限制的快速增長,會引發很大的問題,根據中本聰的設定,目前全網每10分鐘只能產生一個新區塊。並且這10分鐘不是靠本身掐表算的,生成新的區塊是須要大量的運算的,這10分鐘是人家預先設計好的,讓計算量大到全網10分鐘通常只能產生一個。

 

好了,至此,區塊鏈的基本概念已經介紹的差很少了,下面言歸正傳,講下區塊的工做流程:

1.若是A要和B達成一筆交易,好比A轉給B一個btc,B給A打10w的RMB。A首先將本身的btc來源信息、交易人等發送給B,同時還要拷貝一份發到全網。什麼?這樣還有隱私可言嗎?固然,聰明的中本聰固然不會犯這麼低級的錯誤。在區塊鏈中,每一個交易個體(也能夠理解爲每一個網絡節點)都會有一對公鑰和私鑰,公鑰至關於一個「收款地址」,而私鑰是一個代表本身身份的256位的數字,目前通常是用sha265來生成的,這樣,別人並不知道交易的雙方是誰。發送報文時,發送方用一個哈希函數從報文文本中生成報文摘要,而後用本身的私鑰對摘要進行加密,加密後的摘要將做爲報文的數字簽名和報文一塊兒發送給接收方,接收方首先用與發送方同樣的哈希函數從接收到的原始報文中計算出報文摘要,接着再用發送方的公鑰來對報文附加的數字簽名進行解密,若是這兩個摘要相同、那麼接收方就能確認該數字簽名是發送方的。

 

2.那麼此時,這筆交易是否就完成了呢?若是這就算完成了,那跟A直接用包裹裝10w現金快遞給B有什麼區別呢?此時,全網的礦工都會獲得這個交易記錄,那麼全網的礦工都會爲了若干獎勵開始建立區塊的工做,礦工會利用hash函數生成一個256位的惟一編號賦予這個區塊,可是這個編號並非簡簡單單隨便生成的。編號是根據區塊的具體內容如交易內容、nonce等惟一肯定的,換句話說,兩塊內容相同的區塊所對應的編號必定是惟一的。但是你會問:這又怎麼了?並不難啊。錯!中本聰爲了控制區塊的生成時間,使區塊的生成速率知足全網的每10分鐘一個的標準,制定了嚴格的區塊生成校驗規則,也就是說,能不能生成一個成功的區塊要看你的編號是否符合這個規則。例如:生成編號中的前n位必須爲‘0’。

 

因爲區塊的交易內容是沒法修改的,所以礦工們只能經過修改nonce去不斷嘗試這個函數,直到生成了一個成功的區塊爲止。若是當區塊平均生成時間變快或者變慢,那麼系統會對區塊校驗規則進行相應的調整,從而使平均的生成時間可以控制在規定範圍。

 

若是一個礦工完成了一個區塊,會馬上告知其餘礦工,若是其餘礦工此時沒有完成新的區塊生成,則會停下手頭的工做,對區塊進行驗證,須要確認的信息主要有以下幾點:

 

1).區塊的編號有效;這個只要將區塊放入哈希函數中,看產生的編號是否和該區塊中的編號一致便可。

2).區塊的前一個區塊有效;以前提過,區塊是一個串聯的,每個普通區塊都會記載前一個區塊的編號,這須要其餘礦工對比當前的區塊鏈的最後一個區塊,看是否相同。

3).交易清單有效;就是說要驗證A到底有沒有這一個btc能夠給B。在區塊鏈的交易信息中,會記錄交易中全部btc的前世此生,區塊鏈能夠作到追本溯源,所以每個btc在哪裏,爲何在這裏均可以一目瞭然,因此這點也沒問題。

當驗證完一個全新的區塊後,全網就會認爲這個區塊有效,會將它添加到現有的區塊鏈末端,同時結束針對該區塊的挖礦工做,投入到下一個挖礦週期中。

3.可是不難想象,這樣的機制是存在衝突的隱患的,就是這麼巧,兩個礦工同時製做了一個正確的區塊,那麼此時沒必要二選一,能夠將原來線性的區塊鏈改爲樹狀:

可是這樣會致使將來在A、B後都會增長相應的區塊,那麼誰長誰將做爲主鏈延伸下去,另外一個也許會漸漸被遺忘,除非哪天它變得更長。

好啦,這就是區塊鏈最基本的知識,接下來應該談談優缺點了。

 

世界上沒有同樣東西能夠稱爲完美無瑕的,要知道區塊鏈同樣,雖然它被扣上了能夠顛覆將來的帽子,可是仍然存在它的侷限性:

1.時效性。很容易發現,區塊鏈中存在不少的驗證、傳遞環節,這就會致使其時效性較差。

二、能耗,這點也是顯而易見的,區塊鏈須要大量無用計算來控制區塊的生成時間。因此區塊鏈不適用於高時效的網絡中。

 

至於區塊鏈的優勢,諸如安全、去中心化等等在網絡上已經描述的很是清楚,這裏就再也不贅述。接下來我用一段python代碼來簡單實現一個挖礦的流程。

 

代碼示例

首先建立一個表示區塊鏈的類:

class BlockChain:
    def __init__(self, initialHash):
        # init block chain
        self.chain = []

        # init pitman
        self.pitmen = []
        for i in range(6):
            self.pitmen.append(Pitman)

        # collect mine results
        self.results = []

        # generate GenesisBlock
        self.new_block(initialHash)

 

初始化函數中的chain表示當前的區塊鏈,我會在其中存儲區塊對象;pitmen表示爲這個區塊鏈服務的礦工們,這個列表中也會存有礦工對象;results則會存儲每一個階段產生的區塊;new_block方法是建立區塊的方法,若是當前生成的區塊爲第一個區塊,則產生創世區塊。

下面看看區塊鏈類型的方法:

@property
def last_block(self):
    if len(self.chain):
        return self.chain[-1]
    else:
        return None

 

last_block會返回當前區塊鏈的最後一個區塊對象。

def get_trans(self):
    return json.dumps({
        'sender': ''.join(random.sample(string.ascii_letters + string.digits, 8)),
        'recipient': ''.join(random.sample(string.ascii_letters + string.digits, 8)),
        'amount': random.randrange(1, 10000)
    })

 

get_trans方法則用來隨機生成一份交易信息。

def new_block(self, initialHash=None):
    if initialHash:
        # generate Genesis Block
        block = Block()
        block.index = 0
        block.nonce = random.randrange(0, 99999)
        block.previousHash = '0'
        block.difficulty = 0
        block.transactionData = self.get_trans()
        guess = f'{block.previousHash}{block.nonce}{block.transactionData}'.encode()
        block.hash = hashlib.sha256(guess).hexdigest()
        block.time = time()
        self.chain.append(block)
    else:
        for i in range(len(self.pitmen)):
            pm = MyThread(target=self.pitmen[i].mine,
                                  args=(self.pitmen[i],
                                        len(self.chain),
                                        self.last_block.get_block()['Hash'],
                                        self.get_trans()))
            pm.start()
            pm.join()
            self.results.append(pm.get_result())

        # show all blocks
        print("All blocks generated by pitmen:")
        for result in self.results:
            print(result[0].get_block())

        # get new block
        firstblock = self.results[0][0]
        mintime = Decimal(self.results[0][1])
        for i in range(1, len(self.results)):
            if Decimal(self.results[i][1]) < mintime:
                firstblock = self.results[i][0]
            else:
                continue
        self.chain.append(firstblock)
        self.results = []

 

這是生成區塊的核心部分,這個方法主要分紅兩個部分:根據傳參區分是不是創世區塊,若是須要的是創世區塊,那就由該類型自動生成一個區塊佔據區塊鏈的頭一個位置。若是須要生成的是普通區塊,那麼則會將一些基本信息分發給礦工們進行挖礦操做。我在這裏設置了6個礦工,爲了公平起見,這裏也開了個多線程儘可能讓礦工們同時收到消息從而能夠同時進行挖礦操做。按理說,最早挖礦成功的礦工會將消息發給其餘礦工,其餘礦工會馬上中止進行校驗,但因爲時間有限,這一步校驗環節我沒有實現。在這裏,我容許全部礦工都完成工做,並提交成果和相應的工做時間,生成耗時最短的區塊講做爲正確的區塊添加到區塊鏈上。本質上也是依照了「快者優先」的區塊鏈生成原則。

 

說了半天,區塊內部究竟是什麼樣的呢?

class Block:
    def __init__(self):
        self.index = None
        self.time = None
        self.difficulty = None
        self.nonce = None
        self.hash = None
        self.previousHash = None
        self.transactionData = None

    def get_block(self):
        return {
            'Index': self.index,
            'Time': self.time,
            'Difficulty': self.difficulty,
            'Hash': self.hash,
            'Nonce': self.nonce,
            'PreviousHash': self.previousHash,
            'TransactionData': self.transactionData
        }

 

我用了一個相對簡單的類型表示區塊,在這裏區塊就是一個很是簡單的數據結構,這裏的全部字段在上述中已經有了詳細的說明,所以再也不贅述。

那麼咱們再看看礦工,畢竟礦工纔是區塊鏈的生產者和推進者,地位最爲核心。礦工類中我設計了兩個方法,一個mine方法,也就是挖礦的意思,用來生成新的區塊,並會返回該區塊生成的用時長短。

def mine(self, index, previousHash, transactionData):
    beginTime = time()

    block = Block()
    block.index = index
    block.previousHash = previousHash
    block.transactionData = transactionData
    block.difficulty, block.hash, block.nonce = self.generate_hash(previousHash, transactionData)
    block.time = time()
    endTime = time()

    return block, endTime - beginTime

 

另外一個方法是hash生成的方法,生成原則我本身意淫了一個,你們莫怪,由於本人計算機的實在不給力,區塊的成功標準很簡單,就是用sha256生成的編碼最後結尾是0就知足要求,若是改爲00,那會半天看不到結果。

下面咱們看下這套實例的運行結果,我設置了一個長度爲6的區塊鏈,因爲第一個區塊是創世區塊,因此真正開始建立的區塊是從第二個開始的,你們注意index。

這是6個礦工完成的第一區塊:

All blocks generated by pitmen:
{'Index': 1, 'Time': 1516268156.5971138, 'Difficulty': 2, 'Hash': '01f505a276e3f55a868d9ee18f70bcff75429e1de70f5ab59471a3551cc67a30', 'Nonce': 91554, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "OY8z0Rrx", "recipient": "iSGFJsEm", "amount": 8723}'}
{'Index': 1, 'Time': 1516268156.5971138, 'Difficulty': 5, 'Hash': 'c3ba406bad0d87f816f629830a15e2997638bfa230484c224e5470eaa24d8790', 'Nonce': 62372, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "9o8UMDLe", "recipient": "qTOQu7kv", "amount": 2746}'}
{'Index': 1, 'Time': 1516268156.5981123, 'Difficulty': 5, 'Hash': '8ff243885e9017296aa2ef1a611ef5b3927ddce818cb7255a04ff3228c982c60', 'Nonce': 67644, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "kIqy1c8C", "recipient": "WSdK0EXh", "amount": 9329}'}
{'Index': 1, 'Time': 1516268156.5981123, 'Difficulty': 3, 'Hash': 'ff9716bf9379e2ab7a8640419e7c7b7c7329a5e6e1bbf83a1249f49d070ca8b0', 'Nonce': 37336, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "vBwU0luH", "recipient": "d7o6cRCj", "amount": 5628}'}
{'Index': 1, 'Time': 1516268156.5981123, 'Difficulty': 3, 'Hash': '3410c70c31f9bacbfcbd74d63f25f69f27d36075e2d44bddaa60bd72fa042e60', 'Nonce': 34617, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "yzcNpBnh", "recipient": "vbIr7SKo", "amount": 6387}'}
{'Index': 1, 'Time': 1516268156.5981123, 'Difficulty': 27, 'Hash': '91e3dc3ef1a151557a1edd837528410b916362bcfb77dbb14dc54c8929f5a0d0', 'Nonce': 49121, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "p1MguhVz", "recipient": "gVSom4D3", "amount": 7356}'}

很明顯前兩個是最快的,爲了簡單,我在最快的裏面隨便選取一個,意思到了就行。你們能夠看到,難度值,根據上文所說,這是反應一個區塊生成的難易程度的,難度高的,在我這裏會表示這個礦工爲了獲得這個區塊進行了多少次嘗試。看到這裏也許你會問:爲何難度最大的時間並無明顯長呢?也就是用時並無按照難度的增長而增長。我猜測應該是由於個人示例算法簡單,所以結果也不是十分精確,若是計算量達到必定的規模,應該會有明顯的差距。(若是有高人知道,能夠回覆我,在此謝過!)第三到第六個的區塊建立結果格式是與之同樣的,就不刷屏了。

 

最後看下整個區塊鏈的結果:

{'Index': 0, 'Time': 1516268156.5971138, 'Difficulty': 0, 'Hash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'Nonce': 87688, 'PreviousHash': '0', 'TransactionData': '{"sender": "OuVCmHbs", "recipient": "kFxbwSLc", "amount": 503}'}
{'Index': 1, 'Time': 1516268156.5971138, 'Difficulty': 2, 'Hash': '01f505a276e3f55a868d9ee18f70bcff75429e1de70f5ab59471a3551cc67a30', 'Nonce': 91554, 'PreviousHash': '7532402844a1c130833a27600298d09a007d6124603cf44be9c05fcd5428c34a', 'TransactionData': '{"sender": "OY8z0Rrx", "recipient": "iSGFJsEm", "amount": 8723}'}
{'Index': 2, 'Time': 1516268156.5991132, 'Difficulty': 4, 'Hash': '098544436793881e8041c0c903c96c0055e16396113d73c63bc55e7ba78ec130', 'Nonce': 12875, 'PreviousHash': '01f505a276e3f55a868d9ee18f70bcff75429e1de70f5ab59471a3551cc67a30', 'TransactionData': '{"sender": "HJZSX1hk", "recipient": "j82k51yY", "amount": 3521}'}
{'Index': 3, 'Time': 1516268156.6001143, 'Difficulty': 27, 'Hash': '7c10243223caf39bc5a6067de8d93f6ea46bad62c4a0fbcc0aa4e086585d8200', 'Nonce': 18663, 'PreviousHash': '098544436793881e8041c0c903c96c0055e16396113d73c63bc55e7ba78ec130', 'TransactionData': '{"sender": "cJrGxN5R", "recipient": "wkZI8QCv", "amount": 1224}'}
{'Index': 4, 'Time': 1516268156.601114, 'Difficulty': 3, 'Hash': '60a099d3fe53e031800669fcc1d9b5ab6df1f80a40354135310a799892f1c3d0', 'Nonce': 51446, 'PreviousHash': '7c10243223caf39bc5a6067de8d93f6ea46bad62c4a0fbcc0aa4e086585d8200', 'TransactionData': '{"sender": "nCNJoy52", "recipient": "kYBT9f65", "amount": 3603}'}
{'Index': 5, 'Time': 1516268156.605163, 'Difficulty': 2, 'Hash': '765f69163cf95584721015e3ce819c1980ce33752f8a4dea553d3bedd39f8920', 'Nonce': 31804, 'PreviousHash': '60a099d3fe53e031800669fcc1d9b5ab6df1f80a40354135310a799892f1c3d0', 'TransactionData': '{"sender": "FqOkiTEu", "recipient": "y9EDcSYA", "amount": 4185}'}

 

這就是由這6個礦工依次建立的六個區塊,根據hash值環環相扣。

到底我想說的基本就結束了,最後我想說區塊鏈是個神奇的技術,從聽到它就深深的吸引着我,指望將來區塊鏈能夠真的帶來巨大的變革。要知道,隨着AI的興起,區塊鏈的問世,屬於程序員的數字時代會進一步昇華,互聯網時代只是一個開始!謝謝閱讀,本人水平有限,若是您發現問題或我理解誤差的地方能夠及時指出,再次表示感謝。文章末尾會附上完整代碼。

import hashlib
import random
import string
import json
import threading
from decimal import Decimal
from time import time


class MyThread(threading.Thread):

    def __init__(self, target, args=()):
        super(MyThread, self).__init__()
        self.func = target
        self.args = args

    def run(self):
        self.result = self.func(*self.args)

    def get_result(self):
        try:
            return self.result
        except Exception:
            return None


class BlockChain:
    def __init__(self, initialHash):
        # init block chain
        self.chain = []

        # init pitman
        self.pitmen = []
        for i in range(6):
            self.pitmen.append(Pitman)

        # collect mine results
        self.results = []

        # generate GenesisBlock
        self.new_block(initialHash)

    @property
    def last_block(self):
        if len(self.chain):
            return self.chain[-1]
        else:
            return None

    def get_trans(self):
        return json.dumps({
            'sender': ''.join(random.sample(string.ascii_letters + string.digits, 8)),
            'recipient': ''.join(random.sample(string.ascii_letters + string.digits, 8)),
            'amount': random.randrange(1, 10000)
        })

    def new_block(self, initialHash=None):
        if initialHash:
            # generate Genesis Block
            block = Block()
            block.index = 0
            block.nonce = random.randrange(0, 99999)
            block.previousHash = '0'
            block.difficulty = 0
            block.transactionData = self.get_trans()
            guess = f'{block.previousHash}{block.nonce}{block.transactionData}'.encode()
            block.hash = hashlib.sha256(guess).hexdigest()
            block.time = time()
            self.chain.append(block)
        else:
            for i in range(len(self.pitmen)):
                pm = MyThread(target=self.pitmen[i].mine,
                                      args=(self.pitmen[i],
                                            len(self.chain),
                                            self.last_block.get_block()['Hash'],
                                            self.get_trans()))
                pm.start()
                pm.join()
                self.results.append(pm.get_result())

            # show all blocks
            print("All blocks generated by pitmen:")
            for result in self.results:
                print(result[0].get_block())

            # get new block
            firstblock = self.results[0][0]
            mintime = Decimal(self.results[0][1])
            for i in range(1, len(self.results)):
                if Decimal(self.results[i][1]) < mintime:
                    firstblock = self.results[i][0]
                else:
                    continue
            self.chain.append(firstblock)
            self.results = []

    def show_chain(self):
        print('This is mine first block chain!')
        for block in self.chain:
            print(block.get_block())


class Block:
    def __init__(self):
        self.index = None
        self.time = None
        self.difficulty = None
        self.nonce = None
        self.hash = None
        self.previousHash = None
        self.transactionData = None

    def get_block(self):
        return {
            'Index': self.index,
            'Time': self.time,
            'Difficulty': self.difficulty,
            'Hash': self.hash,
            'Nonce': self.nonce,
            'PreviousHash': self.previousHash,
            'TransactionData': self.transactionData
        }


class Pitman:

    def mine(self, index, previousHash, transactionData):
        beginTime = time()

        block = Block()
        block.index = index
        block.previousHash = previousHash
        block.transactionData = transactionData
        block.difficulty, block.hash, block.nonce = self.generate_hash(previousHash, transactionData)
        block.time = time()
        endTime = time()

        return block, endTime - beginTime

    @staticmethod
    def generate_hash(previousHash, transactionData):
        difficulty = 0
        nonce = random.randrange(0, 99999)
        guess = f'{previousHash}{nonce}{transactionData}'.encode()
        myhash = hashlib.sha256(guess).hexdigest()
        while myhash[-1] != '0':
            difficulty += 1
            nonce += difficulty
            guess = f'{previousHash}{nonce}{transactionData}'.encode()
            myhash = hashlib.sha256(guess).hexdigest()
        return difficulty, myhash, nonce


if __name__ == '__main__':
    chain = BlockChain(1)
    length = 5
    for i in range(length):
        chain.new_block()
    chain.show_chain()

 

閱讀原文

相關文章
相關標籤/搜索