引言
緊接上文,在介紹完區(qū)塊鏈中的加密解密以及公鑰私鑰等算法后,本篇開始正式進入?yún)^(qū)塊鏈概念與一個簡單區(qū)塊鏈系統(tǒng)的實現(xiàn)過程介紹。
區(qū)塊鏈技術(shù)介紹
什么是區(qū)塊鏈?
區(qū)塊鏈,就是一個又一個區(qū)塊組成的鏈條。每一個區(qū)塊中保存了一定的信息,它們按照各自產(chǎn)生的時間順序連接成鏈條。這個鏈條被保存在所有的服務(wù)器中,只要整個系統(tǒng)中有一臺服務(wù)器可以工作,整條區(qū)塊鏈就是安全的。這些服務(wù)器在區(qū)塊鏈系統(tǒng)中被稱為節(jié)點,它們?yōu)檎麄€區(qū)塊鏈系統(tǒng)提供存儲空間和算力支持。如果要修改區(qū)塊鏈中的信息,必須征得半數(shù)以上節(jié)點的同意并修改所有節(jié)點中的信息,而這些節(jié)點通常掌握在不同的主體手中,因此篡改區(qū)塊鏈中的信息是一件極其困難的事。相比于傳統(tǒng)的網(wǎng)絡(luò),區(qū)塊鏈具有兩大核心特點:一是數(shù)據(jù)難以篡改、二是去中心化?;谶@兩個特點,區(qū)塊鏈所記錄的信息更加真實可靠,可以幫助解決人們互不信任的問題。
來源:百度百科
總結(jié)一句話就是,區(qū)塊鏈本質(zhì)上是一個去中心化的數(shù)據(jù)庫。而它能提取以下幾個特點:
- 信息不可修改,添加到區(qū)塊鏈中的信息無法再被修改;
- 只支持「增」和「查」操作,不同于傳統(tǒng)的數(shù)據(jù)庫技術(shù)支持「增、刪、查、改」,區(qū)塊鏈技術(shù)只支持「增」操作(即往區(qū)塊鏈里的添加區(qū)塊信息),和「查」操作(即查詢區(qū)塊鏈里的區(qū)塊信息);而不支持「刪」和「改」操作;
- 沒有權(quán)限限制,任何加入?yún)^(qū)塊鏈網(wǎng)絡(luò)的節(jié)點都有權(quán)限「增」和「查」區(qū)塊信息。
更加通俗的理解,可以查看新華網(wǎng)在2019年發(fā)布的刷屏的區(qū)塊鏈究竟是什么?你想知道的都在這里!
這里直接講區(qū)塊鏈的區(qū)塊結(jié)構(gòu),見下表為:
數(shù)據(jù)項 | 字節(jié) | 字段 | 說明 |
---|---|---|---|
Magic NO | 4 | 魔數(shù) | 常數(shù)0xD9B4BEF9 |
Blocksize | 4 | 區(qū)塊大小 | 用字節(jié)表示的該字段之后的區(qū)塊大小 |
Blockheader | 80 | 區(qū)塊頭 | 組成區(qū)塊頭的幾個字段,描述區(qū)塊的元數(shù)據(jù) |
Transaction counter | 1-9 | 交易計數(shù)器 | 該區(qū)塊包含的交易數(shù)量,包含coinbase交易,描述緊跟在該域后面的交易數(shù)據(jù)的數(shù)目 |
Transactions | 不定 | 交易 | 記錄在區(qū)塊里的交易信息,使用原生的交易信息格式,并且交易在數(shù)據(jù)流中的位置必須與Merkle樹的葉子節(jié)點順序一致 |
其中Blocksize和Magic NO都是描述區(qū)塊鏈的一個量詞,余下的三個概念就構(gòu)成了區(qū)塊鏈:

