一個基于web服務器的PoW案例
一、安裝第三方庫
go get github.com/davecgh/go-spew/spew
這個庫的功能是在命令列格式化輸出內容,
go get github.com/gorilla/mux
這個開發包是用來撰寫Web處理程式的,
go get github.com/joho/godotenv
這個工具包是讀取.env后綴名的檔案中的資料,如果是Linux環境,.env檔案放置在專案的根目錄即可,如果是Windows和Mac OS,.env檔案需要放在GOPATH/src目錄下,
二、定義區塊資訊、難度系數
const difficulty = 4
type Block struct {
Index int
Timestamp string
BMP int
HashCode string
PreHash string
Diff int
Nonce int
}
var Blockchain []Block
type Message struct {
BlockPostMessage int
}
var mutex = &sync.Mutex{}
這里我們定義一下挖礦生成區塊的難度值,然后定義區塊,包含區塊高度、時間戳、交易資訊、當前的Hash值和上一個的Hash值,還有難度和隨機值,
然后定義區塊鏈,用區塊陣列,
然后我們這里要根據Get請求和Post請求來生成區塊,所以定義一個訊息結構體用于存盤Post資訊,
最后定義一個互斥鎖,
三、生成區塊
func generateBlock(oldBlock Block, BlockPostMessage int) Block {
var newBlock Block
newBlock.PreHash = oldBlock.HashCode
newBlock.Index = oldBlock.Index + 1
t := time.Now()
newBlock.Timestamp = t.String()
newBlock.BlockPostMessage = BlockPostMessage
newBlock.Diff = difficulty
for i := 0; ; i++ {
newBlock.Nonce++
hash := calculateHash(newBlock)
fmt.Println(hash)
if isHashValid(hash, newBlock.Diff) {
fmt.Println("挖礦成功")
newBlock.HashCode = hash
return newBlock
}
}
}
每次生成新的區塊前,先獲取先前區塊的Hash值放置在這個區塊的上一個區塊Hash值,然后獲取當前時間,通過String()方法轉換成為時間戳后放入區塊的Timestamp,然后將Post傳遞的訊息放入區塊,將我們固定不變的困難值放入區塊,
然后回圈挖礦,每次挖礦將亂數加一,然后先不管這個區塊能不能成功并入區塊鏈,得先計算它的哈希值才能知道,然后校驗哈希值的前導0,如果成功就輸出挖礦成功,
四、生成哈希值
func calculateHash(block Block) string {
hashed := strconv.Itoa(block.Index) + block.Timestamp +
strconv.Itoa(block.Nonce) + strconv.Itoa(block.BlockPostMessage) +
block.PreHash
sha := sha256.New()
sha.Write([]byte(hashed))
hash := sha.Sum(nil)
return hex.EncodeToString(hash)
}
很簡單的邏輯,將區塊的資料拼接后用sha256進行加密,得到hash值,
五、區塊校驗
func isHashValid(hash string, difficulty int) bool {
prefix := strings.Repeat("0", difficulty)
return strings.HasPrefix(hash, prefix)
}
這個我們本專欄之前的文章介紹了,在此簡單說一下,這里我們就校驗一下哈希值前面的零的數量是不是和難度值一致,
六、啟動HTTP服務器
func run() error {
mux := makeMuxRouter()
httpAddr := os.Getenv("PORT")
log.Println("Listening on ", httpAddr)
s := &http.Server{
Addr: ":" + httpAddr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if err := s.ListenAndServe(); err != nil {
return err
}
return nil
}
我們先從.env檔案中獲取PORT的值,然后監聽獲取的埠號,http.Server是設定http服務器的引數,其中Addr是地址,ReadTimeout、WriteTimeout分別是讀寫超時時間,然后是設定請求頭的資料大小的最大值,1 << 20是位運算,算出來就是1MB,!!!最重要的就是回呼函式了,這里需要我們自己撰寫來處理Get和Post請求,
然后我們就來監聽事件并且根據監聽到的事件來服務,
七、回呼函式的撰寫
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/",
handGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/",
handWriteBlock).Methods("POST")
return muxRouter
}
mux.NewRouter()是用來創建路由,muxRouter.HandleFunc("/",handGetBlockchain).Methods(“GET”)是根據你訪問的目錄和請求型別來呼叫指定的方法,這里是使用Get方法訪問根目錄就呼叫handGetBlockchain方法,同樣的,muxRouter.HandleFunc("/",handWriteBlock).Methods(“POST”)就是使用Post請求訪問根目錄時就呼叫handWriteBlock方法,
八、處理Get請求
func handGetBlockchain(w http.ResponseWriter, r *http.Request) {
bytes, err := json.MarshalIndent(Blockchain, "", "\t")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.WriteString(w, string(bytes))
}
我們需要將資料轉換為json格式,便于與前端進行互動,
同樣我們的引數分別是回應和請求,然后處理錯誤,當出現500錯誤時,也就是http.StatusInternalServerError,我們將err.Error()寫入w:

