2018-05-28第一個區(qū)塊鏈的創(chuàng)建和踩過的坑

區(qū)塊鏈是 21 世紀最具革命性的技術之一,它仍然處于不斷成長的階段,而且還有很多潛力尚未顯現。 本質上,區(qū)塊鏈只是一個分布式數據庫而已。 不過,使它獨一無二的是,區(qū)塊鏈是一個公開的數據庫,而不是一個私人數據庫,也就是說,每個使用它的人都有一個完整或部分的副本。 只有經過其他“數據庫管理員”的同意,才能向數據庫中添加新的記錄。 此外,也正是由于區(qū)塊鏈,才使得加密貨幣和智能合約成為現實。

在本系列文章中,我們將實現一個簡化版的區(qū)塊鏈,并基于它來構建一個簡化版的加密貨幣。

區(qū)塊

首先從 “區(qū)塊” 談起。在區(qū)塊鏈中,真正存儲有效信息的是區(qū)塊(block)。而在比特幣中,真正有價值的信息就是交易(transaction)。實際上,交易信息是所有加密貨幣的價值所在。除此以外,區(qū)塊還包含了一些技術實現的相關信息,比如版本,當前時間戳和前一個區(qū)塊的哈希。

不過,我們要實現的是一個簡化版的區(qū)塊鏈,而不是一個像比特幣技術規(guī)范所描述那樣成熟完備的區(qū)塊鏈。所以在我們目前的實現中,區(qū)塊僅包含了部分關鍵信息,它的數據結構如下:

type Block struct {
    Timestamp     int64
    Data          []byte
    PrevBlockHash []byte
    Hash          []byte
}

字段 解釋
Timestamp 當前時間戳,也就是區(qū)塊創(chuàng)建的時間
PrevBlockHash 前一個塊的哈希,即父哈希
Hash 當前塊的哈希
Data 區(qū)塊存儲的實際有效信息,也就是交易

我們這里的 Timestamp,PrevBlockHash, Hash,在比特幣技術規(guī)范中屬于區(qū)塊頭(block header),區(qū)塊頭是一個單獨的數據結構。 完整的 比特幣的區(qū)塊頭(block header)結構 如下:

Field Purpose Updated when... Size (Bytes)
Version Block version number You upgrade the software and it specifies a new version 4
hashPrevBlock 256-bit hash of the previous block header A new block comes in 32
hashMerkleRoot 256-bit hash based on all of the transactions in the block A transaction is accepted 32
Time Current timestamp as seconds since 1970-01-01T00:00 UTC Every few seconds 4
Bits Current target in compact format The difficulty is adjusted 4
Nonce 32-bit number (starts at 0) A hash is tried (increments) 4

下面是比特幣的 golang 實現 btcd 的 BlockHeader 定義:

// BlockHeader defines information about a block and is used in the bitcoin
// block (MsgBlock) and headers (MsgHeaders) messages.
type BlockHeader struct {
    // Version of the block.  This is not the same as the protocol version.
    Version int32

    // Hash of the previous block in the block chain.
    PrevBlock chainhash.Hash

    // Merkle tree reference to hash of all transactions for the block.
    MerkleRoot chainhash.Hash

    // Time the block was created.  This is, unfortunately, encoded as a
    // uint32 on the wire and therefore is limited to 2106.
    Timestamp time.Time

    // Difficulty target for the block.
    Bits uint32

    // Nonce used to generate the block.
    Nonce uint32
}

而我們的 Data, 在比特幣中對應的是交易,是另一個單獨的數據結構。為了簡便起見,目前將這兩個數據結構放在了一起。在真正的比特幣中,區(qū)塊 的數據結構如下:

Field Description Size
Magic no value always 0xD9B4BEF9 4 bytes
Blocksize number of bytes following up to end of block 4 bytes
Blockheader consists of 6 items 80 bytes
Transaction counter positive integer VI = VarInt 1 - 9 bytes
transactions the (non empty) list of transactions <transaction counter="" style="box-sizing: border-box; -webkit-font-smoothing: antialiased; font-size: inherit;">-many transactions</transaction>