其中區(qū)塊頭的結(jié)構(gòu)為:
大小 | 域名 | 描述 |
---|---|---|
4 字節(jié) | Version | 版本號,升級了軟件并指定了新版本 |
32 字節(jié) | Previous Block Hash | 與本區(qū)塊形成鏈的前一區(qū)塊的散列值,更準確的講是前一區(qū)塊的區(qū)塊頭的散列值 |
32 字節(jié) | Merkle Root Hash | 這個域的準確意義解釋起來稍微有點復(fù)雜,暫時可以理解為區(qū)塊體的散列值 |
4 字節(jié) | Timestamp | 可以簡單理解為該區(qū)塊被創(chuàng)建時的 Unix 時間戳(即從 UTC 1970 年 1 月 1 日 0 時 0 分 0 秒起至現(xiàn)在的總秒數(shù)) |
4 字節(jié) | Difficulty Target | 難度調(diào)整的一個目標值,后續(xù)工作量介紹 |
4 字節(jié) | Nonce | 為了找到滿足難度目標所設(shè)定的隨機數(shù),為了解決32位隨機數(shù)在算力飛升的情況下不夠用的問題,規(guī)定時間戳和coinbase交易信息均可更改,以此擴展nonce的位數(shù) |
在了解比特幣系統(tǒng)中的區(qū)塊頭結(jié)構(gòu)中 Previous Block Hash
和 Merkle Root Hash
這兩個域的大致意義后,區(qū)塊如何形成區(qū)塊鏈的具體細節(jié)便清晰了。
區(qū)塊鏈的結(jié)構(gòu)有以下兩個主要特點:
-
區(qū)塊鏈中的每個區(qū)塊都通過
Previous Block Hash
記錄了前一區(qū)塊的區(qū)塊頭的散列值; -
區(qū)塊鏈中的每個區(qū)塊的區(qū)塊頭都通過
Merkle Root Hash
記錄了該區(qū)塊的區(qū)塊體的散列值。
這種結(jié)構(gòu)和數(shù)據(jù)結(jié)構(gòu)中的鏈表是非常相似的,見下圖所示:

在鏈表中,初始結(jié)點為頭結(jié)點,而區(qū)塊鏈中,初始的塊則叫做創(chuàng)世區(qū)塊(Genesis Block),鏈表的頭結(jié)點指向的為Null,因為它本身就是head,創(chuàng)世塊同樣,它沒有前置區(qū)塊,所以它的Previous Block Hash
指向的值便為空。
實現(xiàn)區(qū)塊類與區(qū)塊鏈類
區(qū)塊類
基于上一節(jié)對于區(qū)塊鏈結(jié)構(gòu)的分析,下面我們可以使用 Python 中的 Block 類來實現(xiàn)我們第一個版本的區(qū)塊鏈的區(qū)塊了。Block 類包含以下幾個域:
字段 | 解釋 |
---|---|
Timestamp |
當前時間戳,也就是區(qū)塊創(chuàng)建的時間 |
PrevBlockHash |
前一個塊的哈希,即父哈希 |
Hash |
當前塊的哈希 |
Data |
區(qū)塊存儲的實際有效信息,也就是交易 |
用代碼表示為:
class Block(object):
def __init__(self, data, prev_block_hash=''):
self.timestamp = int(time.time())
self.prev_block_hash = prev_block_hash
self.data = data
self.data_hash = hashlib.sha256(data.encode('utf-8')).hexdigest()
代碼中prev_block_hash=''
相當于在申明類時,就自動創(chuàng)建了一個創(chuàng)世區(qū)塊,并且它指向了在python中代表空的一種字符,使用hashlib中的sha256來計算區(qū)塊體和區(qū)塊頭的散列值。
目前,我們僅取了 Block 結(jié)構(gòu)的部分字段(Timestamp
, Data
和 PrevBlockHash
),并將它們相互拼接起來,然后在拼接后的結(jié)果上計算一個 SHA-256,就得到了一個新的哈希值,這與比特幣的實現(xiàn)方式也是一致的:
class Block(object):
......
def hash(self):
data_list = [str(self.timestamp), self.prev_block_hash, self.data_hash]
return hashlib.sha256(''.join(data_list).encode('utf-8')).hexdigest()
......
為了能夠以更好的可讀性的打印出 Block 類,我們給它加上 __repr__
方法:
class Block(object):
......
def __repr__(self):
s = 'Block(Hash={}, TimeStamp={}, PrevBlockHash={}, Data={}, DataHash={})'
return s.format(self.hash(), self.timestamp, self.prev_block_hash,
self.data, self.data_hash)
至此,一個區(qū)塊類就完成了,可以將其理解為一種數(shù)據(jù)結(jié)構(gòu),跟單鏈表相似,但是功能與攜帶信息比單鏈表更多。
區(qū)塊鏈類
如果說區(qū)塊類為單節(jié)點行為,而區(qū)塊鏈類相當于賦予了該類與其它節(jié)點通信的權(quán)利,區(qū)塊鏈類為實現(xiàn)區(qū)塊鏈整個系統(tǒng)的主要的一個類,所以這里直接給出最簡版本為:
from block import Block
from datetime import datetime
class BlockChain(object):
def __init__(self):
print('Created a new blockchain.\n')
self.chain = []
genesis_block = Block('This is a genesis block')
self.chain.append(genesis_block)
def add_block(self, data):
new_block = Block(data, self.chain[-1].hash())
self.chain.append(new_block)
def print_chain(self):
for block in self.chain:
print('Block Hash: {}'.format(block.hash()))
print('Prev Block Hash: {}'.format(block.prev_block_hash))
print('TimeStamp: {}'.format(datetime.fromtimestamp(block.timestamp)))
print('Data: {}'.format(block.data))
print('')
至此,我們創(chuàng)建一個main函數(shù)直接去調(diào)用上述代碼為:
from block import Block
from blockchain import BlockChain
if __name__ == '__main__':
# 創(chuàng)建區(qū)塊鏈
block_chain = BlockChain()
# 添加第一個區(qū)塊到區(qū)塊鏈中
block_chain.add_block('Send 1 BTC to Ivan')
# 添加第二個區(qū)塊到區(qū)塊鏈中
block_chain.add_block('Send 2 more BTC to Ivan')
# 打印整個區(qū)塊鏈的信息
block_chain.print_chain()
輸出為:
這里初步實現(xiàn)了區(qū)塊鏈的功能,但為什么還需要使用GPU以及分布式等更多設(shè)備呢?那就是下面要講的礦工核心算法。
工作量證明算法
在上一節(jié),我們構(gòu)造了一個非常簡單的數(shù)據(jù)結(jié)構(gòu) – 區(qū)塊,它也是整個區(qū)塊鏈數(shù)據(jù)庫的核心。目前所完成的區(qū)塊鏈原型,已經(jīng)可以通過鏈式關(guān)系把區(qū)塊相互關(guān)聯(lián)起來:每個塊都與前一個塊相關(guān)聯(lián)。
但是,依照上述代碼實現(xiàn)的區(qū)塊鏈有一個巨大的缺陷:向鏈中加入?yún)^(qū)塊太容易,也太廉價了。區(qū)塊鏈的一個關(guān)鍵點就是,一個人必須經(jīng)過一系列困難的工作,才能將數(shù)據(jù)放入到區(qū)塊鏈中。正是由于這種困難的工作,才保證了區(qū)塊鏈的安全和一致。此外,完成這個工作的人,也會獲得相應(yīng)獎勵(這也就是通過挖礦獲得幣)。
這個機制與生活現(xiàn)象非常類似:一個人必須通過努力工作,才能夠獲得回報或者獎勵,用以支撐他們的生活。在區(qū)塊鏈中,是通過網(wǎng)絡(luò)中的參與者(礦工)不斷的工作來支撐起了整個網(wǎng)絡(luò)。礦工不斷地向區(qū)塊鏈中加入新塊,然后獲得相應(yīng)的獎勵。在這種機制的作用下,新生成的區(qū)塊能夠被安全地加入到區(qū)塊鏈中,它維護了整個區(qū)塊鏈數(shù)據(jù)庫的穩(wěn)定性。值得注意的是,完成了這個工作的人必須要證明這一點,即他必須要證明他的確完成了這些工作。
整個 “努力工作并進行證明” 的機制,就叫做工作量證明(proof-of-work)。
工作量證明流程
工作量證明的核心思想就是給區(qū)塊鏈添加新區(qū)塊的這一操作設(shè)置一定的難度。誰都有權(quán)限往區(qū)塊鏈添加新區(qū)塊,但并不是誰都有能力這么做,而必須付出一定的代價,這個代價就是工作量證明。
怎么給「區(qū)塊鏈添加新區(qū)塊」的這一操作設(shè)置難度呢?其實也很簡單,就是規(guī)定新區(qū)塊的區(qū)塊頭的散列值必須滿足某種特征。由于散列值具有「單向性」和「雪崩效應(yīng)」,因此計算機只能通過窮舉計算的方法來獲得具備某種特征的散列值,而工作量證明算法也就是窮舉計算散列值的過程:

