iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
Web 3

從 區塊鏈 到 去中心化應用程式(DApp)系列 第 12

區塊鏈建立: 資料庫的建置與規劃

  • 分享至 

  • xImage
  •  

資料庫的建置與規劃

專案 GitHub 位置: https://github.com/weiawesome/block-chain_go

最開始聽到 區塊鏈(比特幣) 會怎麼形容呢?

不錯 就是分散式帳本

那既然是帳本 就是會紀錄資訊
那就必然需要資料庫

接下來這篇就來實現區塊鏈中的資料庫

分為以下進行介紹與創建

  1. 區塊 資料庫
  2. UTXO 資料庫
  3. 區塊資訊 資料庫
  4. 其餘相關函式

區塊 資料庫

首先必須釐清該儲存哪些資訊 先看回區塊模樣

基本上 區塊哈希值 它並不會重複 且能直接代表整個區塊

所以其實很適合使用 鍵值型(Key-Value)資料庫

鍵(Key) 值(Value)
區塊哈希值 整個區塊資訊

只要透過 鍵(區塊哈希值) 便能輕易找到 值(整個區塊)

透過類似上圖的方式 就能完整建立整個區塊資料庫

儲存格式

檔案位置: "./database/block/model/block_key_value.go"

type BlockKeyValue struct {
	BlockHash string           `bson:"block_hash"`
	Block     blockchain.Block `bson:"block"`
}

獲取區塊內容 函式

檔案位置: "./database/block/repository.go"

func GetBlock(Hash string) (blockchain.Block, error) {
	BlockCollection := utils.GetBlockCollection()
	filter := bson.M{model.BlockKey: Hash}

	var result model.BlockKeyValue
	err := BlockCollection.FindOne(context.TODO(), filter).Decode(&result)

	return result.Block, err
}

設置區塊內容 函式

檔案位置: "./database/block/repository.go"

func SetBlock(Block blockchain.Block) error {
	BlockCollection := utils.GetBlockCollection()
	_, err := BlockCollection.InsertOne(context.TODO(), model.BlockKeyValue{BlockHash: Block.BlockHash, Block: Block})
	return err
}

刪除區塊內容 函式

檔案位置: "./database/block/repository.go"

func DeleteBlock(Hash string) error {
	BlockCollection := utils.GetBlockCollection()

	filter := bson.M{model.BlockKey: Hash}
	
	_, err := BlockCollection.DeleteOne(context.TODO(), filter)

	return err
}

UTXO 資料庫

UTXO 全文是 Unspent Transaction Output
代表著未花費的輸出

那這種資料該如何儲存呢?

首先必須知道UTXO是基於交易產生的 所以必須先看回
交易的模樣:

交易哈希 交易發送地址 交易接收地址 金額 給礦工手續費

但其實對於發送時 可以有多個接收地址 因此接收地址又可以細分
(把每個接受地址的金額 也都仔細記錄進去)

交易接收地址:

交易接收地址 與 金額 ... 交易接收地址 與 金額

