專案 GitHub 位置: https://github.com/weiawesome/block-chain_go
首先 區塊鏈網路 是 點對點(P2P)
所以這次協議設計
打算採用 基於 TCP 之上
至於初始化的設定
包括 區塊 與 UTXO 的資料初始化
因此這篇主要以幾點敘述
首先在建立節點監聽前
可以先決定連線的節點(已存在區塊鏈網上的節點)
檔案位置: "./protocal/connection/node_build.go"
func BuildNode(NodeAddr string, NodeAddresses []string, TransactionChannel chan transaction.Transaction, BlockChannel chan blockchain.Block, BroadcastTransactionChannel chan transaction.Transaction, BroadcastBlockChannel chan blockchain.Block) {
ConnectionChannel := make(chan net.Conn)
go CommunicateClient(ConnectionChannel, BroadcastTransactionChannel, BroadcastBlockChannel)
for _, addr := range NodeAddresses {
conn, err := net.Dial("tcp", addr)
if err != nil {
continue
}
go InitBlocks(conn)
ConnectionChannel <- conn
}
}
備註:
Goroutine是Go語言中的輕量級執行緒,用於並行編程。
與傳統執行緒(Threads)相比,Goroutine更加高效,
可輕鬆創建數千個,而不會消耗大量記憶體。
每個Goroutine都由Go運行時管理,
具有獨立的執行流程,但共享相同的記憶體空間。
透過通道(Channel)進行Goroutine之間的通訊,實現數據同步。
接下來要建立節點 可以接收其他節點的連線
每當新的連線出現 便開啟一個 Goroutine 處理相關連線
檔案位置: "./protocal/connection/node_build.go"
func BuildNode(NodeAddr string, NodeAddresses []string, TransactionChannel chan transaction.Transaction, BlockChannel chan blockchain.Block, BroadcastTransactionChannel chan transaction.Transaction, BroadcastBlockChannel chan blockchain.Block) {
listener, err := net.Listen("tcp", NodeAddr)
if err != nil {
panic(err)
return
}
defer func(listener net.Listener) {
err := listener.Close()
if err != nil {
panic(err)
}
}(listener)
for {
conn, err := listener.Accept()
if err != nil {
utils.LogError(err.Error())
continue
}
go ReceiveReplyClient(conn, TransactionChannel, BlockChannel)
ConnectionChannel <- conn
}
}
對於節點間通訊內容包括
最後則是 相互溝通的函式
檔案位置: "./protocal/connection/content.go"
type BroadcastTransaction struct {
Transaction transaction.Transaction
}
type BroadcastBlock struct {
Block blockchain.Block `json:"block"`
}
基本上 就是傳輸出去的內容必須包括 區塊 或是 交易
檔案位置: "./protocal/connection/content.go"
const InitQuery = "InitQuery"
type InitBlock struct {
BlockHash string `json:"blockHash"`
}
type ReturnBlock struct {
Block blockchain.Block `json:"block"`
}
想法是逐一詢問
舊有節點透過給新節點 最後一區塊 再不斷往前訊問
InitBlock 過去詢問的區塊
ReturnBlock 回傳的區塊資訊
檔案位置: "./protocal/connection/content.go"
func SendContent(conn net.Conn, val string) {
responseBytes := []byte(string(val) + SuffixString)
_, err := conn.Write(responseBytes)
if err != nil {
utils.LogError(err.Error())
return
}
}
type ErrorMessage struct {
Error string `json:"error"`
}
func SentErrorMessage(conn net.Conn, message string) {
response, err := json.Marshal(ErrorMessage{Error: message})
if err != nil {
utils.LogError(err.Error())
}
SendContent(conn, string(response))
}
檔案位置: "./protocal/connection/parameters.go"
const (
SuffixString = "\r\nEOF\r\n"
BufferSize = 1024 * 10
)
基本上 就是透過 制式化的方式 傳輸資訊
檔案位置: "./protocal/connection/node_build.go"
func ReceiveReplyClient(conn net.Conn, transactionChannel chan transaction.Transaction, blockChannel chan blockchain.Block) {
defer func(conn net.Conn) {
err := conn.Close()
if err != nil {
panic(err)
}
}(conn)
buffer := make([]byte, BufferSize)
for {
totalRequest := ""
for {
n, err := conn.Read(buffer)
if err != nil {
utils.LogError(err.Error())
break
}
request := string(buffer[n:])
totalRequest += request
if strings.HasSuffix(totalRequest, SuffixString) {
totalRequest = totalRequest[:len(totalRequest)-len(SuffixString)]
break
}
}
var data interface{}
err := json.Unmarshal([]byte(totalRequest), &data)
if err != nil {
utils.LogError(err.Error())
return
}
switch v := data.(type) {
case BroadcastTransaction:
transactionChannel <- v.Transaction
case BroadcastBlock:
blockChannel <- v.Block
case InitBlock:
if v.BlockHash == InitQuery {
lastBlock, err := block_control.GetLastBlock()
if err != nil {
SentErrorMessage(conn, "Error to get last block")
utils.LogError(err.Error())
continue
}
Block, err := block.GetBlock(lastBlock)
if err != nil {
SentErrorMessage(conn, "Error to get block")
utils.LogError(err.Error())
continue
}
response, err := json.Marshal(ReturnBlock{Block: Block})
if err != nil {
SentErrorMessage(conn, "Error to send block")
utils.LogError(err.Error())
continue
}
SendContent(conn, string(response))
} else {
Block, err := block.GetBlock(v.BlockHash)
if err != nil {
SentErrorMessage(conn, "Error to get block")
utils.LogError(err.Error())
continue
}
response, err := json.Marshal(ReturnBlock{Block: Block})
if err != nil {
SentErrorMessage(conn, "Error to send block")
utils.LogError(err.Error())
continue
}
SendContent(conn, string(response))
}
case ErrorMessage:
utils.LogError(v.Error)
default:
SentErrorMessage(conn, "Unknown request")
}
}
}
func CommunicateClient(ConnectionChannel chan net.Conn, BroadcastTransactionChannel chan transaction.Transaction, BroadcastBlockChannel chan blockchain.Block) {
var connections []net.Conn
for {
select {
case val := <-ConnectionChannel:
connections = append(connections, val)
case b := <-BroadcastBlockChannel:
response, err := json.Marshal(BroadcastBlock{Block: b})
fmt.Println(b.BlockHash)
for _, connection := range connections {
if err != nil {
SentErrorMessage(connection, "Error to send block")
utils.LogError(err.Error())
continue
}
SendContent(connection, string(response))
}
case t := <-BroadcastTransactionChannel:
response, err := json.Marshal(BroadcastTransaction{Transaction: t})
for _, connection := range connections {
if err != nil {
SentErrorMessage(connection, "Error to send transaction")
utils.LogError(err.Error())
continue
}
SendContent(connection, string(response))
}
default:
continue
}
}
}
ReceiveReplyClient
此處為接收相鄰節點的資訊 並做處理或回覆
CommunicateClient
此處主要是主動傳送訊息給連接的節點
連接節點後就要開始去更新區塊資料庫
收到區塊時同時要建立 UTXO 資料庫內容
檔案位置: "./protocal/connection/node_build.go"
func InitBlocks(conn net.Conn) {
flag := false
request, err := json.Marshal(InitBlock{BlockHash: InitQuery})
if err != nil {
utils.LogError(err.Error())
return
}
SendContent(conn, string(request))
for {
totalResponse := ""
buffer := make([]byte, BufferSize)
for {
n, err := conn.Read(buffer)
if err != nil {
utils.LogError(err.Error())
break
}
response := string(buffer[n:])
totalResponse += response
if strings.HasSuffix(totalResponse, SuffixString) {
totalResponse = totalResponse[:len(totalResponse)-len(SuffixString)]
break
}
}
var data interface{}
err := json.Unmarshal([]byte(totalResponse), &data)
if err != nil {
utils.LogError(err.Error())
return
}
switch v := data.(type) {
case ReturnBlock:
if err := block.SetBlock(v.Block); err == nil {
err := initalize.BuildUTXO(v.Block)
if err != nil {
return
}
if v.Block.BlockTop.PreviousHash == conseous.GenesisBlockPreviousHash {
return
}
request, err := json.Marshal(InitBlock{BlockHash: v.Block.BlockTop.PreviousHash})
if err != nil {
utils.LogError(err.Error())
return
}
SendContent(conn, string(request))
if !flag {
err := block_control.SetLastBlock(v.Block.BlockHash)
if err != nil {
return
}
}
flag = true
} else {
return
}
default:
SentErrorMessage(conn, "Unknown Request")
return
}
}
}
檔案位置: "./protocal/initalize/build_utxo.go"
func BuildUTXO(Block blockchain.Block) error {
for i, transaction := range Block.BlockTransactions {
if i == 0 {
continue
}
err := utxo.SetUTXO(transaction)
if err != nil {
return err
}
}
return nil
}
主要就是透過 傳遞過來的區塊內容新增 UTXO
整體來說 建立連線並不困難
困難點 在於溝通格式的決定
希望透過這篇能夠理解
經過節點的建立
現在已經能與其他節點溝通了
不過對於實際區塊運算現在才剛開始
礦工對於交易從節點接收後
如何處理 驗證 運算 選擇
以交易來說 當然先挑手續費高的來做啊
對礦工來說
那究竟要如何實現呢?