JobPlus知识库 IT 工业智能4.0 文章
Go语言打造区块链

1、先直接上代码,有详细注释,然后讲一下如何操作运行这个区块链

[html] 

  1. package main  
  2.   
  3. import (  
  4.     "crypto/sha256"  
  5.     "encoding/hex"  
  6.     "encoding/json"  
  7.     "fmt"  
  8.     "io"  
  9.     "log"  
  10.     "net/http"  
  11.     "os"  
  12.     "strconv"  
  13.     "strings"  
  14.     "sync"  
  15.     "time"  
  16.   
  17.     "github.com/davecgh/go-spew/spew"  
  18.     "github.com/gorilla/mux"  
  19.     "github.com/joho/godotenv"  
  20. )  
  21.   
  22. const difficulty = 1  
  23.   
  24. // Block 的结构  
  25. type Block struct {  
  26.     Index      int  
  27.     Timestamp  string  
  28.     BPM        int  
  29.     Hash       string  
  30.     PrevHash   string  
  31.     Difficulty int  
  32.     Nonce      string  
  33. }  
  34.   
  35. // Blockchain 定义,即很多区块的集合  
  36. var Blockchain []Block  
  37.   
  38. // 用来记录脉搏信息的结构  
  39. type Message struct {  
  40.     BPM int  
  41. }  
  42. //声明一个互斥量mutex,后面会使用该变量避免出现数据竞争,确保多个区块不会同一时间生成。  
  43. var mutex = &sync.Mutex{}  
  44.   
  45. func main() {  
  46.     err := godotenv.Load()  
  47.     if err != nil {  
  48.         log.Fatal(err)  
  49.     }  
  50.   
  51.     go func() {  
  52.         t := time.Now()  
  53.         genesisBlock := Block{}  
  54.         genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), "", difficulty, ""}  
  55.         spew.Dump(genesisBlock)  
  56.   
  57.         mutex.Lock()  
  58.         Blockchain = append(Blockchain, genesisBlock)  
  59.         mutex.Unlock()  
  60.     }()  
  61.     log.Fatal(run())  
  62.   
  63. }  
  64.   
  65. /**  
  66. 快速搭一个Web服务器。首先创建一个run函数,main函数随后会调用这个函数来运行服务器。  
  67. 在makeMuxRouter()中声明了相应的请求处理函数。请注意,我们需要通过GET请求获取区块链,  
  68. 通过POST请求添加新的区块。区块链无法更改,因此我们不需要实现编辑或删除功能。  
  69. 可以通过浏览器访问http://localhost:8080/来访问我们构建的应用。  
  70.  */  
  71. func run() error {  
  72.     mux := makeMuxRouter()  
  73.     httpPort := os.Getenv("PORT")  
  74.     log.Println("HTTP Server Listening on port :", httpPort)  
  75.     s := &http.Server{  
  76.         Addr:           ":" + httpPort,  
  77.         Handler:        mux,  
  78.         ReadTimeout:    10 * time.Second,  
  79.         WriteTimeout:   10 * time.Second,  
  80.         MaxHeaderBytes: 1 << 20,  
  81.     }  
  82.   
  83.     if err := s.ListenAndServe(); err != nil {  
  84.         return err  
  85.     }  
  86.   
  87.     return nil  
  88. }  
  89.   
  90. // create handlers  
  91. func makeMuxRouter() http.Handler {  
  92.     muxRouter := mux.NewRouter()  
  93.     muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")  
  94.     muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")  
  95.     return muxRouter  
  96. }  
  97.   
  98. // write blockchain when we receive an http request  
  99. func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {  
  100.     bytes, err := json.MarshalIndent(Blockchain, "", "  ")  
  101.     if err != nil {  
  102.         http.Error(w, err.Error(), http.StatusInternalServerError)  
  103.         return  
  104.     }  
  105.     io.WriteString(w, string(bytes))  
  106. }  
  107.   
  108. /**  
  109. 这个函数可以实现新区块的添加过程。  
  110. 我们使用Postman来发起POST请求,向http://localhost:8080发送JSON数据(如{“BPM”:60}),  
  111. 其中包含前面你记录下的那个脉搏次数。  
  112.  */  
  113. func handleWriteBlock(w http.ResponseWriter, r *http.Request) {  
  114.     w.Header().Set("Content-Type", "application/json")  
  115.     var m Message  
  116.   
  117.     decoder := json.NewDecoder(r.Body)  
  118.     if err := decoder.Decode(&m); err != nil {  
  119.         respondWithJSON(w, r, http.StatusBadRequest, r.Body)  
  120.         return  
  121.     }  
  122.     defer r.Body.Close()  
  123.   
  124.     //锁定新的区块,避免数据竞争  
  125.     mutex.Lock()  
  126.     newBlock := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)  
  127.     mutex.Unlock()  
  128.   
  129.     if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {  
  130.         Blockchain = append(Blockchain, newBlock)  
  131.         spew.Dump(Blockchain)  
  132.     }  
  133.   
  134.     respondWithJSON(w, r, http.StatusCreated, newBlock)  
  135.   
  136. }  
  137. /**  
  138. 一旦API调用过程中出现错误就能以JSON格式返回错误信息。  
  139.  */  
  140. func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {  
  141.     w.Header().Set("Content-Type", "application/json")  
  142.     response, err := json.MarshalIndent(payload, "", "  ")  
  143.     if err != nil {  
  144.         w.WriteHeader(http.StatusInternalServerError)  
  145.         w.Write([]byte("HTTP 500: Internal Server Error"))  
  146.         return  
  147.     }  
  148.     w.WriteHeader(code)  
  149.     w.Write(response)  
  150. }  
  151.   
  152. /**  
  153. 确保我们的索引能正确递增,并且当前区块的PrevHash与前一个区块的Hash相匹配。  
  154.  */  
  155. func isBlockValid(newBlock, oldBlock Block) bool {  
  156.     if oldBlock.Index+1 != newBlock.Index {  
  157.         return false  
  158.     }  
  159.   
  160.     if oldBlock.Hash != newBlock.PrevHash {  
  161.         return false  
  162.     }  
  163.   
  164.     if calculateHash(newBlock) != newBlock.Hash {  
  165.         return false  
  166.     }  
  167.   
  168.     return true  
  169. }  
  170.   
  171. /**  
  172. 用HASH256来生成哈希值,以计算Hash以及PrevHash  
  173.  */  
  174. func calculateHash(block Block) string {  
  175.     record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash + block.Nonce  
  176.     h := sha256.New()  
  177.     h.Write([]byte(record))  
  178.     hashed := h.Sum(nil)  
  179.     return hex.EncodeToString(hashed)  
  180. }  
  181.   
  182. // 用前一个区块的hash生成一个新的区块  
  183. func generateBlock(oldBlock Block, BPM int) Block {  
  184.     var newBlock Block  
  185.   
  186.     t := time.Now()  
  187.   
  188.     newBlock.Index = oldBlock.Index + 1  
  189.     newBlock.Timestamp = t.String()  
  190.     newBlock.BPM = BPM  
  191.     newBlock.PrevHash = oldBlock.Hash  
  192.     newBlock.Difficulty = difficulty  
  193.   
  194.     for i := 0; ; i++ {  
  195.         hex := fmt.Sprintf("%x", i)  
  196.         newBlock.Nonce = hex  
  197.         if !isHashValid(calculateHash(newBlock), newBlock.Difficulty) {  
  198.             fmt.Println(calculateHash(newBlock), " do more work!")  
  199.             time.Sleep(time.Second)  
  200.             continue  
  201.         } else {  
  202.             fmt.Println(calculateHash(newBlock), " work done!")  
  203.             newBlock.Hash = calculateHash(newBlock)  
  204.             break  
  205.         }  
  206.   
  207.     }  
  208.     return newBlock  
  209. }  
  210. /**  
  211. 判断计算得到的hash值是否是合适的  
  212. Proof of Work生成的哈希必须具有特定位数的前导零。  
  213. 前导零的位数由程序刚开始定义的difficulty常量来决定(这里这个值为1).  
  214.   
  215.  */  
  216. func isHashValid(hash string, difficulty int) bool {  
  217.     prefix := strings.Repeat("0", difficulty)  
  218.     return strings.HasPrefix(hash, prefix)  
  219. }  

2、运行程序

可以在Golang中直接运行,也可以命令行go run运行,下载源码之后,Readme里面有使用方法

其中的    navigate to this directory and rename the example file `mv example.env .env` 

意思是将项目根目录的example.env文件改名为.env,如下图

然后运行程序,通过postman请求,如下图

然后在浏览器中也可以查看区块链数据


如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏支持
292人赞 举报
分享到
用户评价(0)

暂无评价,你也可以发布评价哦:)

扫码APP

扫描使用APP

扫码使用

扫描使用小程序