這個窮舉計算散列值的過程在比特幣系統(tǒng)中也被稱為挖礦。比特幣的實現(xiàn)要求的新區(qū)塊的區(qū)塊頭散列值特征是散列值必須是一定數(shù)目的前導(dǎo) 0
。前導(dǎo) 0
的數(shù)目被稱為挖礦的難度,該數(shù)值越大表示挖礦的難度也就越大(即要找到能夠加入比特幣區(qū)塊鏈的新區(qū)塊的時間就越長)。比特幣區(qū)塊頭結(jié)構(gòu)中,有兩個域與挖礦相關(guān):
大小 | 域名 | 描述 |
---|---|---|
4 字節(jié) | Difficulty Target | 表示當前挖礦的難度,但該值不是直接表示前導(dǎo) 0 的數(shù)目,而是一個通過特殊編碼處理的值,計算方法見如下圖 |
4 字節(jié) | Nonce | 為了找到滿足難度目標所設(shè)定的隨機數(shù),為了解決32位隨機數(shù)在算力飛升的情況下不夠用的問題,規(guī)定時間戳和coinbase交易信息均可更改,以此擴展nonce的位數(shù) |
關(guān)于Difficulty Target,表格里已經(jīng)解釋得很清楚,需要注意的是,這里的nonce是區(qū)塊中的nonce,主要是調(diào)整挖礦難度;還有一種是每筆交易中nonce,每個外部賬戶(私鑰控制的賬戶)都有一個nonce值,從0開始連續(xù)累加,每累加一次,代表一筆交易。關(guān)于后者,會之后介紹。
所以我們可以更新區(qū)塊類中的哈希方法,將Nonce加入列表:
import time
import hashlib
class Block(object):
def __init__(self, data, prev_block_hash=''):
self.timestamp = int(time.time())
self.prev_block_hash = prev_block_hash
self.data = data
self.nonce = 0 # 添加 nonce 成員,初始值為 0
self.data_hash = hashlib.sha256(data.encode('utf-8')).hexdigest()
def hash(self): # 計算散列值時,同樣加入 nonce值
data_list = [str(self.nonce),str(self.timestamp),
self.prev_block_hash, self.data_hash]
return hashlib.sha256(''.join(data_list).encode('utf-8')).hexdigest()
def __repr__(self): # 打印輸出,同樣加入 nonce值
return 'Block(Hash={}, TimeStamp={}, PrevBlockHash={}, Nonce={}, \
Data={}, DataHash={})'.format(self.hash(), self.timestamp,
self.prev_block_hash, self.nonce, self.data, self.data_hash)
然后定義工作量的nonce
和difficulty_bits
:
class ProofOfWork(object):
MAX_NONCE = sys.maxsize
def __init__(self, difficulty_bits=12):
self._target = 1 << (256-difficulty_bits)
這里的MAX_NONCE是用于限定在求解 nonce
值時的上限,sys.maxsize
與操作系統(tǒng)有關(guān),如果是32位的操作系統(tǒng),那么該值將為2^31-1
,即2147483647。如果是64位, 將為2^63-1
,即9223372036854775807
difficulty_bits
的計算規(guī)則如下圖:
可能看到左邊的1 << (256 - n)
,就知道這是移位的符號,學過計算機組成原理的,還能說出具體的操作,為算術(shù)左移:依次左移n位,尾部補0,最高的符號位保持不變。
這就是工作量類的核心,也是證明是否付出勞力的兩道質(zhì)檢工序,代碼為:
def mine_block(self, data, prev_hash=''):
tmp_block = Block(data, prev_hash)
while tmp_block.nonce<ProofOfWork.MAX_NONCE:
hash_int = int(tmp_block.hash(), 16) # 16進制
if hash_int < self._target: # 區(qū)塊頭的散列值滿足要求,小于target,算法完成
break
else : # 區(qū)塊頭的散列值不滿足要求,nonce加1,繼續(xù)循環(huán)
tmp_block.nonce+=1
return tmp_block
這里還可以加一個驗證功能,在后續(xù)將挖到的礦持久化以及上傳數(shù)據(jù)庫后,該值可隨時進行驗證,這里簡單寫為:
def validate_block(self, block):
hash_int = int(block.hash(), 16)
return True if hash_int<self._target else False
有了工作量證明我們的 Blockchain 類,在生成新區(qū)塊的時候就都需要通過 proofOfWork 類的 mine_block 方法來生成區(qū)塊了,另外為了更易于理解我們相應(yīng)的將 Blockchain 類的 add_block 方法也更名為 mine_block 方法,在 print_chain 方法中我們加入了對區(qū)塊的工作量證明校驗結(jié)果,因此修改后Blockchain 類的完整代碼如下:
from block import Block
from datetime import datetime
from proofofwork import ProofOfWork
class BlockChain(object):
def __init__(self):
print('Created a new blockchain.\n')
self.chain = []
self.pow = ProofOfWork() # 創(chuàng)建工作量證明類
genesis_block = self.pow.mine_block('This is a genesis block') # 通過工作量證明類來挖出創(chuàng)世區(qū)塊
self.chain.append(genesis_block)
# 挖出一個數(shù)據(jù)為data的區(qū)塊
def mine_block(self, data):
new_block = self.pow.mine_block(data, self.chain[-1].hash())
self.chain.append(new_block)
def print_chain(self):
for block in self.chain:
print('Block Hash: {}'.format(block.hash()))
print('Prev Block Hash: {}'.format(block.prev_block_hash))
print('TimeStamp: {}'.format(datetime.fromtimestamp(block.timestamp)))
print('Nonce: {}'.format(block.nonce)) # 增加打印 nonce值
print('Data: {}'.format(block.data))
print('POW: {}'.format(self.pow.validate_block(block))) # 校驗區(qū)塊
print('')
我們更改main.py
運行為:
from block import Block
from blockchain import BlockChain
if __name__ == '__main__':
# 創(chuàng)建區(qū)塊鏈
block_chain = BlockChain()
# 挖出第一個區(qū)塊
block_chain.mine_block('Send 1 BTC to Ivan')
# 挖出第二個區(qū)塊
block_chain.mine_block('Send 2 more BTC to Ivan')
# 打印整個區(qū)塊鏈的信息
block_chain.print_chain()
這里以每4位為首位添一位0,因為默認的difficulty_bits
為12,可以看到截圖中,有3位的16進制0開頭,換算為12位的二進制0,而每向上添加4位,都將增加其計算量,比如說將其改為24,增大兩倍,我們使用Linux默認計算時間工具測試:
time python3 main.py
很明顯,對比上一個,時間慢了一大截,因為現(xiàn)在還是單線程,看單核情況發(fā)現(xiàn),直接將我這個學生機的CPU的1核跑滿了:
所以,這也是為什么在比特幣大火的20年到22年間,各種云服務(wù)器被植入挖礦程序,導(dǎo)致每次上去查看,要不就顯卡跑滿,要不就CPU整個也全部跑滿,原因就是太密集的哈希計算,讓日常使用都不會觸頂?shù)姆?wù)器,第一次有了算不過來的感覺,emmm。。。文章來源:http://www.zghlxwxcb.cn/news/detail-798905.html
當然,之后的交易以及網(wǎng)絡(luò)等篇幅還會對工作量算法進行修改,比如說預(yù)防作弊等,具體的可以見如下圖,但也不會做得那么完全,那么至此,本篇結(jié)束。文章來源地址http://www.zghlxwxcb.cn/news/detail-798905.html

到了這里,關(guān)于動手學區(qū)塊鏈學習筆記(二):區(qū)塊鏈以及工作量證明算法的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!