使用Go構建區塊鏈 第1部分:基本原型

銀河1號發表於2019-01-21

Introduction

區塊鏈是21世紀最具革命性的技術之一,至今仍在發展,很多潛力尚未完全實現。從本質上講,區塊鏈只是一個分散式資料庫。但是它的獨特之處在於它不是私人資料庫,而是公共資料庫,也就是說,使用它的每個人都有完整或部分資料副本。只有在資料庫的其他管理員同意的情況下才能新增新記錄。此外,正是由於區塊鏈才使加密貨幣和智慧合約成為可能。

在本系列文章中,我們將構建一個基於簡單區塊鏈實現的簡化加密貨幣。

Block

讓我們從“區塊鏈”的“區塊”部分開始。在區塊鏈中,區塊是用來儲存有價值資訊的。實際上,交易資訊是所有加密貨幣的價值所在。除此之外,區塊包含一些技術資訊,如版本,當前時間戳和前一個塊的雜湊值。在本文中,我們不打算實現像比特幣規範中描述的那種區塊,而是將使用它的簡化版本,它只包含重要資訊。這是它的樣子:

type Block struct {
	Timestamp     int64
	Data          []byte
	PrevBlockHash []byte
	Hash          []byte
}
複製程式碼

Timestamp欄位是當前時間戳(建立區塊時),Data欄位是區塊中包含的實際有價值的資訊,PrevBlockHash欄位儲存前一個區塊的雜湊值,Hash欄位是當前區塊的雜湊值。在比特幣規範中Timestamp, PrevBlockHash, 和 Hash是塊頭,它形成一個單獨的資料結構,Data是另一個資料結構。我們為了簡單起見將它們混合在了一起。

那麼我們如何計算雜湊?雜湊計算方法是區塊鏈的一個非常重要的特徵,正是這個特徵使區塊鏈變得安全。問題在於計算雜湊是一個計算上很困難的操作,即使在快速計算機上也需要一些時間(這就是人們購買功能強大的GPU來挖掘比特幣的原因)。這是一種有意的架構設計,這使得新增新的區塊變得困難,從而防止它們在新增後進行修改。我們將在以後的文章中討論並實現這種機制。

目前,我們僅取了 Block 結構的部分欄位(Timestamp, DataPrevBlockHash),並將它們相互拼接起來,然後在拼接後的結果上計算一個 SHA-256,然後就得到了雜湊.

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約定,我們將實現一個簡化建立區塊的函式:

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

這就是區塊!

Blockchain

現在讓我們實現一個區塊鏈。從本質上講,區塊鏈只是一個具有特定結構的資料庫:它是一個有序的反向連結列表。這意味著區塊以插入順序儲存,並且每個區塊都連結到前一個區塊。這種結構允許快速獲取鏈中的最新區塊並通過其雜湊(有效地)獲得區塊。

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

type Blockchain struct {
	blocks []*Block
}
複製程式碼

這是我們的第一個區塊鏈!我從沒想過會這麼容易?

現在讓我們可以新增塊:

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

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

要新增新的區塊,我們需要一個現有的區塊,但是我們的區塊鏈中沒有區塊!因此,在任何區塊鏈中,必須至少有一個區塊,這個區塊,也就是鏈中的第一個區塊,通常叫做創世區塊(genesis block). 讓我們實現一個方法來建立創世區塊:

func NewGenesisBlock() *Block {
	return NewBlock("Genesis Block", []byte{})
}
複製程式碼

現在,我們可以實現一個函式來建立有創世區塊的區塊鏈:

func NewBlockchain() *Blockchain {
	return &Blockchain{[]*Block{NewGenesisBlock()}}
}
複製程式碼

讓我們檢查下區塊鏈是否正常工作:

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()
	}
}
複製程式碼

Output:

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
複製程式碼

Conclusion

我們構建了一個非常簡單的區塊鏈原型:它只是一個區塊陣列,每個區塊都與前一個區塊連線。實際的區塊鏈顯然複雜得多。在我們的區塊鏈中新增新區塊很容易而且很快,但在實際區塊鏈中新增新區塊需要一些工作:在獲得新增區塊的許可權之前必須執行一些繁重的計算(這種機制稱為工作量證明)。此外,區塊鏈是一個沒有單一決策者的分散式資料庫。因此,必須由網路的其他參與者確認和批准新的區塊(該機制稱為共識)。我們的區塊鏈中還沒有交易!

在以後的文章中,我們將介紹這些功能。

英文原文:jeiwan.cc/posts/build…


更多文章歡迎訪問 http://www.apexyun.com/

聯絡郵箱:public@space-explore.com

(未經同意,請勿轉載)


相關文章