在我們的簡化版區(qū)塊中,還有一個 Hash 字段,那么,要如何計算哈希呢?哈希計算,是區(qū)塊鏈一個非常重要的部分。正是由于它,才保證了區(qū)塊鏈的安全。計算一個哈希,是在計算上非常困難的一個操作。即使在高速電腦上,也要耗費很多時間 (這就是為什么人們會購買 GPU,FPGA,ASIC 來挖比特幣) 。這是一個架構上有意為之的設計,它故意使得加入新的區(qū)塊十分困難,繼而保證區(qū)塊一旦被加入以后,就很難再進行修改。在接下來的內容中,我們將會討論和實現這個機制。

目前,我們僅取了 Block 結構的部分字段(Timestamp, DataPrevBlockHash),并將它們相互拼接起來,然后在拼接后的結果上計算一個 SHA-256,然后就得到了哈希.

Hash = SHA256(PrevBlockHash + Timestamp + Data)

SetHash 方法中完成這些操作:

func (b *Block) SetHash() {
    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
    hash := sha256.Sum256(headers)

    b.Hash = hash[:]
}

接下來,按照 Golang 的慣例,我們會實現一個用于簡化創(chuàng)建區(qū)塊的函數 NewBlock

func NewBlock(data string, prevBlockHash []byte) *Block {
    block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
    block.SetHash()
    return block
}

區(qū)塊鏈

有了區(qū)塊,下面讓我們來實現區(qū)塊。本質上,區(qū)塊鏈就是一個有著特定結構的數據庫,是一個有序,每一個塊都連接到前一個塊的鏈表。也就是說,區(qū)塊按照插入的順序進行存儲,每個塊都與前一個塊相連。這樣的結構,能夠讓我們快速地獲取鏈上的最新塊,并且高效地通過哈希來檢索一個塊。

在 Golang 中,可以通過一個 array 和 map 來實現這個結構:array 存儲有序的哈希(Golang 中 array 是有序的),map 存儲 hash -> block 對(Golang 中, map 是無序的)。 但是在基本的原型階段,我們只用到了 array,因為現在還不需要通過哈希來獲取塊。

type Blockchain struct {
    blocks []*Block
}

這就是我們的第一個區(qū)塊鏈!是不是出乎意料地簡單? 就是一個 Block 數組。

現在,讓我們能夠給它添加一個區(qū)塊:

