python區(qū)塊鏈持久化和命令行接口實現(xiàn)簡版
說明
本文根據(jù)https://github.com/liuchengxu/blockchain-tutorial的內(nèi)容,用python實現(xiàn)的,但根據(jù)個人的理解進行了一些修改,大量引用了原文的內(nèi)容。文章末尾有"本節(jié)完整源碼實現(xiàn)地址"。
引言
到目前為止,我們已經(jīng)構(gòu)建了一個有工作量證明機制的區(qū)塊鏈。有了工作量證明,挖礦也就有了著落。雖然目前距離一個有著完整功能的區(qū)塊鏈越來越近了,但是它仍然缺少了一些重要的特性。在今天的內(nèi)容中,我們會將區(qū)塊鏈持久化到一個數(shù)據(jù)庫中,然后會提供一個簡單的命令行接口,用來完成一些與區(qū)塊鏈的交互操作。本質(zhì)上,區(qū)塊鏈是一個分布式數(shù)據(jù)庫,不過,我們暫時先忽略 “分布式” 這個部分,僅專注于 “存儲” 這一點。
選擇數(shù)據(jù)庫
目前,我們的區(qū)塊鏈實現(xiàn)里面并沒有用到數(shù)據(jù)庫,而是在每次運行程序時,簡單地將區(qū)塊鏈存儲在內(nèi)存中。那么一旦程序退出,所有的內(nèi)容就都消失了。我們沒有辦法再次使用這條鏈,也沒有辦法與其他人共享,所以我們需要把它存儲到磁盤上。
那么,我們要用哪個數(shù)據(jù)庫呢?實際上,任何一個數(shù)據(jù)庫都可以。在 比特幣原始論文 中,并沒有提到要使用哪一個具體的數(shù)據(jù)庫,它完全取決于開發(fā)者如何選擇。 Bitcoin Core ,最初由中本聰發(fā)布,現(xiàn)在是比特幣的一個參考實現(xiàn),它使用的是 LevelDB。而我們將要使用的是…
couchdb
因為它:
- 簡單易用
- 有一個web的UI界面,方便我們查看
- 豐富的查詢支持
- 良好的python支持
couchdb的安裝
直接安裝,參考http://chabaoo.cn/article/202914.htm
docker版couchdb安裝,使用docker-compose安裝couchdb
# couchdb.yaml version: '2' services: couchdb: image: hyperledger/fabric-couchdb ports: - 5984:5984
執(zhí)行docker-compose -f couchdb.yaml up -d即可安裝。
使用http://ip:5984/_utils即可訪問couchdb的后臺管理系統(tǒng)。
數(shù)據(jù)庫結(jié)構(gòu)
在開始實現(xiàn)持久化的邏輯之前,我們首先需要決定到底要如何在數(shù)據(jù)庫中進行存儲。為此,我們可以參考 Bitcoin Core 的做法:
簡單來說,Bitcoin Core 使用兩個 “bucket” 來存儲數(shù)據(jù):
- 其中一個 bucket 是 blocks,它存儲了描述一條鏈中所有塊的元數(shù)據(jù)
- 另一個 bucket 是 chainstate,存儲了一條鏈的狀態(tài),也就是當前所有的未花費的交易輸出,和一些元數(shù)據(jù)
此外,出于性能的考慮,Bitcoin Core 將每個區(qū)塊(block)存儲為磁盤上的不同文件。如此一來,就不需要僅僅為了讀取一個單一的塊而將所有(或者部分)的塊都加載到內(nèi)存中。而我們直接使用couchdb。
在 blocks 中,key -> value 為:
key | value |
---|---|
b + 32 字節(jié)的 block hash | block index record |
f + 4 字節(jié)的 file number | file information record |
l + 4 字節(jié)的 file number | the last block file number used |
R + 1 字節(jié)的 boolean | 是否正在 reindex |
F + 1 字節(jié)的 flag name length + flag name string | 1 byte boolean: various flags that can be on or off |
t + 32 字節(jié)的 transaction hash | transaction index record |
在 chainstate,key -> value 為:
key | value |
---|---|
c + 32 字節(jié)的 transaction hash | unspent transaction output record for that transaction |
B | 32 字節(jié)的 block hash: the block hash up to which the database represents the unspent transaction outputs |
詳情可見 這里。
因為目前還沒有交易,所以我們只需要 blocks bucket。另外,正如上面提到的,我們會將整個數(shù)據(jù)庫存儲為單個文件,而不是將區(qū)塊存儲在不同的文件中。所以,我們也不會需要文件編號(file number)相關(guān)的東西。最終,我們會用到的鍵值對有:
- 32 字節(jié)的 block-hash(轉(zhuǎn)換為16進制字符串) -> block 結(jié)構(gòu)
- l -> 鏈中最后一個塊的 hash(轉(zhuǎn)換為16進制字符串)
這就是實現(xiàn)持久化機制所有需要了解的內(nèi)容了。
序列化
為了方便我們查看,這里我們不直接使用二進制數(shù)據(jù),而將其轉(zhuǎn)換為16進制字符串。所以我們需要對區(qū)塊內(nèi)容進行序列化。
讓我們來實現(xiàn) Block 的 Serialize 方法:
# class Block def serialize(self): return { "magic_no": self._magic_no, "block_header": self._block_header.serialize(), "transactions": self._transactions }
直接返回我們需要的數(shù)據(jù)構(gòu)成的字典即可,而block_header則需要進一步序列化。它的序列化同樣也只需要返回具體的數(shù)據(jù)字典即可,如下:
# class BlockHeader def serialize(self): return self.__dict__
反序列化則是把信息轉(zhuǎn)換為區(qū)塊對象。
# class Block @classmethod def deserialize(cls, data): block_header_dict = data['block_header'] block_header = BlockHeader.deserialize(block_header_dict) transactions = data["transactions"] return cls(block_header, transactions)
首先反序列化塊,然后構(gòu)造成一個對象,反序列化Header:
# class BlockHeader @classmethod def deserialize(cls, data): timestamp = data.get('timestamp', '') prev_block_hash = data.get('pre_block_hash', '') # hash = data.get('hash', '') hash_merkle_root = data.get('hash_merkle_root', '') height = data.get('height', '') nonce = data.get('nonce', '') block_header = cls(hash_merkle_root, height, prev_block_hash) block_header.timestamp = timestamp block_header.nonce = nonce return block_header
持久化
持久化要做的事情就是把區(qū)塊數(shù)據(jù)寫入到數(shù)據(jù)庫中,則我們要做的事情有:
- 檢查數(shù)據(jù)庫是否已經(jīng)有了一個區(qū)塊鏈
- 如果沒有則創(chuàng)建一個,創(chuàng)建創(chuàng)世塊并將l指向這個塊的哈希
- 添加一個區(qū)塊,將l指向新添加的區(qū)塊哈希
創(chuàng)建創(chuàng)世塊如下:
# class BlockChain: def new_genesis_block(self): if 'l' not in self.db: genesis_block = Block.new_genesis_block('genesis_block') genesis_block.set_header_hash() self.db.create(genesis_block.block_header.hash, genesis_block.serialize()) self.set_last_hash(genesis_block.block_header.hash)
添加一個區(qū)塊如下:
def add_block(self, transactions): """ add a block to block_chain """ last_block = self.get_last_block() prev_hash = last_block.get_header_hash() height = last_block.block_header.height + 1 block_header = BlockHeader('', height, prev_hash) block = Block(block_header, transactions) block.mine() block.set_header_hash() self.db.create(block.block_header.hash, block.serialize()) last_hash = block.block_header.hash self.set_last_hash(last_hash)
對couchdb的操作的簡單封裝如下:
class DB(Singleton): def __init__(self, db_server_url, db_name='block_chain'): self._db_server_url = db_server_url self._server = couchdb.Server(self._db_server_url) self._db_name = db_name self._db = None @property def db(self): if not self._db: try: self._db = self._server[self._db_name] except couchdb.ResourceNotFound: self._db = self._server.create(self._db_name) return self._db def create(self, id, data): self.db[id] = data return id def __getattr__(self, name): return getattr(self.db, name) def __contains__(self, name): return self.db.__contains__(name) def __getitem__(self, key): return self.db[key] def __setitem__(self, key, value): self.db[key] = value
區(qū)塊鏈迭代器
由于我們現(xiàn)在使用了數(shù)據(jù)庫存儲,不再是數(shù)組,那么我們便失去了迭代打印區(qū)塊鏈的特性,我們需要重寫__getitem__以獲得該特性,實現(xiàn)如下:
# class BlockChain(object): def __getitem__(self, index): last_block = self.get_last_block() height = last_block.block_header.height if index <= height: return self.get_block_by_height(index) else: raise IndexError('Index is out of range')
# class BlockChain(object): def get_block_by_height(self, height): """ Get a block by height """ query = {"selector": {"block_header": {"height": height}}} docs = self.db.find(query) block = Block(None, None) for doc in docs: block.deserialize(doc) break return block
根據(jù)區(qū)塊高度獲取對應(yīng)的區(qū)塊,此處是利用了couchdb的mongo_query的富查詢來實現(xiàn)。
CLI
到目前為止,我們的實現(xiàn)還沒有提供一個與程序交互的接口。是時候加上交互了:
這里我們使用argparse來解析參數(shù):
def new_parser(): parser = argparse.ArgumentParser() sub_parser = parser.add_subparsers(help='commands') # A print command print_parser = sub_parser.add_parser( 'print', help='Print all the blocks of the blockchain') print_parser.add_argument('--print', dest='print', action='store_true') # A add command add_parser = sub_parser.add_parser( 'addblock', help='Print all the blocks of the blockchain') add_parser.add_argument( '--data', type=str, dest='add_data', help='block data') return parser def print_chain(bc): for block in bc: print(block) def add_block(bc, data): bc.add_block(data) print("Success!") def main(): parser = new_parser() args = parser.parse_args() bc = BlockChain() if hasattr(args, 'print'): print_chain(bc) if hasattr(args, 'add_data'): add_block(bc, args.add_data) if __name__ == "__main__": main()
測試一下
# 創(chuàng)世塊創(chuàng)建 $python3 main.py Mining a new block Found nonce == 19ash_hex == 047f213bcb01f1ffbcdfafad57ffeead0e86924cf439594020da47ff2508291c <Document 'l'@'191-2f44a1493638684d9e000d8dd105192a' {'hash': 'e4f7adac65bcbb304af21be52a1b52bb28c0205a3746d63453d9e8c182de927a'}> Mining a new block Found nonce == 1ash_hex == 0df1ac18c84a8e524d6fe49cb04aae9af02dd85addc4ab21ac13f9d0d7ffe769 <Document 'l'@'192-168ff7ea493ca53c66690985deb5b7ac' {'hash': '01015004e21d394b1a6574eb81896e1c800f18aa22997e96b79bca22f7821a67'}> Block(_block_header=BlockHeader(timestamp='1551317137.2814202', hash_merkle_root='', prev_block_hash='', hash='f20f3c74c831d03aaa2291af23e607896a61809b5ced222483b46795a456a1c5', nonce=None, height=0)) Block(_block_header=BlockHeader(timestamp='1551317137.358466', hash_merkle_root='', prev_block_hash='f20f3c74c831d03aaa2291af23e607896a61809b5ced222483b46795a456a1c5', hash='e4f7adac65bcbb304af21be52a1b52bb28c0205a3746d63453d9e8c182de927a', nonce=19, height=1)) Block(_block_header=BlockHeader(timestamp='1551317137.4621542', hash_merkle_root='', prev_block_hash='e4f7adac65bcbb304af21be52a1b52bb28c0205a3746d63453d9e8c182de927a', hash='01015004e21d394b1a6574eb81896e1c800f18aa22997e96b79bca22f7821a67', nonce=1, height=2))
$python3 cli.py addblock --data datas Mining a new block Found nonce == 6ash_hex == 0864df4bfbb2fd115eeacfe9ff4d5813754198ba261c469000c29b74a1b391c5 <Document 'l'@'193-92e02b894d09dcd64f8284f141775920' {'hash': '462ac519b6050acaa78e1be8c2c8de298b713a2e138d7139fc882f7ae58dcc88'}> Success!
一切正常工作。
參考:
[1] persistence-and-cli
[2] 完整實現(xiàn)源碼
以上就是python區(qū)塊鏈持久化和命令行接口實現(xiàn)簡版的詳細內(nèi)容,更多關(guān)于區(qū)塊鏈持久化命令行接口的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python爬蟲基礎(chǔ)之XPath語法與lxml庫的用法詳解
這篇文章主要給大家介紹了關(guān)于Python爬蟲基礎(chǔ)之XPath語法與lxml庫用法的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-09-09python ForMaiR實現(xiàn)自定義規(guī)則的郵件自動轉(zhuǎn)發(fā)工具
這篇文章主要為大家介紹了python ForMaiR實現(xiàn)自定義規(guī)則的郵件自動轉(zhuǎn)發(fā)工具示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12pytorch nn.Conv2d()中的padding以及輸出大小方式
今天小編就為大家分享一篇pytorch nn.Conv2d()中的padding以及輸出大小方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-01-01