專案 GitHub 位置: https://github.com/weiawesome/block-chain_go
最開始聽到 區塊鏈(比特幣) 會怎麼形容呢?
不錯 就是分散式帳本
那既然是帳本 就是會紀錄資訊
那就必然需要資料庫
接下來這篇就來實現區塊鏈中的資料庫
分為以下進行介紹與創建
首先必須釐清該儲存哪些資訊 先看回區塊模樣
基本上 區塊哈希值 它並不會重複 且能直接代表整個區塊
所以其實很適合使用 鍵值型(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 全文是 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"`
}
之後再透過 交易哈希 與 索引 聯合成為鍵
檔案位置: "./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
}
檔案位置: "./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
}
檔案位置: "./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
}
將整個區塊 作為輸入
透過上方區塊資料庫的建立後
目前可以輕易地進行查詢
不過 目前無法知道區塊鏈的最後位置
因此還需要建立 區塊資訊 資料庫 去儲存一些區塊的額外資訊
對於區塊資訊 所需要的額外資料
在 已被確認區塊 與 預選區塊 的設計
是為了區塊鏈中分鏈的處理
檔案位置: "./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
}
初始化 內容包括
檔案位置: "./utils/mongodb_utils.go"
func CloseMongoDb() error {
return client.Disconnect(context.TODO())
}
檔案位置: "./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)
}
資料庫的建置 完成!
能對於資訊在區塊鏈中的模樣 應該會有更清晰的模樣
對於資料庫目前已經完成程序的撰寫
希望透過這篇能理解
經過一番設計 現在已經能 儲存區塊鏈
也能夠藉由拿取資料庫 獲得區塊內容
藉此每個節點(礦工)上已經清楚如何放資料
不過 節點要如何與其他節點連線呢?
又該如何 傳遞資訊呢?
眾所皆知 區塊鏈 是 點對點(P2P) 網路架構
接下來 趕緊來實現 點的建立 與 點的連接