深入底層:Go語言從零構建區塊鏈(一): Hello, Blockchain
轉自我的blog:Mingrui Cao‘s Blog
前言
有時覺得前言什麼的可以省略,但作為一個教程還是應該在最開始的時候說兩句。
這個系列的教程目的是使用Golang由淺入深地還原PoW共識機制最基礎區塊鏈系統(參照比特幣),適合想要快速入門區塊鏈核心技術的讀者,當然也適合剛學完Go基礎語法希望練手的讀者。相較於網路上其它Go語言實現區塊鏈的教程,本系列教程以最終建立一個可以分散式執行的區塊鏈系統為目標,使用新版本的Golang(v1。17)及相關包,在還原的過程中會盡量說明一些常規教程忽視的細節,給出所有的程式碼。
大部分人對於區塊鏈技術的學習常常停留在表象,瞭解了UTXO模型,共識機制,P2P網路後,總是迫不及待地就想用區塊鏈往所有涉及隱私與安全的問題裡套。我始終持有的觀點是,可以允許區塊鏈在各種應用場景中試錯,但不應該過於推崇區塊鏈,它不是萬能的,要學習區塊鏈就應該瞭解其本質,從事物的兩面性去研究它。舉個例子,如果我們把區塊鏈當作一個分散式的資料庫看待,那麼它的效能無疑是拉跨的,但是如果充分理解區塊鏈的本質,就能夠明白比特幣為什麼僅僅用一段程式碼就能夠在全球沒有第三方機構的參與下實現資產資訊的長久儲存,穩定執行超過五年,感嘆其精妙之處。
如果想要了解區塊鏈技術的本質,瞭解區塊鏈技術的優缺點以及可能的研究方向,最直接的辦法無疑是閱讀比特幣原始碼。但是比特幣原始碼使用cpp進行編寫,人們常常不知道從哪裡開始進行學習理解,在耗費大量時間與精力的過程中,漸漸消磨學習者對區塊鏈的興趣。本人在學習區塊鏈的過程中發現,透過理解區塊鏈原理一步一步構建區塊鏈系統比直接閱讀比特幣原始碼要有樂趣的多,學習速度也更快,而且最終都能達到同樣的目的,就像B樹的建立往往比B樹的查詢容易學習理解一樣。
為了達到使讀者快速入門並理解區塊鏈核心技術的目的,本教程不太注重對Go語言相關問題的講解,關注的是程式碼背後的區塊鏈原理與實現細節。讀者需要做的只是建立一個空專案,從零開始跟隨本教程敲下一行又一行的程式碼。對讀者來說,重要的不是對我所寫的程式碼進行改動,而是理解每一行程式碼的意義。
由於本人Go語言也還處於學習階段,故一些部分程式碼可能較為冗餘。區塊鏈實現細節有誤的地方也隨時歡迎討論指正。
完整的專案地址:https://github.com/leo201313/Blockchain_with_Go
建立專案
首先我們需要建立一個專案。建立一個資料夾命名為goblockchain(當然你也可以取一個霸氣的名字,比如lighteningchain,goXchain等等),然後使用VS(Visual Studio Code,推薦使用VS作為IDE)開啟資料夾,如下圖。
此時檔案中什麼都沒有,我們使用go mod來初始化專案,點選VS左下方的小三角,在terminal中輸入go mod init goblockchain。
此時資料夾中將會多出一個go。mod檔案,證明專案已經初始化成功。在goblockchain資料夾下建立main。go檔案。
區塊與區塊鏈
區塊鏈以區塊(block)的形式儲存資料資訊,一個區塊記錄了一段時間內系統或網路中產生的重要資料資訊,區塊透過引用上一個區塊的hash值來連線上一個區塊這樣區塊就按時間順序排列形成了一條鏈。每個區塊應該包含頭部(head)資訊用於總結性的描述這個區塊,然後在區塊的資料存放區(body)中存放要儲存的重要資料。首先我們需要初始化main。go,並匯入一些基本的包。
//main。go
package
main
import
(
“bytes”
“crypto/sha256”
“encoding/binary”
“fmt”
“log”
“time”
)
func
main
{
}
然後定義區塊的結構體。
//main。go
type
Block
struct
{
Timestamp
int64
Hash
[]
byte
PrevHash
[]
byte
Data
[]
byte
}
我們定義的區塊中有時間戳,本身的雜湊值,指向上一個區塊的雜湊這三個屬性構成頭部資訊,而區塊中的資料以Data屬性表示。在獲得了區塊後,我們可以定義區塊鏈。
//main。go
type
BlockChain
struct
{
Blocks
[]
*
Block
}
可以看到我們這裡的區塊鏈就是區塊的一個集合。好了,現在你已經掌握了區塊與區塊鏈了,現在就可以去搭建自己的區塊鏈系統了。
雜湊
QVQ,好吧,我們現在來給我們的區塊增加點細節,來看看它們是怎麼連線起來的。對於一個區塊而言,可以透過雜湊演算法概括其所包含的所有資訊,雜湊值就相當於區塊的ID值,同時也可以用來檢查區塊所包含資訊的完整性。雜湊函式構造如下。
//main。go
func
(
b
*
Block
)
SetHash
()
{
information
:=
bytes
。
Join
([][]
byte
{
ToHexInt
(
b
。
Timestamp
),
b
。
PrevHash
,
b
。
Data
},[]
byte
{})
hash
:=
sha256
。
Sum256
(
information
)
b
。
Hash
=
hash
[:]
}
func
ToHexInt
(
num
int64
)
[]
byte
{
buff
:=
new
(
bytes
。
Buffer
)
err
:=
binary
。
Write
(
buff
,
binary
。
BigEndian
,
num
)
if
err
!=
nil
{
log
。
Panic
(
err
)
}
return
buff
。
Bytes
()
}
information變數是將區塊的各項屬性串聯之後的位元組串。這裡提醒一下bytes。Join可以將多個位元組串連線,第二個引數是將位元組串連線時的分隔符,這裡設定為[]byte{}即為空,ToHexInt將int64轉換為位元組串型別。然後我們對information做雜湊就可以得到區塊的雜湊值了。
區塊建立與創始區塊
既然我們可以獲得區塊的雜湊值了,我們就能夠建立區塊了。
//main。go
func
CreateBlock
(
prevhash
,
data
[]
byte
)
*
Block
{
block
:=
Block
{
time
。
Now
()。
Unix
(),
[]
byte
{},
prevhash
,
data
}
block
。
SetHash
()
return
&
block
}
可以看到在建立一個區塊時一定要引用前一個區塊的雜湊值,這裡會有一個問題,那就是區塊鏈中的第一個區塊怎麼建立?其實,在區塊鏈中有一個創世區塊,隨著區塊鏈的建立而新增,它指向的上一個區塊的雜湊值為空。
//main。go
func
GenesisBlock
()
*
Block
{
genesisWords
:=
“Hello, blockchain!”
return
CreateBlock
([]
byte
{},
[]
byte
(
genesisWords
))
}
可以看到我們在創始區塊中存放了
Hello, blockchain!
這段資訊。現在我們來構建函式,使得區塊鏈可以根據其它資訊建立區塊進行儲存。
//main。go
func
(
bc
*
BlockChain
)
AddBlock
(
data
string
)
{
newBlock
:=
CreateBlock
(
bc
。
Blocks
[
len
(
bc
。
Blocks
)
-
1
]。
Hash
,
[]
byte
(
data
))
bc
。
Blocks
=
append
(
bc
。
Blocks
,
newBlock
)
}
最後我們構建一個區塊鏈初始化函式,使其返回一個包含創始區塊的區塊鏈。
//main。go
func
CreateBlockChain
()
*
BlockChain
{
blockchain
:=
BlockChain
{}
blockchain
。
Blocks
=
append
(
blockchain
。
Blocks
,
GenesisBlock
())
return
&
blockchain
}
執行區塊鏈系統
現在我們已經擁有了所有建立區塊鏈需要的函數了,來看看我們的區塊鏈是怎麼運作的。
//main。go
func
main
()
{
blockchain
:=
CreateBlockChain
()
time
。
Sleep
(
time
。
Second
)
blockchain
。
AddBlock
(
“After genesis, I have something to say。”
)
time
。
Sleep
(
time
。
Second
)
blockchain
。
AddBlock
(
“Leo Cao is awesome!”
)
time
。
Sleep
(
time
。
Second
)
blockchain
。
AddBlock
(
“I can’t wait to follow his github!”
)
time
。
Sleep
(
time
。
Second
)
for
_
,
block
:=
range
blockchain
。
Blocks
{
fmt
。
Printf
(
“Timestamp: %d\n”
,
block
。
Timestamp
)
fmt
。
Printf
(
“hash: %x\n”
,
block
。
Hash
)
fmt
。
Printf
(
“Previous hash: %x\n”
,
block
。
PrevHash
)
fmt
。
Printf
(
“data: %s\n”
,
block
。
Data
)
}
}
在terminal中輸入go run main。go,輸出如下。
D:
\l
earngo
\g
oblockchain>go run main。go
Timestamp:
1632471455
hash: 289c596026a32c6ac5702fd2d3c96104d6b7178de49beb70a71c100ee839ac26
Previous hash:
data: Hello, blockchain!
Timestamp:
1632471456
hash: a29d04ef59529bb50b1526393203ebf7cc60d8f0ddfbb09900475c9dcf180d3b
Previous hash: 289c596026a32c6ac5702fd2d3c96104d6b7178de49beb70a71c100ee839ac26
data: After genesis, I have something to say。
Timestamp:
1632471457
hash: 69eb263ab680cc0d45530c5ba0db1514255c891e084c3f04bfb416f0f1b06a59
Previous hash: a29d04ef59529bb50b1526393203ebf7cc60d8f0ddfbb09900475c9dcf180d3b
data: Leo Cao is awesome!
Timestamp:
1632471458
hash: 453ff251f95183c92ace277dec4181d5c71582129dc31cce3ceb37c7b1377efc
Previous hash: 69eb263ab680cc0d45530c5ba0db1514255c891e084c3f04bfb416f0f1b06a59
data: I can
‘
t
wait
to follow his github!
你需要注意的是創始區塊沒有Previous Hash,同時後面的每一個區塊都保留了前一個區塊的雜湊值。
總結
在本章中,我們構建了一個最簡單的區塊鏈模型。本章需要重點理解區塊與區塊鏈的關係,區塊的雜湊值的意義,以及創世區塊的構建。在下一章中,我們將講解PoW(Proof of Work)共識機制,並增加一些區塊結構體的頭部資訊。