日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

用 Go 构建一个区块链 -- Part 3: 持久化和命令行接口

發布時間:2025/3/15 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用 Go 构建一个区块链 -- Part 3: 持久化和命令行接口 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

翻譯的系列文章我已經放到了 GitHub 上:blockchain-tutorial,后續如有更新都會在 GitHub 上,可能就不在這里同步了。如果想直接運行代碼,也可以 clone GitHub 上的教程倉庫,進入 src 目錄執行?make?即可。


引言

到目前為止,我們已經構建了一個有工作量證明機制的區塊鏈。有了工作量證明,挖礦也就有了著落。雖然目前的實現離一個有著完整功能的區塊鏈越來越近了,但是它仍然缺少了一些重要的特性。在今天的內容中,我們會將區塊鏈持久化到一個數據庫中,然后會提供一個簡單的命令行接口,用來完成一些與區塊鏈的交互操作。本質上,區塊鏈是一個分布式數據庫,不過,我們暫時先忽略 “分布式” 這個部分,僅專注于 “存儲” 這一點。

選擇數據庫

目前,我們的區塊鏈實現里面并沒有用到數據庫,而是在每次運行程序時,簡單地將區塊鏈存儲在內存中。那么一旦程序退出,所有的內容就都消失了。我們沒有辦法再次使用這條鏈,也沒有辦法與其他人共享,所以我們需要把它存儲到磁盤上。

那么,我們要用哪個數據庫呢?實際上,任何一個數據庫都可以。在?比特幣原始論文?中,并沒有提到要使用哪一個具體的數據庫,它完全取決于開發者如何選擇。?Bitcoin Core?,最初由中本聰發布,現在是比特幣的一個參考實現,它使用的是 ?LevelDB。而我們將要使用的是...

BoltDB

