接下來就要透過 Go 實現區塊鏈
專案 GitHub 位置: https://github.com/weiawesome/block-chain_go
在上回 視覺化的區塊鏈 時
已經大部分實現過基礎區塊及計算的部分
不過在此處會面臨區塊鏈中更仔細的問題
包括交易的細節的內容、計算細節等等
首先列出此次實現的重點
(像是此處就先將 區塊裡的交易 與 上傳的交易 做分離)
檔案位置: "./block_structure/blockchain/block.go"
物件格式:
type Block struct {
BlockTop BlockTop `json:"blockTop"`
BlockTransactions []BlockTransaction `json:"blockTransactions"`
BlockHash string `json:"blockHash"`
BlockMeta BlockMeta `json:"blockMeta"`
}
屬性 BlockTop 物件後方實現
屬性 List of BlockTransaction 物件後方實現
屬性 BlockMeta 主要放置一些訊息進去 物件後方實現
計算MarkleRoot方法:
func (b *Block) ComputeMarkleRoot() {
var HashList []string
for _, transaction := range b.BlockTransactions {
HashList = append(HashList, transaction.TransactionHash)
}
for len(HashList) != 1 {
var tmpHashList []string
for i := range HashList {
if i%2 == 0 {
if i == len(HashList)-1 {
tmpHashList = append(tmpHashList, utils.HashSHA256(HashList[i]))
} else {
tmpHashList = append(tmpHashList, utils.HashSHA256(HashList[i])+utils.HashSHA256(HashList[i+1]))
}
}
}
HashList = tmpHashList
}
b.BlockTop.MarkleRoot = HashList[0]
}
配合上圖作為解釋,(感覺是棵樹對吧(資料結構 哈哈哈
每次循環都是計算下一層的並且取代掉原本的
最後直到長度為1時 此時的值 就是 Markle Root
計算區塊哈希方法:
func (b *Block) ComputeBlockHash() {
b.BlockHash = utils.HashSHA256(utils.HashSHA256(b.BlockTop.toString()))
}
將區塊標頭的資訊連續哈希兩次便是此區塊的區塊哈希值
檢查區塊難度方法:
func (b *Block) CheckDifficulty() bool {
for i := int64(0); i < b.BlockTop.Difficulty; i++ {
if b.BlockHash[i] != '0' {
return false
}
}
return true
}
檢查 區塊哈希值 的前方0的個數是否大於等於 區塊難度
檔案位置: "./block_structure/blockchain/block_top.go"
物件格式:
type BlockTop struct {
Version int32 `json:"version"`
PreviousHash string `json:"previousHash"`
TimeStamp int64 `json:"timeStamp"`
Difficulty int64 `json:"difficulty"`
Nonce uint32 `json:"nonce"`
MarkleRoot string `json:"markleRoot"`
}
基本上 可以看到 區塊標頭的物件實現 基本與當初沒什麼差別
不過對於每個值的屬性使用更精確的型態
屬性 使用 int32 用數字代表版本號
屬性 使用 string 用字串顯示哈希值
屬性 使用 int64 用秒級的時間資訊 確保準確度
屬性 使用 int64 用數字代表區塊難度
屬性 使用 uint64 用沒分正負的64位元整數 來做為隨機數
屬性 使用 string 用字串顯示MarkleRoot值
轉字串方法:
func (bt BlockTop) ToString() string {
return string(bt.Version) +
bt.PreviousHash +
strconv.FormatInt(bt.TimeStamp, 10) +
strconv.FormatInt(bt.Difficulty, 10) +
strconv.FormatUint(uint64(bt.Nonce), 10) +
bt.MarkleRoot
}
檔案位置: "./block_structure/blockchain/block_transaction.go"
物件格式:
type BlockTransaction struct {
From []From `json:"from"`
To []To `json:"to"`
Fee float64 `json:"fee"`
TransactionHash string `json:"transactionHash"`
}
type From struct {
UTXOHash string
Index int
}
type To struct {
Address string
Amount float64
}
交易發起人必須提出它一筆或多筆未花費的紀錄作為此次花費依據
交易接收人可以是一位或多位 提供每位的地址與給予金額
給予礦工的小費
屬性 string 用來表示該筆的交易哈希
轉為字串方法:
func (bt *BlockTransaction) ToString() (string, error) {
fromJson, err := json.Marshal(bt.From)
if err != nil {
return "", err
}
toJson, err := json.Marshal(bt.To)
if err != nil {
return "", err
}
return string(fromJson) + string(toJson) + strconv.FormatFloat(bt.Fee, 'f', -1, 64), nil
}
檔案位置: "./block_structure/blockchain/block_meta.go"
物件格式:
type BlockMeta struct {
Content string `json:"content"`
}
基本就是放一些訊息的地方(通常此處都會是空白)
未來有其他附加功能也可以放在此處
檔案位置: "./block_structure/transaction/transaction.go"
物件格式:
type Transaction struct {
From []From
To []To
Fee float64
Signature string
TransactionHash string
PublicKey string
}
type From struct {
UTXOHash string
Index int
}
type To struct {
Address string
Amount float64
}
整體格式與上方區塊交易內容差異不大
因此此處僅提出差異內容
提交交易時 必須提供公鑰 以作為驗證該筆交易合法性
包括 檢查UTXO是否是屬於本人、簽名合法性
為交易發起者對該筆交易的簽名 但不會放進區塊鏈中
轉為字串方法:
func (t Transaction) ToString() (string, error) {
fromJson, err := json.Marshal(t.From)
if err != nil {
return "", err
}
toJson, err := json.Marshal(t.To)
if err != nil {
return "", err
}
return string(fromJson) + string(toJson) + strconv.FormatFloat(t.Fee, 'f', -1, 64), nil
}
檔案位置: "./utils/hash.go"
func HashSHA256(value string) string {
data := []byte(value)
hash := sha256.New()
hash.Write(data)
hashedData := hash.Sum(nil)
hashString := hex.EncodeToString(hashedData)
return hashString
}
檔案位置: "./block_structure/utils/transaction_tools.go"
func ConvertTransaction(transaction transaction.Transaction) blockchain.BlockTransaction {
var bt blockchain.BlockTransaction
bt.TransactionHash = transaction.TransactionHash
bt.Fee = transaction.Fee
for _, from := range transaction.From {
bt.From = append(bt.From, blockchain.From{UTXOHash: from.UTXOHash, Index: from.Index})
}
for _, to := range transaction.To {
bt.To = append(bt.To, blockchain.To{Address: to.Address, Amount: to.Amount})
}
return bt
}
檔案位置: "./block_structure/utils/transaction_tools.go"
func MinerTransaction(Address string) (blockchain.BlockTransaction, error) {
var bt blockchain.BlockTransaction
bt.To = append(bt.To, blockchain.To{Address: Address})
stringValue, err := bt.ToString()
if err != nil {
return bt, err
}
bt.TransactionHash = utils.HashSHA256(stringValue)
return bt, nil
}
礦工在撰寫區塊時
在交易內容上
可以將第一筆內容的 To 寫為自己錢包的地址
這樣便可以證明該筆區塊(如果計算完成)的持有權
未來此區塊生成的虛擬貨幣就屬於 該名礦工的
目前總算完成一個關於區塊以及鏈的基本設定
主要是在處理 交易、區塊
而這兩項是組成區塊鏈的關鍵元素
希望透過這篇你能理解
其實可以感受到這篇代碼非常多。
認為可以先(閱讀)理解一個基礎設計概念。
再透過 GitHub 下載 Source code 比較能輕鬆理解
搭配使用 便更能夠理解 其中原理!
經過巧妙的設計
目前區塊及交易的 型態與方法 已經有了基本的塑形
不過區塊鏈儲存資料的地方在哪裡呢?
又是如何的儲存資訊的呢?
著實令人好奇!!!
接下來是時候再來著手 關於資料庫的建立了