func (bc *Blockchain) AddBlock(data string) {
    prevBlock := bc.blocks[len(bc.blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.blocks = append(bc.blocks, newBlock)
}

結束!不過,就這樣就完成了嗎?

為了加入一個新的塊,我們必須要有一個已有的塊,但是,初始狀態(tài)下,我們的鏈是空的,一個塊都沒有!所以,在任何一個區(qū)塊鏈中,都必須至少有一個塊。這個塊,也就是鏈中的第一個塊,通常叫做創(chuàng)世塊(genesis block). 讓我們實現一個方法來創(chuàng)建創(chuàng)世塊:

func NewGenesisBlock() *Block {
    return NewBlock("Genesis Block", []byte{})
}

現在,我們可以實現一個函數來創(chuàng)建有創(chuàng)世塊的區(qū)塊鏈:

func NewBlockchain() *Blockchain {
    return &Blockchain{[]*Block{NewGenesisBlock()}}
}

檢查一個我們的區(qū)塊鏈是否如期工作:

func main() {
    bc := NewBlockchain()

    bc.AddBlock("Send 1 BTC to Ivan")
    bc.AddBlock("Send 2 more BTC to Ivan")

    for _, block := range bc.blocks {
        fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
        fmt.Printf("Data: %s\n", block.Data)
        fmt.Printf("Hash: %x\n", block.Hash)
        fmt.Println()
    }
}

輸出:

Prev. hash:
Data: Genesis Block
Hash: aff955a50dc6cd2abfe81b8849eab15f99ed1dc333d38487024223b5fe0f1168

Prev. hash: aff955a50dc6cd2abfe81b8849eab15f99ed1dc333d38487024223b5fe0f1168
Data: Send 1 BTC to Ivan
Hash: d75ce22a840abb9b4e8fc3b60767c4ba3f46a0432d3ea15b71aef9fde6a314e1

Prev. hash: d75ce22a840abb9b4e8fc3b60767c4ba3f46a0432d3ea15b71aef9fde6a314e1
Data: Send 2 more BTC to Ivan
Hash: 561237522bb7fcfbccbc6fe0e98bbbde7427ffe01c6fb223f7562288ca2295d1

總結

我們創(chuàng)建了一個非常簡單的區(qū)塊鏈原型:它僅僅是一個數組構成的一系列區(qū)塊,每個塊都與前一個塊相關聯(lián)。真實的區(qū)塊鏈要比這復雜得多。在我們的區(qū)塊鏈中,加入新的塊非常簡單,也很快,但是在真實的區(qū)塊鏈中,加入新的塊需要很多工作:你必須要經過十分繁重的計算(這個機制叫做工作量證明),來獲得添加一個新塊的權力。并且,區(qū)塊鏈是一個分布式數據庫,并且沒有單一決策者。因此,要加入一個新塊,必須要被網絡的其他參與者確認和同意(這個機制叫做共識(consensus))。還有一點,我們的區(qū)塊鏈還沒有任何的交易!

進入 src 目錄查看代碼,執(zhí)行 make 即可運行:

$ cd src
$ make
==> Go build
==> Running
Prev. hash:
Data: Genesis Block
Hash: 4693b71eee96760de4b0f051083376dcbed2f0711a44294ee5fd42fbeacc9579

Prev. hash: 4693b71eee96760de4b0f051083376dcbed2f0711a44294ee5fd42fbeacc9579
Data: Send 1 BTC to Ivan
Hash: 839380a2d0af1dc4686f16ade5423fecdc5f287db9322d9e18adcb4071e7c8ff

Prev. hash: 839380a2d0af1dc4686f16ade5423fecdc5f287db9322d9e18adcb4071e7c8ff
Data: Send 2 more BTC to Ivan
Hash: b38052a029bd2b1b9d4bb478af45b4c88605e99bc64e49031ba06d21ad4b0b38

// 上面的data類型不統(tǒng)一但是可以做到統(tǒng)一這樣類型定義不嚴謹代碼如下

1.block.go 類

package block

import (
    "bytes"
    "crypto/sha256"
    "strconv"
    "time"
)

 //Block keeps block headers
type Block struct {
    Timestamp     int64
    Data          []byte
    PrevBlockHash []byte
    Hash          []byte
}


 //SetHash calculates and sets block hash
func (b *Block) SetHash() {
    timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
    headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
    hash := sha256.Sum256(headers)

    b.Hash = hash[:]
}



// NewBlock creates and returns Block
func NewBlock(data []byte, prevBlockHash []byte) *Block {
    block := &Block{time.Now().Unix(), data, prevBlockHash, []byte{}}
    block.SetHash()
    return block
}


// NewGenesisBlock creates and returns genesis Block
func NewGenesisBlock() *Block {
    return NewBlock([]byte("dhdhhdhdhdhd"), []byte{})
}

2.blockchain類

package block

// Blockchain keeps a sequence of Blocks
type Blockchain struct {
    Blocks []*Block
}

// AddBlock saves provided data as a block in the blockchain
func (bc *Blockchain) AddBlock(data []byte) {
    prevBlock := bc.Blocks[len(bc.Blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.Blocks = append(bc.Blocks, newBlock)
}

// NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain() *Blockchain {
    return &Blockchain{[]*Block{NewGenesisBlock()}}
}

3.main.go

package main

import (
    "fmt"
    "kongyxueyuan.com/PublicChain/part_1/block"

    "time"
)

func main() {
    bc := block.NewBlockchain()

    bc.AddBlock([]byte("Send 1 more BTC to Iva"))
    fmt.Println([]byte("Send 1 more BTC to Iva"))
    bc.AddBlock([]byte("Send 2 more BTC to Iva"))

    for _, Block := range bc.Blocks {
        fmt.Printf("Prev. hash: %x\n", Block.PrevBlockHash)
        fmt.Printf("Data: %s\n", Block.Data)
        fmt.Printf("Hash: %x\n", Block.Hash)
        fmt.Printf("Timestamp:%s \n",time.Unix(Block.Timestamp, 0).Format("2006-01-02 03:04:05 PM") )

        fmt.Println()
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容