所以交易接收地址其實就是個數列 (第一筆、第二筆...

而 交易哈希這個值 邏輯上也不會重複

那資料庫又可以使用鍵值型資料庫建立了

鍵(Key) 值(Value)
接收者的索引 與 交易哈希 地址 與 金額

未來有新的交易出現時
就是提供一筆或多筆 之前接受的交易的金額 再轉出去

不過在花出去後就不能再花費(上面設計無法判斷)
因此可以再新增 一個屬性 查詢是否已經花費

鍵(Key) 值(Value)
接收者的索引 與 交易哈希 地址 與 金額 與 是否已花費

儲存格式

檔案位置: "./database/utxo/model/utxo_key_value.go"

type UTXOKeyValue struct {
	TransactionHash string  `bson:"transaction_hash"`
	Index           int     `bson:"index"`
	Amount          float64 `bson:"amount"`
	Spent           bool    `bson:"spent"`
	Address         string  `bson:"address"`
}

之後再透過 交易哈希 與 索引 聯合成為鍵

獲取 UTXO 內容 函式

檔案位置: "./database/utxo/repository.go"

func GetUTXO(TransactionHash string, Index int) (model.UTXOKeyValue, error) {
	UTXOCollection := utils.GetUTXOCollection()
	filter := bson.M{model.UTXOKey: TransactionHash, model.UTXOIndex: Index}

	var result model.UTXOKeyValue
	err := UTXOCollection.FindOne(context.TODO(), filter).Decode(&result)

	return result, err
}

編輯 UTXO 區塊內容 函式

檔案位置: "./database/utxo/repository.go"

func ReplaceUTXO(value model.UTXOKeyValue) error {
	filter := bson.M{model.UTXOKey: value.TransactionHash, model.UTXOIndex: value.Index}
	UTXOCollection := utils.GetUTXOCollection()
	_, err := UTXOCollection.ReplaceOne(context.TODO(), filter, value)
	return err
}

設置 UTXO 內容 函式

檔案位置: "./database/utxo/repository.go"

func SetUTXO(transaction blockchain.BlockTransaction) error {
	UTXOCollection := utils.GetUTXOCollection()
	for i, to := range transaction.To {
		if _, err := GetUTXO(transaction.TransactionHash, i); err != nil {
			_, err = UTXOCollection.InsertOne(context.TODO(), model.UTXOKeyValue{TransactionHash: transaction.TransactionHash, Index: i, Amount: to.Amount, Address: to.Address, Spent: false})
			if err != nil {
				return err
			}
		}
	}
	for i, from := range transaction.From {
		if utxo, err := GetUTXO(from.UTXOHash, i); err != nil {
			_, err = UTXOCollection.InsertOne(context.TODO(), model.UTXOKeyValue{TransactionHash: transaction.TransactionHash, Index: i, Spent: true})
			if err != nil {
				return err
			}
		} else {
			utxo.Spent = true
			err := ReplaceUTXO(utxo)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

將整個區塊 作為輸入

  1. 將 接收地址們 逐筆 添加進入 utxo 設為未花費
  2. 將 發送地址們 逐筆 添加進入 utxo 設為已花費

區塊資訊 資料庫

透過上方區塊資料庫的建立後

目前可以輕易地進行查詢
不過 目前無法知道區塊鏈的最後位置

因此還需要建立 區塊資訊 資料庫 去儲存一些區塊的額外資訊

對於區塊資訊 所需要的額外資料

  1. 區塊鏈中 最後的區塊 之 區塊哈希值
  2. 區塊鏈中 已被確認的區塊 之 區塊哈希值
  3. 區塊鏈中 預選區塊們 之 區塊哈希值

在 已被確認區塊 與 預選區塊 的設計
是為了區塊鏈中分鏈的處理

儲存格式

檔案位置: "./database/block_control/model/block_control_key_value.go"

const (
	BlockControlKey        = "type"
	LastBlockKeyValue      = "LastBlock"
	CheckedBlockKeyValue   = "CheckedBlock"
	CandidateBlockKeyValue = "CandidateBlock"
)

type BlockControl struct {
	Type      string `bson:"type"`
	BlockHash string `bson:"block_hash"`
}

藉由不同的 種類設計 以此做為分類

最後的區塊 之 相關函式

檔案位置: "./database/block_control/repository.go"

func SetLastBlock(Hash string) error {
	BlockControlCollection := utils.GetBlockControlCollection()

	filter := bson.M{model.BlockControlKey: model.LastBlockKeyValue}
	option := options.Replace().SetUpsert(true)

	_, err := BlockControlCollection.ReplaceOne(context.TODO(), filter, model.BlockControl{Type: model.LastBlockKeyValue, BlockHash: Hash}, option)
	return err
}

func GetLastBlock() (string, error) {
	BlockControlCollection := utils.GetBlockControlCollection()

	filter := bson.M{model.BlockControlKey: model.LastBlockKeyValue}
	var lastBlock model.BlockControl
	err := BlockControlCollection.FindOne(context.TODO(), filter).Decode(lastBlock)
	return lastBlock.BlockHash, err
}

設置 與 獲取 最後區塊 之 哈希值

已被確認的區塊 之 相關函式

檔案位置: "./database/block_control/repository.go"

func SetCheckedBlock(Hash string) error {
	BlockControlCollection := utils.GetBlockControlCollection()

	filter := bson.M{model.BlockControlKey: model.CheckedBlockKeyValue}
	option := options.Replace().SetUpsert(true)

	_, err := BlockControlCollection.ReplaceOne(context.TODO(), filter, model.BlockControl{Type: model.LastBlockKeyValue, BlockHash: Hash}, option)
	return err
}

func GetCheckedBlock() (string, error) {
	BlockControlCollection := utils.GetBlockControlCollection()

	filter := bson.M{model.BlockControlKey: model.CheckedBlockKeyValue}
	var lastBlock model.BlockControl
	err := BlockControlCollection.FindOne(context.TODO(), filter).Decode(lastBlock)
	return lastBlock.BlockHash, err
}

設置 與 獲取 已被確認的區塊 之 哈希值

預選區塊們 之 相關函式

檔案位置: "./database/block_control/repository.go"

func SetCandidateBlock(Hash string) error {
	BlockControlCollection := utils.GetBlockControlCollection()

	_, err := BlockControlCollection.InsertOne(context.TODO(), model.BlockControl{Type: model.LastBlockKeyValue, BlockHash: Hash})

	return err
}

func GetCandidateBlock() ([]string, error) {
	var results []string

	BlockControlCollection := utils.GetBlockControlCollection()

	filter := bson.M{model.BlockControlKey: model.CandidateBlockKeyValue}

	cursor, err := BlockControlCollection.Find(context.TODO(), filter)
	if err != nil {
		return results, err
	}

	for cursor.Next(context.TODO()) {
		var result model.BlockControl
		if err := cursor.Decode(&result); err != nil {
			return results, err
		}
		results = append(results, result.BlockHash)
	}
	return results, err
}

func DeleteCandidateBlock(Hash string) error {
	BlockControlCollection := utils.GetBlockControlCollection()

	filter := bson.M{model.CandidateBlockKeyValue: Hash}

	_, err := BlockControlCollection.DeleteOne(context.TODO(), filter)

	return err
}

設置 與 獲取 與 刪除 預選區塊們 之 哈希值

其餘相關函式

關於資料庫的選擇 鍵值型資料庫很多種
而這次選擇使用 MongoDB

資料庫初始化

檔案位置: "./utils/mongodb_utils.go"

const (
	DataBase        = "DataBase"
	BlockCollection = "Block"
	UTXOCollection  = "UTXO"
	BLockControl    = "BlockControl"
)
const (
	BlockKey = "block_hash"
)
const (
	UTXOKey   = "transaction_hash"
	UTXOIndex = "index"
)

var client *mongo.Client

func BlockCollectInit() error {
	blockCollection := GetBlockCollection()
	_, err := blockCollection.Indexes().CreateOne(
		context.TODO(),
		mongo.IndexModel{
			Keys:    map[string]interface{}{BlockKey: 1},
			Options: options.Index().SetUnique(true),
		},
	)
	return err
}

func UTXOCollectInit() error {
	utxoCollection := GetUTXOCollection()
	_, err := utxoCollection.Indexes().CreateOne(
		context.TODO(),
		mongo.IndexModel{
			Keys: bson.D{
				{Key: UTXOKey, Value: 1},
				{Key: UTXOIndex, Value: 1},
			},
			Options: options.Index().
				SetUnique(true),
		},
	)
	return err
}

func InitClient(address string) error {
	var err error

	clientOptions := options.Client().ApplyURI("mongodb://" + address)
	client, err = mongo.Connect(context.TODO(), clientOptions)
	if err != nil {
		return err
	}

	err = BlockCollectInit()
	if err != nil {
		return err
	}

	err = UTXOCollectInit()
	if err != nil {
		return err
	}

	return nil
}

初始化 內容包括

  1. 建立 MongoDB 連線
  2. 初始化 BlockCollection
  3. 初始化 UTXOCollection

資料庫關閉

檔案位置: "./utils/mongodb_utils.go"

func CloseMongoDb() error {
	return client.Disconnect(context.TODO())
}

獲取各項 Collection

檔案位置: "./utils/mongodb_utils.go"

func GetBlockCollection() *mongo.Collection {
	return client.Database(DataBase).Collection(BlockCollection)
}

func GetUTXOCollection() *mongo.Collection {
	return client.Database(DataBase).Collection(UTXOCollection)
}

func GetBlockControlCollection() *mongo.Collection {
	return client.Database(DataBase).Collection(BLockControl)
}

結言

資料庫的建置 完成!

能對於資訊在區塊鏈中的模樣 應該會有更清晰的模樣

對於資料庫目前已經完成程序的撰寫

希望透過這篇能理解

  1. 在資料庫中 區塊 設計
  2. 在資料庫中 UTXO 設計
  3. 在資料庫中 區塊資訊 設計
  4. 資料庫如何連線與初始化

下回預告

經過一番設計 現在已經能 儲存區塊鏈

也能夠藉由拿取資料庫 獲得區塊內容

藉此每個節點(礦工)上已經清楚如何放資料
不過 節點要如何與其他節點連線呢?
又該如何 傳遞資訊呢?

眾所皆知 區塊鏈 是 點對點(P2P) 網路架構

接下來 趕緊來實現 點的建立 與 點的連接

下回 "區塊鏈建立: 連線協定 與 初始化設定"


上一篇
區塊鏈建立: 設定區塊與鏈
下一篇
區塊鏈建立: 連線協定 與 初始化設定
系列文
從 區塊鏈 到 去中心化應用程式(DApp)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言