如果沒出錯,就將json資料寫入w,
九、處理POST請求
func handWriteBlock(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "application/json")
var message Message
decoder := json.NewDecoder(request.Body)
if err := decoder.Decode(&message); err != nil {
responseWithJSON(writer, request, http.StatusNotFound, request.Body)
}
defer request.Body.Close()
mutex.Lock()
newBlock := generateBlock(Blockchain[len(Blockchain)-1], message.BPM)
mutex.Unlock()
if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
Blockchain = append(Blockchain, newBlock)
spew.Dump(Blockchain)
}
//回傳回應資訊
responseWithJSON(writer, request, http.StatusCreated, newBlock)
}
因為需要服務器回應結果為json,先設定回應頭的"Content-Type"為"application/json",然后從request中讀取JSON資料,將JSON資料轉成Message,如果轉換失敗,就交給下一步處理例外,如果成功就創建新的區塊,
這里使用defer,說明我們要記得關閉請求哦~
然后添加區塊時要記得上鎖,可以防止同個時間點多個POST請求生成區塊,
接下來就要校驗生成的區塊是否正確,如果正確就加入區塊鏈中,
十、處理例外
func responseWithJSON(writer http.ResponseWriter, request *http.Request,
code int, inter interface{}) {
writer.Header().Set("Content-Type", "application/json")
response, err := json.MarshalIndent(inter, "", "\t")
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
writer.Write([]byte("HTTP 500:Server Error"))
return
}
writer.WriteHeader(code)
writer.Write(response)
}
如果將傳入的inter轉換為json格式的資料沒有出現錯誤就往回應頭寫入回應碼,并將資料寫入,
十一、校驗區塊是否正確
func isBlockValid(newBlock, oldBlock Block) bool {
if oldBlock.Index+1 != newBlock.Index {
return false
}
if oldBlock.HashCode != newBlock.PreHash {
return false
}
if calculateHash(newBlock) != newBlock.HashCode {
return false
}
return true
}
這里校驗了新區塊的index是否等于原來最后一個區塊的index加一,新區塊的PreHash應該等于之前區塊鏈最后一個區塊的HashCode,然后還需要再一次計算區塊的哈希值,進行比對,
十二、主邏輯
然后我們現在用Go實作通過http請求來完成區塊鏈,
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
go func() {
t := time.Now()
genessisBlock := Block{}
genessisBlock = Block{0, t.String(),
0, calculateHash(genessisBlock),
"", difficulty, 0}
mutex.Lock()
Blockchain = append(Blockchain, genessisBlock)
mutex.Unlock()
spew.Dump(genessisBlock)
}()
//創建http服務器的啟動函式
log.Fatal(run())
}
godotenv.Load()加載一個檔案,如果不填寫引數,就默認是加載.env檔案,
這個.env檔案我們這里就只需要填寫一個埠號,

這里我們先將創世區塊加入區塊鏈,然后用spew.Dump()將其格式化輸出到命令列,
最后我們會要用run來啟動http服務器,
十三、運行結果
我們可以使用curl來進行get和post請求,

這是get請求,得到區塊鏈,

這是進行post請求,新建一個區塊加到了區塊鏈,

可以看到再次get請求,已經有新的區塊在區塊鏈中了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/356821.html
標籤:區塊鏈