因為它:

  • 非常簡單和簡約
  • 用 Go 實現
  • 不需要運行一個服務器
  • 能夠允許我們構造想要的數據結構
  • BoltDB GitHub 上的?README?是這么說的:

    Bolt 是一個純鍵值存儲的 Go 數據庫,啟發自 Howard Chu 的 LMDB. 它旨在為那些無須一個像 Postgres 和 MySQL 這樣有著完整數據庫服務器的項目,提供一個簡單,快速和可靠的數據庫。

    由于 Bolt 意在用于提供一些底層功能,簡潔便成為其關鍵所在。它的
    API 并不多,并且僅關注值的獲取和設置。僅此而已。

    聽起來跟我們的需求完美契合!來快速過一下:

    Bolt 使用鍵值存儲,這意味著它沒有像 SQL RDBMS (MySQL,PostgreSQL 等等)的表,沒有行和列。相反,數據被存儲為鍵值對(key-value pair,就像 Golang 的 map)。鍵值對被存儲在 bucket 中,這是為了將相似的鍵值對進行分組(類似 RDBMS 中的表格)。因此,為了獲取一個值,你需要知道一個 bucket 和一個鍵(key)。

    需要注意的一個事情是,Bolt 數據庫沒有數據類型:鍵和值都是字節數組(byte array)。鑒于需要在里面存儲 Go 的結構(準確來說,也就是存儲(塊)Block),我們需要對它們進行序列化,也就說,實現一個從 Go struct 轉換到一個 byte array 的機制,同時還可以從一個 byte array 再轉換回 Go struct。雖然我們將會使用 ?encoding/gob? 來完成這一目標,但實際上也可以選擇使用?JSON,?XML,?Protocol Buffers?等等。之所以選擇使用?encoding/gob, 是因為它很簡單,而且是 Go 標準庫的一部分。

    數據庫結構

    在開始實現持久化的邏輯之前,我們首先需要決定到底要如何在數據庫中進行存儲。為此,我們可以參考 Bitcoin Core 的做法:

    簡單來說,Bitcoin Core 使用兩個 “bucket” 來存儲數據:

  • 其中一個 bucket 是?blocks,它存儲了描述一條鏈中所有塊的元數據
  • 另一個 bucket 是?chainstate,存儲了一條鏈的狀態,也就是當前所有的未花費的交易輸出,和一些元數據
  • 此外,出于性能的考慮,Bitcoin Core 將每個區塊(block)存儲為磁盤上的不同文件。如此一來,就不需要僅僅為了讀取一個單一的塊而將所有(或者部分)的塊都加載到內存中。但是,為了簡單起見,我們并不會實現這一點。

    在?blocks?中,key -> value?為:

    keyvalue
    b?+ 32 字節的 block hashblock index record
    f?+ 4 字節的 file numberfile information record
    l?+ 4 字節的 file numberthe last block file number used
    R?+ 1 字節的 boolean是否正在 reindex
    F?+ 1 字節的 flag name length + flag name string1 byte boolean: various flags that can be on or off
    t?+ 32 字節的 transaction hashtransaction index record

    在?chainstatekey -> value?為:

    keyvalue
    c?+ 32 字節的 transaction hashunspent transaction output record for that transaction
    B32 字節的 block hash: the block hash up to which the database represents the unspent transaction outputs

    詳情可見?這里:_Data_Storage)。

    因為目前還沒有交易,所以我們只需要?blocks?bucket。另外,正如上面提到的,我們會將整個數據庫存儲為單個文件,而不是將區塊存儲在不同的文件中。所以,我們也不會需要文件編號(file number)相關的東西。最終,我們會用到的鍵值對有:

  • 32 字節的 block-hash -> block 結構
  • l?-> 鏈中最后一個塊的 hash
  • 這就是實現持久化機制所有需要了解的內容了。

    序列化

    上面提到,在 BoltDB 中,值只能是?[]byte?類型,但是我們想要存儲?Block?結構。所以,我們需要使用?encoding/gob?來對這些結構進行序列化。

    讓我們來實現?Block?的?Serialize?方法(為了簡潔起見,此處略去了錯誤處理):

    func (b *Block) Serialize() []byte {var result bytes.Bufferencoder := gob.NewEncoder(&result)err := encoder.Encode(b)return result.Bytes() }

    這個部分比較直觀:首先,我們定義一個 buffer 存儲序列化之后的數據。然后,我們初始化一個?gob?encoder 并對 block 進行編碼,結果作為一個字節數組返回。

    接下來,我們需要一個解序列化的函數,它會接受一個字節數組作為輸入,并返回一個?Block. 它不是一個方法(method),而是一個單獨的函數(function):

    func DeserializeBlock(d []byte) *Block {var block Blockdecoder := gob.NewDecoder(bytes.NewReader(d))err := decoder.Decode(&block)return &block }

    這就是序列化部分的內容了。

    持久化

    讓我們從?NewBlockchain?函數開始。在之前的實現中,它會創建一個新的
    Blockchain?實例,并向其中加入創世塊。而現在,我們希望它做的事情有:

  • 打開一個數據庫文件
  • 檢查文件里面是否已經存儲了一個區塊鏈
  • 如果已經存儲了一個區塊鏈:

  • 創建一個新的?Blockchain?實例
  • 設置?Blockchain?實例的 tip 為數據庫中存儲的最后一個塊的哈希
  • 如果沒有區塊鏈:

  • 創建創世塊
  • 存儲到數據庫
  • 將創世塊哈希保存為最后一個塊的哈希
  • 創建一個新的?Blockchain?實例,其 tip 指向創世塊(tip 有尾部,尖端的意思,在這里 tip 存儲的是最后一個塊的哈希)
  • 代碼大概是這樣:

    func NewBlockchain() *Blockchain {var tip []bytedb, err := bolt.Open(dbFile, 0600, nil)err = db.Update(func(tx *bolt.Tx) error {b := tx.Bucket([]byte(blocksBucket))if b == nil {genesis := NewGenesisBlock()b, err := tx.CreateBucket([]byte(blocksBucket))err = b.Put(genesis.Hash, genesis.Serialize())err = b.Put([]byte("l"), genesis.Hash)tip = genesis.Hash} else {tip = b.Get([]byte("l"))}return nil})bc := Blockchain{tip, db}return &bc }

    來一段一段地看下代碼:

    db, err := bolt.Open(dbFile,?0600,?nil)

    這是打開一個 BoltDB 文件的標準做法。注意,即使不存在這樣的文件,它也不會返回錯誤。

    err = db.Update(func(tx *bolt.Tx) error { ... })

    在 BoltDB 中,數據庫操作通過一個事務(transaction)進行操作。有兩種類型的事務:只讀(read-only)和讀寫(read-write)。這里,打開的是一個讀寫事務(db.Update(...)),因為我們可能會向數據庫中添加創世塊。

    b := tx.Bucket([]byte(blocksBucket))if b == nil {genesis := NewGenesisBlock()b, err := tx.CreateBucket([]byte(blocksBucket))err = b.Put(genesis.Hash, genesis.Serialize())err = b.Put([]byte("l"), genesis.Hash)tip = genesis.Hash } else {tip = b.Get([]byte("l")) }

    這里是函數的核心。在這里,我們先獲取了存儲區塊的 bucket:如果存在,就從中讀取?l?鍵;如果不存在,就生成創世塊,創建 bucket,并將區塊保存到里面,然后更新?l?鍵以存儲鏈中最后一個塊的哈希。

    另外,注意創建?Blockchain?一個新的方式:

    bc := Blockchain{tip, db}

    這次,我們不在里面存儲所有的區塊了,而是僅存儲區塊鏈的 tip。另外,我們存儲了一個數據庫連接。因為我們想要一旦打開它的話,就讓它一直運行,直到程序運行結束。因此,Blockchain?的結構現在看起來是這樣:

    type Blockchain struct {tip []bytedb *bolt.DB }

    接下來我們想要更新的是?AddBlock?方法:現在向鏈中加入區塊,就不是像之前向一個數組中加入一個元素那么簡單了。從現在開始,我們會將區塊存儲在數據庫里面:

    func (bc *Blockchain) AddBlock(data string) {var lastHash []byteerr := bc.db.View(func(tx *bolt.Tx) error {b := tx.Bucket([]byte(blocksBucket))lastHash = b.Get([]byte("l"))return nil})newBlock := NewBlock(data, lastHash)err = bc.db.Update(func(tx *bolt.Tx) error {b := tx.Bucket([]byte(blocksBucket))err := b.Put(newBlock.Hash, newBlock.Serialize())err = b.Put([]byte("l"), newBlock.Hash)bc.tip = newBlock.Hashreturn nil}) }

    繼續來一段一段分解開來:

    err := bc.db.View(func(tx *bolt.Tx) error {b := tx.Bucket([]byte(blocksBucket))lastHash = b.Get([]byte("l"))return nil })

    這是 BoltDB 事務的另一個類型(只讀)。在這里,我們會從數據庫中獲取最后一個塊的哈希,然后用它來挖出一個新的塊的哈希:

    newBlock := NewBlock(data, lastHash) b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) err = b.Put([]byte("l"), newBlock.Hash) bc.tip = newBlock.Hash

    檢查區塊鏈

    現在,產生的所有塊都會被保存到一個數據庫里面,所以我們可以重新打開一個鏈,然后向里面加入新塊。但是在實現這一點后,我們失去了之前一個非常好的特性:我們再也無法打印區塊鏈的區塊了,因為現在不是將區塊存儲在一個數組,而是放到了數據庫里面。讓我們來解決這個問題!

    BoltDB 允許對一個 bucket 里面的所有 key 進行迭代,但是所有的 key 都以字節序進行存儲,而且我們想要以區塊能夠進入區塊鏈中的順序進行打印。此外,因為我們不想將所有的塊都加載到內存中(因為我們的區塊鏈數據庫可能很大!或者現在可以假裝它可能很大),我們將會一個一個地讀取它們。故而,我們需要一個區塊鏈迭代器(BlockchainIterator):

    type BlockchainIterator struct {currentHash []bytedb *bolt.DB }

    每當要對鏈中的塊進行迭代時,我們就會創建一個迭代器,里面存儲了當前迭代的塊哈希(currentHash)和數據庫的連接(db)。通過?db,迭代器邏輯上被附屬到一個區塊鏈上(這里的區塊鏈指的是存儲了一個數據庫連接的?Blockchain?實例),并且通過?Blockchain?方法進行創建:

    func (bc *Blockchain) Iterator() *BlockchainIterator {bci := &BlockchainIterator{bc.tip, bc.db}return bci }

    注意,迭代器的初始狀態為鏈中的 tip,因此區塊將從頭到尾,也就是從最新的到最舊的進行獲取。實際上,選擇一個 tip 就是意味著給一條鏈“投票”。一條鏈可能有多個分支,最長的那條鏈會被認為是主分支。在獲得一個 tip (可以是鏈中的任意一個塊)之后,我們就可以重新構造整條鏈,找到它的長度和需要構建它的工作。這同樣也意味著,一個 tip 也就是區塊鏈的一種標識符。

    BlockchainIterator?只會做一件事情:返回鏈中的下一個塊。

    func (i *BlockchainIterator) Next() *Block {var block *Blockerr := i.db.View(func(tx *bolt.Tx) error {b := tx.Bucket([]byte(blocksBucket))encodedBlock := b.Get(i.currentHash)block = DeserializeBlock(encodedBlock)return nil})i.currentHash = block.PrevBlockHashreturn block }

    這就是數據庫部分的內容了!

    CLI

    到目前為止,我們的實現還沒有提供一個與程序交互的接口:目前只是在?main?函數中簡單執行了?NewBlockchain?和?bc.AddBlock?。是時候改變了!現在我們想要擁有這些命令:

    blockchain_go addblock "Pay 0.031337 for a coffee" blockchain_go printchain

    所有命令行相關的操作都會通過?CLI?結構進行處理:

    type CLI struct {bc *Blockchain }

    它的 “入口” 是?Run?函數:

    func (cli *CLI) Run() {cli.validateArgs()addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError)printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)addBlockData := addBlockCmd.String("data", "", "Block data")switch os.Args[1] {case "addblock":err := addBlockCmd.Parse(os.Args[2:])case "printchain":err := printChainCmd.Parse(os.Args[2:])default:cli.printUsage()os.Exit(1)}if addBlockCmd.Parsed() {if *addBlockData == "" {addBlockCmd.Usage()os.Exit(1)}cli.addBlock(*addBlockData)}if printChainCmd.Parsed() {cli.printChain()} }

    我們會使用標準庫里面的?flag?包來解析命令行參數:

    addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) addBlockData := addBlockCmd.String("data", "", "Block data")

    首先,我們創建兩個子命令:?addblock?和?printchain, 然后給?addblock?添加?-data?標志。printchain?沒有任何標志。

    switch os.Args[1] { case "addblock":err := addBlockCmd.Parse(os.Args[2:]) case "printchain":err := printChainCmd.Parse(os.Args[2:]) default:cli.printUsage()os.Exit(1) }

    然后,我們檢查用戶提供的命令,解析相關的?flag?子命令:

    if addBlockCmd.Parsed() {if *addBlockData == "" {addBlockCmd.Usage()os.Exit(1)}cli.addBlock(*addBlockData) }if printChainCmd.Parsed() {cli.printChain() }

    接著檢查解析是哪一個子命令,并調用相關函數:

    func (cli *CLI) addBlock(data string) {cli.bc.AddBlock(data)fmt.Println("Success!") }func (cli *CLI) printChain() {bci := cli.bc.Iterator()for {block := bci.Next()fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)fmt.Printf("Data: %s\n", block.Data)fmt.Printf("Hash: %x\n", block.Hash)pow := NewProofOfWork(block)fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))fmt.Println()if len(block.PrevBlockHash) == 0 {break}} }

    這部分內容跟之前的很像,唯一的區別是我們現在使用的是?BlockchainIterator?對區塊鏈中的區塊進行迭代:

    記得不要忘了對?main?函數作出相應的修改:

    func main() {bc := NewBlockchain()defer bc.db.Close()cli := CLI{bc}cli.Run() }

    注意,無論提供什么命令行參數,都會創建一個新的鏈。

    這就是今天的所有內容了! 來看一下是不是如期工作:

    $ blockchain_go printchain No existing blockchain found. Creating a new one... Mining the block containing "Genesis Block" 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109bPrev. hash: Data: Genesis Block Hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b PoW: true$ blockchain_go addblock -data "Send 1 BTC to Ivan" Mining the block containing "Send 1 BTC to Ivan" 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13Success!$ blockchain_go addblock -data "Pay 0.31337 BTC for a coffee" Mining the block containing "Pay 0.31337 BTC for a coffee" 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148Success!$ blockchain_go printchain Prev. hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 Data: Pay 0.31337 BTC for a coffee Hash: 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148 PoW: truePrev. hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b Data: Send 1 BTC to Ivan Hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 PoW: truePrev. hash: Data: Genesis Block Hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b PoW: true

    總結

    在下篇文章中,我們將會實現地址,錢包,(可能實現)交易。盡請收聽!

    鏈接

    • Full source codes
    • Bitcoin Core Data Storage:_Data_Storage)
    • boltdb
    • encoding/gob
    • flag

    本文源碼:part_3

    原文:Building Blockchain in Go. Part 3: Persistence and CLI

    總結

    以上是生活随笔為你收集整理的用 Go 构建一个区块链 -- Part 3: 持久化和命令行接口的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 久久久一区二区三区 | 操欧美老逼 | 麻豆出品 | 在线碰 | 精品在线一区二区三区 | 欧美精品入口蜜桃 | 在线观看www.| 狠狠操操 | 成人在线欧美 | 欧美一级片a | 操韩国美女| 不卡福利视频 | 成人一区二区免费视频 | 又黄又色又爽的视频 | 天天操天天曰 | 麻豆传媒在线观看视频 | 在线va视频| 亚洲经典在线观看 | 91美女网 | 琪琪色在线观看 | 一区二区 中文字幕 | 亚洲乱码精品久久久久 | 日韩精品手机在线 | 国产精品久久久久久白浆 | 精品久久久精品 | 少妇久久久| 夜夜操网站 | 久久青 | 噜噜噜精品欧美成人 | www.69av.com| 亚洲视频你懂的 | 免费三片在线播放 | 日本乱码一区二区 | 日韩成人av免费在线观看 | 日本成人精品 | 好吊色免费视频 | 美女狠狠干 | 91精品国产入口 | 在线播放免费av | 97国产精品人人爽人人做 | 日韩成人不卡 | 五月99久久婷婷国产综合亚洲 | 91免费看大片 | 高清无码视频直接看 | 国内精品999| 欧美日韩在线免费视频 | 日韩有码在线观看 | 日韩在线观看免费高清 | 明星双性精跪趴灌满h | 国产精品午夜一区二区 | 久久视频国产 | 国产av无码专区亚洲精品 | 综合网天天| 嫩草伊人久久精品少妇av | 亚洲va天堂va国产va久 | 青青综合网 | 日本女人黄色 | 超碰一级片 | 亚洲一区二区三区国产 | 国产一区视频在线播放 | 国产精品久久久久久久无码 | 91精品久久久久久久 | 国产极品一区 | 男女污网站 | 区一区二区三 | 依人成人| 秋霞av一区二区三区 | 国产精品宾馆在线精品酒店 | 亚洲高清在线免费观看 | 黄色网络在线观看 | 伊人网免费视频 | 日韩电影在线观看中文字幕 | 日韩国产小视频 | 公车激情云雨小说 | 成人av图片 | 在线岛国 | 超碰天天操 | 久久久久玖玖 | 中文字幕乱码亚洲精品一区 | 亚洲图片在线视频 | 中文字幕亚洲视频 | 熟女视频一区二区三区 | 亚洲av电影一区二区 | 国精品人妻无码一区二区三区喝尿 | 色网站在线播放 | 亚洲国产精品电影 | 成人夜色视频 | 成人区人妻精品一区 | 女人叫床很黄很污句子 | 欧洲美一区二区三区亚洲 | 精品欧美视频 | 国产不卡视频在线观看 | 日韩久久久 | 91精品视频在线 | 国产一区久久久 | 日本黄色免费网站 | 欧美视频在线观看一区二区三区 | 国内外成人免费视频 | 国产福利片一区二区 |